作用域
作用域是 ArkType 的基础,也是用户想要完全控制配置并使其自己的关键字在字符串定义语法中流畅可用时最强大的功能之一。
作用域就像代码中的作用域一样——一个解析空间,在其中您可以定义类型、泛型或其他作用域。type 导出实际上只是我们默认 Scope 上的一个方法!
定义作用域
要定义作用域,您可以 import { scope } from "arktype" 或使用默认 type 导出上的 type.scope。
作用域被指定为一个对象字面量,将名称映射到定义。
coolScope 是一个具有可重用方法如 type 和 generic 的对象。您可以使用它来创建额外的 Type,这些 Type 可以引用您的别名——id、user 和 usersById。
不要在 type 中包装您的作用域定义!
要直接使用作用域类型,您必须将您的 Scope .export() 到一个 Module。Module 只是一个将别名映射到 Type 的对象。它们可以用于验证或在任何其他 Type 可以使用的上下文中。
.export() 还可与扩展运算符结合使用,以扩展您的 Scope。回想一下,Type 可以作为定义被引用。这意味着将 Module 展开到传递给 scope 的定义中,会将该 Module 的所有别名包含在您的新 Scope 中。
如果您不打算重用您的 Scope 来创建额外类型,通常可以内联导出它:
type.module 作为此模式的语法糖可用:
循环类型
作用域使创建递归 Type 变得容易。只需像引用任何其他别名一样引用该别名:
循环类型被推断到任意深度。在运行时,它们可以安全地验证循环数据。
有些 `any` 并非表面看起来那样!
默认情况下,TypeScript 将匿名循环表示为 ...。然而,如果您启用了 noErrorTruncation,它们会视觉上显示为 any😬
幸运的是,尽管外观如此,该类型否则会按您的预期行为——TypeScript 将提供补全,并在您访问不存在的属性时像正常一样抱怨。
visibility
中间别名对于从别名组成作用域定义很有用。有时,您可能不想在将 Scope export 时将这些别名外部暴露为 Type。
这可以通过使用 private 别名来实现:
import()
私有别名特别适用于在不污染作用域的情况下构建作用域,而不会包含您可能想内部引用的每个别名。为此,作用域有一个 import() 方法,其行为与 export() 相同,但将所有导出的别名转换为 private。
子模块
如果您使用过像 string.email 或 number.integer 这样的关键字,您可能想知道别名是否可以在您自己的作用域中分组。回想 作用域介绍,type 实际上只是 ArkType 默认 Scope 上的一个方法,这意味着它的所有功能都可以外部使用,包括称为 Submodules 的别名组。
子模块是具有共享前缀的别名组。要定义一个,只需将前缀的值分配给包含您想要名称的 Module:
子模块是自底向上解析的。这意味着子别名可以在根作用域中直接引用,但根别名不能从子模块中引用,即使它是内联的。
嵌套
子模块可以嵌套到任意深度:
根化
我们之前示例中的子模块将 Type 分组在一起,但不能像 string 和 number 那样作为 Type 本身被引用。要定义一个 Rooted Submodule,只需使用名为 root 的别名:
thunks
当用户首次学习作用域时,最常见的错误之一是在嵌套 type 调用中引用别名:
此错误发生是因为虽然 id 别名可以直接在当前作用域中解析,但 type 只允许引用内置关键字。在这种情况下,type 包装是多余的,修复方法是简单地移除它:
然而,即使通过组合别名和元组表达式有可能不调用 type 来定义您的作用域,Type 上可用的流畅方法可以定义复杂的类型,否则表达起来会很繁琐。在这些情况下,您可以使用 thunk 定义 来访问您当前定义的作用域上的 type 方法:
虽然 thunk 定义实际上仅在定义作用域时有用,但它们可以在任何期望 Type 定义的地方使用: