定义一个类型 
我们来定义一个商品类型。
const Product = typedef({
    name: string,
    price: money,
})
console.log(typeinit(Product))
// output: { name: '', price: '0.00' }
console.log(typeinit(Product, { name: 'Apple' }))
// output: { name: 'Apple', price: '0.00' }这种由字段组成的结构称为:Struct类型。
其中还使用了金额类型,定义如下:
const money = typedef({
    '@type': string,
    '@value': () => '0.00,
    '@adjust': (self) => {
        // 仅演示,实际使用请注意精度
        return parseFloat(self).toFixed(2)
    },
})这种包含描述符号@type的称为:修饰类型。@type指向的就是被修饰的类型。
 除了基础类型bool,int32,float64,string,unknown,还内置了从unknown修饰来的object,array,和从array修饰来的CArray。 任何类型都可以被修饰,包括Struct类型。
描述符号 
修饰类型时,必须提供@type,若没有则表示一个Struct类型。
| 描述符号 | 说明 | 
|---|---|
| @type | 被修饰的类型 | 
| 生成值 | |
| @value | 生成默认值 | 
| 初始化 | 执行顺序 @value > @init | 
| @init | 自定义初始化,无返回值 只对 Struct,unknown有效 | 
| 校验与修正 | 执行顺序 @verify > @adjust > @assert | 
| @verify | 对值进行校验,抛出错误 | 
| @adjust | 对值进行修正,返回修正后的值 | 
| @assert | 对值进行断言,抛出错误 | 
| 引用与释放 | 极少使用 只对 Struct,unknown有效 | 
| @retain | 当被父结构体的字段引用时触发 | 
| @release | 当从父结构体的字段释放时触发 | 
| 其他 | |
| @notnil | 禁止 undefined,null | 
| @noinit | 禁用自动初始化,必须显式使用 typeinit来初始化只对 Struct,unknown有效 | 
| @change | 极少使用 标记此类型会触发观察,称为可变类型 只对 unknown有效需使用 change()令unknown像Struct一样触发观察 | 
关于 Struct 类型 
Struct类型是工作中最常用到的,我们来掌握几个进阶操作。
修饰须知
- 不能在修饰中定义新字段,因为修饰不是继承。
- 修饰后可以使用ruledef定义新规则,但规则名不能重复。
初始化时立刻触发规则 
我们知道,只有当字段被赋值时才会触发规则,所以我们巧妙地利用@init来实现。
const Product = typedef({
    price: money,
    // ...
    '@init': (self) => {
        self.price = self.price
    },
})
ruledef(
    Product,
    'price',
    {
        price: true,
    },
    (self) => {
        // ...
    }
)递归引用 
比如实现一个链表结构并不难,但要具备正确的类型提示就要动点脑筋了。
const __LinkedNode = typedef({}) as TypeDesc<unknown>
const _LinkedNode = typedef({
    value: unknown,
    next: __LinkedNode,
}, __LinkedNode as never)
type LinkedNode = typeof _LinkedNode & TypeDesc<Struct<{
    next: LinkedNode
}>>
const LinkedNode = _LinkedNode as LinkedNode如上,先定义一个空结构体类型,然后再定义字段,就实现了链表结构。接下来重新定义一个递归引用的交叉类型,最后再使用类型断言,就得到正确的类型提示了。
循环依赖 
如果有循环引用的 A B 两个类型,分别定义在两个文件中,就会导致循环依赖。这是先有鸡还是先有蛋的问题,需要我们好好发挥聪明才智才行。
我们先创建 A.ts B.ts 两个文件,并分别定义空的结构体类型,这相当于头文件,只声明但不具体实现。然后再创建 A.def.ts B.def.ts 两个文件,在其中定义具体实现。最后在使用 A B 类型前,比如在入口文件中引入一次 A.def.ts B.def.ts 就好了。
import './A.def'
import './B.def'
import { A } from './A'
import { B } from './B'
// 正确的类型提示
typeinit(A).b
typeinit(B).aimport type { A_def } from './A.def'
export const A = typedef({}) as A_defimport { A } from './A'
import { B } from './B'
const __B = B as TypeDesc<unknown>
const __A = A as TypeDesc<unknown>
const _A = typedef({
    b: __B,
}, __A as never)
export type A_def = typeof _A & TypeDesc<Struct<{
    b: typeof B
}>>import type { B_def } from './B.def'
export const B = typedef({}) as B_defimport { A } from './A'
import { B } from './B'
const __A = A as TypeDesc<unknown>
const __B = B as TypeDesc<unknown>
const _B = typedef({
    a: __A,
}, __B as never)
export type B_def = typeof _B & TypeDesc<Struct<{
    a: typeof A
}>>Uncaught ReferenceError: Cannot access 'xxx' before initialization
遇到这个错误,大概率是循环引用导致的,需要使用此方法来解决。
反射 
极少使用。反射是从结构体实例获取其类型。比如为某个结构体实例定义规则,但该类型未知。此时就是反射的用武之地。
const Delegate = typedef({
    ref: structof(someInstance),
})
ruledef(
    Delegate,
    'someRule',
    {
        ref: {
            // ...
        },
    },
    (self) => {
        // ...
    }
)
typeinit(Delegate, { ref: someInstance })TIP
此示例同时介绍了一种委托模式。使用委托,我们可以为所欲为。广义上讲,间接引用都算是委托,我们其实一直在用,却不自知而已。
获取字段类型 
极少使用。先获取到结构体类型的所有字段,然后从字段名取出该类型。
const User = typedef({
    contact: typedef({
        phone: string,
        email: string,
    }),
})
const Contact = structbody(User).contact使用数组 
默认数组元素的类型都是unknown,若要具有正确的类型提示,需要使用类型断言。
const HomePage = typedef({
    banners: array as TypeDesc<array<typeof string>>,
    products: typedef({
        '@type': array as TypeDesc<array<typeof Product>>,
    }),
})上面示例,我们使用了两种数组方式:直接使用array和修饰array(建议)。这两种没有区别,都能提供正确的类型提示。但修饰array的好处是,可以使用描述符号@verify来校验数组的元素。
还有一个CArray。两者的区别是,如果定义了规则观察该字段,array类型的索引更新时不会触发规则,而CArray类型的索引更新时会触发规则。因为CArray是可变类型,具体细节请阅读源码。
可变类型 
极少使用。含有描述符号@change的unknown类型,以及衍生的修饰类型,都是可变类型。配合使用change()可以令unknown像Struct一样触发观察。
const HomePage = typedef({
    products: typedef({
        '@type': array as TypeDesc<array<typeof Product>>,
        '@change': true,
    }),
})
ruledef(
    HomePage,
    'products',
    {
        products: true,
    },
    (self) => {
        console.log(self.products.length)
        // ...
    }
)
const { products } = typeinit(HomePage)
products[0] = typeinit(Product)
change(products) // output: 1上面示例也可以换用CArray来实现,其原理就使用了change()与Proxy,所以当索引更新时会自动触发规则。