作用域

作用域是 ArkType 的基础,也是用户想要完全控制配置并使其自己的关键字在字符串定义语法中流畅可用时最强大的功能之一。

作用域就像代码中的作用域一样——一个解析空间,在其中您可以定义类型、泛型或其他作用域。type 导出实际上只是我们默认 Scope 上的一个方法!

定义作用域

要定义作用域,您可以 import { scope } from "arktype" 或使用默认 type 导出上的 type.scope

作用域被指定为一个对象字面量,将名称映射到定义。

import { scope } from "arktype"

const  = scope({
	// 关键字在您的作用域中仍然可用
	Id: "string",
	// 但您也可以直接引用自己的别名!
	User: { id: "Id", friends: "Id[]" },
	// 您的别名将与 ArkType 的关键字一起自动完成和验证
	UsersById: {
		"[Id]": "User | undefined"
	}
})

coolScope 是一个具有可重用方法如 typegeneric 的对象。您可以使用它来创建额外的 Type,这些 Type 可以引用您的别名——iduserusersById

不要在 type 中包装您的作用域定义!

即使您在作用域定义中引用它,全局 'type' 解析器也只知道内置关键字。

const  = ({
	Id: "string",
	// ❌ 在 `type` 中包装此定义将失败
	BadEntity: ({
		id: "Id"
TypeScript: 'Id' is unresolvable 
}), // ✅ 直接引用作用域定义而不是包装它们 GoodEntity: { id: "Id" } })

如果您需要在作用域内部访问流畅语法,请参阅 thunks


const  = .type({
	name: "string",
	members: "User[]"
})

// 链式定义在与原始 Type 相同的作用域中解析
const  = .and({
	ownerId: "Id"
})

要直接使用作用域类型,您必须将您的 Scope .export() 到一个 ModuleModule 只是一个将别名映射到 Type 的对象。它们可以用于验证或在任何其他 Type 可以使用的上下文中。


const  = .export()

const  = .User({
	id: "99",
	friends: ["7", 8, "9"]
})

if ( instanceof type.errors) {
	// 悬停摘要以查看验证错误
	console.error(.)
}

.export() 还可与扩展运算符结合使用,以扩展您的 Scope。回想一下,Type 可以作为定义被引用。这意味着将 Module 展开到传递给 scope 的定义中,会将该 Module 的所有别名包含在您的新 Scope 中。


const  = ({
	three: "3",
	sixty: "60",
	no: "'no'"
})

const  = ({
	....export(),
	// 如果您不想包含整个作用域,您可以传递 ...别名的列表
	....export("three", "sixty"),
	saiyan: {
		powerLevel: "number > 9000"
	}
})

如果您不打算重用您的 Scope 来创建额外类型,通常可以内联导出它:

const  = ({
	Ez: "'moochi'"
}).export()

type.module 作为此模式的语法糖可用:

const  = type.module({
	Ez: "'moochi'"
})

循环类型

作用域使创建递归 Type 变得容易。只需像引用任何其他别名一样引用该别名:

export const  = ({
	Package: {
		name: "string",
		"dependencies?": "Package[]",
		"contributors?": "Contributor[]"
	},
	Contributor: {
		email: "string.email",
		"packages?": "Package[]"
	}
}).export()

循环类型被推断到任意深度。在运行时,它们可以安全地验证循环数据。


export type  = typeof .Package.infer

const :  = {
	name: "arktype",
	dependencies: [{ name: "typescript" }],
	contributors: [{ email: "david@sharktypeio" }]
}

// 更新 arktype 以依赖自身
.dependencies![0].dependencies = []

// ArkErrors: contributors[0].email must be an email address (was "david@sharktypeio")
const  = .Package()

有些 `any` 并非表面看起来那样!

默认情况下,TypeScript 将匿名循环表示为 ...。然而,如果您启用了 noErrorTruncation,它们会视觉上显示为 any😬

幸运的是,尽管外观如此,该类型否则会按您的预期行为——TypeScript 将提供补全,并在您访问不存在的属性时像正常一样抱怨。

visibility

中间别名对于从别名组成作用域定义很有用。有时,您可能不想在将 Scope export 时将这些别名外部暴露为 Type

这可以通过使用 private 别名来实现:

const  = ({
	// 以 "#" 前缀的别名被视为私有
	"#BaseShapeProps": {
		perimeter: "number",
		area: "number"
	},
	Ellipse: {
		// 引用私有别名时,不应包含 "#"
		"...": "BaseShapeProps",
		radii: ["number", "number"]
	},
	Rectangle: {
		"...": "BaseShapeProps",
		width: "number",
		height: "number"
	}
})

// 私有别名可以从任何作用域定义中引用,
// 即使在原始作用域之外
const  = .type("Partial<BaseShapeProps>")

// 当作用域导出到 Module 时,它们不会被包含
// 悬停查看 Scope 的导出
const  = .export()

import()

私有别名特别适用于在不污染作用域的情况下构建作用域,而不会包含您可能想内部引用的每个别名。为此,作用域有一个 import() 方法,其行为与 export() 相同,但将所有导出的别名转换为 private

const  = ({
	"withId<o extends object>": {
		"...": "o",
		id: "string"
	}
})

const  = type.module({
	// 因为我们在这里使用 `import()`,我们可以内部引用我们的实用程序,
	// 但它们不会包含在 `userModule` 中。
	// 如果我们使用 `export()` 而不是,`withId` 可以访问 `userModule` 上。
	....import(),
	Payload: {
		name: "string",
		age: "number"
	},
	db: "withId<Payload>"
})

子模块

如果您使用过像 string.emailnumber.integer 这样的关键字,您可能想知道别名是否可以在您自己的作用域中分组。回想 作用域介绍type 实际上只是 ArkType 默认 Scope 上的一个方法,这意味着它的所有功能都可以外部使用,包括称为 Submodules 的别名组。

子模块是具有共享前缀的别名组。要定义一个,只需将前缀的值分配给包含您想要名称的 Module

const  = type.module({ alias: "number" })

const  = ({
	a: "string",
	b: "sub.alias",
	sub: 
})

const  = .type({
	someKey: "sub.alias[]"
})

子模块是自底向上解析的。这意味着子别名可以在根作用域中直接引用,但根别名不能从子模块中引用,即使它是内联的。

嵌套

子模块可以嵌套到任意深度:


const  = ({
	// 引用我们之前示例中的 rootScope
	newRoot: .export()
})

const  = .type({
	someOtherKey: "newRoot.sub.alias | boolean"
})

根化

我们之前示例中的子模块将 Type 分组在一起,但不能像 stringnumber 那样作为 Type 本身被引用。要定义一个 Rooted Submodule,只需使用名为 root 的别名:

const  = type.module({
	root: {
		name: "string"
	},
	// 子别名可以通过引用 'root' 来扩展基本类型
	// 像任何其他别名一样
	Admin: {
		"...": "root",
		isAdmin: "true"
	},
	Saiyan: {
		"...": "root",
		powerLevel: "number > 9000"
	}
})

const  = type.module({
	User: ,
	// user 现在可以在定义中直接引用
	Group: "User[]",
	// 或用作前缀来访问子别名
	ElevatedUser: "User.Admin | User.Saiyan"
})

thunks

当用户首次学习作用域时,最常见的错误之一是在嵌套 type 调用中引用别名:

const  = scope({
	id: "string#id",
	user: ({
		name: "string",
		id: "id"
TypeScript: 'id' is unresolvable 
}) })

此错误发生是因为虽然 id 别名可以直接在当前作用域中解析,但 type 只允许引用内置关键字。在这种情况下,type 包装是多余的,修复方法是简单地移除它:

const  = ({
	Id: "string#id",
	User: {
		name: "string",
		// 现在正确解析
		id: "Id"
	}
})

然而,即使通过组合别名和元组表达式有可能不调用 type 来定义您的作用域,Type 上可用的流畅方法可以定义复杂的类型,否则表达起来会很繁琐。在这些情况下,您可以使用 thunk 定义 来访问您当前定义的作用域上的 type 方法:

const  = ({
	Id: "string#id",
	expandUserGroup: () =>
		.type({
			name: "string",
			id: "Id"
		})
			.or("Id")
			.pipe(user =>
				typeof user === "string" ? { id: user, name: "Anonymous" } : user
			)
			.array()
			.atLeastLength(2)
})

const  = .export()

// 输入被验证并转换 为:
// [{ name: "Magical Crawdad", id: "777" }, { name: "Anonymous", id: "778" }]
const  = .expandUserGroup([
	{ name: "Magical Crawdad", id: "777" },
	"778"
])

虽然 thunk 定义实际上仅在定义作用域时有用,但它们可以在任何期望 Type 定义的地方使用:

// 您 *可以* 在任何地方使用它们,但您 *应该* 吗?(不)
const  = (() =>
	({ inelegantKey: () => ("'inelegant value'") })
)

On this page