class-formatter
TypeScript icon, indicating that this package has built-in type declarations

3.3.1 • Public • Published

class-formatter 使用文档

注意:

当前版本仅支持 stage1 阶段的装饰器提案,若您想支持 stage3,请将 class-formatter 升级到 5.0.0 及以上的版本

目录

简介

一套装饰器风格的数据格式化方法。

根据模板针对数据的每个字段进行格式化。

使用场景

复杂数据结构格式化

在一些复杂场景中(例如:大表单数据提交),会遇到层层嵌套的复杂数据结构,且不同字段之间拥有复杂的联动关系。 class-formatter 可以通过装饰器简化这种格式化过程,减轻心智负担,将格式化从业务逻辑中抽离出来

安装

npm install class-formatter

tsconfig.json 需进行如下配置:

{
    "compilerOptions": {
        ...
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        ...
    },
    ...
}

名称释义

  • 模板: 类即是模板。
  • 指令: 模板中属性或方法的 class-formatter 装饰器,一个装饰器即为一个指令。
  • 源数据: 被转换的数据。
  • 转换: 调用 executeTransformexecuteTransArray 函数对源数据进行格式化的行为。

例如:

class User {
    @toString()
    name!: string;

    @toNumber()
    age!: number;
}

const user = {
    name: '张三',
    age: 18
};

const result = executeTransform(User, user);
  • 模板:上述示例中,类 User 即为模板,也是转换函数(executeTransform)的第一个参数。
  • 指令:上述示例中,@toString()@toNumber() 即为指令。
  • 源数据:上述示例中,对象 user 即为源数据。

使用方法

普通对象

class User {
    @toString()
    name!: string;

    @toNumber()
    age!: number;
}

const user = {
    name: '张三',
    age: '18'
};

const formatUser = executeTransform(User, user);
// => { name: '张三', age: 18 }

在上述示例中,formatUser 一定拥有 字符串类型 属性 name数字类型 属性 age

数组

class User {
    @toString()
    name!: string;

    @toNumber()
    age!: number;
}

const users = [{
    name: '张三',
    age: '18'
}];

const formatUsers = executeTransArray(User, users);

若源数据为数组,则可以使用 executeTransArray 进行格式化。

默认值

class User {
    @toString()
    name: string = '张三';

    @toNumber(1)
    age!: number;
}
  • 若源数据中不存在属性,则会根据默认值生成该属性。
    • 例如根据上述模板,若源数据不存在 name 属性,则转换结果一定拥有字符串属性 name,且 name 属性值为 '张三'。
  • 默认值有两种传入方式,模板传入与指令传入,其中模板传入优先级 高于 指令传入。
    • 上述模板中,name 为模板传入,age 为指令传入。

API

属性装饰器 说明 类型 默认值
toNumber 若属性为非数字类型,则将属性转换为 number 类型。
autoTranstrue 时会自动将字符串转换为数字。
(value?: NumberConfig | number) => Decorator defaultValue: 0
autoTrans: true
toString 若属性为非字符串类型,则将属性转换为 string 类型。
autoTranstrue 时会自动将数字转换为字符串。
(value?: StringConfig | string) => Decorator defaultValue: ''
autoTrans: true
toBoolean 若属性为非布尔类型,则将属性转换为 boolean 类型 (value?: BooleanConfig | boolean) => Decorator defaultValue: false
toSymbol 若属性为非 symbol 类型,则将属性转换为 symbol 类型 (value?: SymbolConfig | symbol) => Decorator defaultValue: Symbol()
toRegExp 若属性为非正则类型,则将属性转换为正则类型 (value?: RegConfig | RegExp | string) => Decorator defaultValue: new RegExp('')
toType 若属性为非对象类型,则将属性转换为对象。
若指定了 Type,则可以将类型转换为 Type 的类型。
(value?: ObjectConfig | Type) => Decorator defaultValue: {}
toArray 若属性为非数组类型,则将属性转换为数组。
若指定了 Type,则可以将数组内所有数据转换为 Type 的类型。
(value?: ArrayConfig | Type) => Decorator defaultValue: []
toKeep 保持源数据引用 keys?: ModelKey | ModelKey[]) => Decorator --
Remove 移除属性 (value?: RemoveConfig | RemoveCallback | ModelKey | ModelKey[]) => Decorator --
Format 对属性进行自定义格式化。
注意:Format 会在所有内置校验结束后执行,且不限制返回值类型,使用时请格外注意
(callback: FormatCallback, keys?: ModelKey | ModelKey[]) => Decorator --
Rename 对属性重命名。 (name: string, keys?: ModelKey | ModelKey[]) => Decorator --
方法装饰器 说明 类型 默认值
ExtendMethod 在结果中继承被装饰的方法或访问器 (keys?: ModelKey | ModelKey[]) => MethodDecorator --
类装饰器 说明 类型 默认值
Extend 继承父类的全部装饰器 (parent: Type) => ClassDecorator --
Mixins 混入,同时继承全部类的装饰器 (...parents: Type[]) => ClassDecorator --

executeTransform

参数名称 说明 类型
ClassType 模板类 Type
values 被格式化对象 any
options 配置项 Omit<FormatOptions, 'map'>

executeTransArray

参数名称 说明 类型
ClassType 模板类 Type
values 被格式化对象 any[]
options 配置项 FormatOptions

Interface

Decorator

type Decorator =  (target: any, propertyKey: string) => void;

NumberConfig

type NumberConfig = { 
    defaultValue?: number;
    autoTrans?: boolean;
    keys?: ModelKey | ModelKey[];
};

StringConfig

type StringConfig = { 
    defaultValue?: string;
    autoTrans?: boolean;
    keys?: ModelKey | ModelKey[];
};

BooleanConfig

type BooleanConfig = { 
    defaultValue?: boolean;
    keys?: ModelKey | ModelKey[]; 
};

SymbolConfig

type SymbolConfig = { 
    defaultValue?: symbol;
    keys?: ModelKey | ModelKey[]; 
};

RegConfig

type RegConfig = { 
    defaultValue?: Regexp | string;
    keys?: ModelKey | ModelKey[]; 
};

ObjectConfig

type ObjectConfig<T = any> = { 
    defaultValue?: Partial<T>; 
    ClassType?: Type<T>;
    keys?: ModelKey | ModelKey[];
};

ArrayConfig

type ArrayConfig<T = any> = { 
    defaultValue?: Partial<T>[]; 
    ClassType?: Type<T>;
    keys?: ModelKey | ModelKey[];
    map?: (value: T, index: number, array: T[]) => T;
};

FormatCallback

type FormatCallback = (item, values) => any;
名称 说明
item 属性被转换后的值
values 源数据
注意:values 源数据的直接引用,请勿在转换过程中对其进行修改
shareValue 共享数据
注意:shareValue 为共享数据的直接引用,请勿在转换过程中对其进行修改

RemoveCallback

type RemoveCallback = (value: any, target: Readonly<any>, shareValue?: any) => boolean;

RemoveConfig

type RemoveConfig = {
    beforeRemove?: RemoveCallback;
    keys?: ModelKey | ModelKey[];
};

Type

interface Type<T = any> extends Function {
    new(...args: any[]): T;
}

ModelKey
执行键类型

type ModelKey = string | number;

FormatOptions

type FormatOptions<T> = {
    mergeSource?: boolean;
    key?: ModelKey;
    shareValue?: any;
    deep?: number;
    map?: (value: T, index: number, array: T[]) => T;
}
名称 说明 类型
mergeSource 是否将 源数据 合并到转换结果中 boolean
key 执行键 ModelKey
shareValue 共享数据。可在自定义装饰器与 Format 中获取的额外数据 any
deep 转换深度限制。详情 boolean
map 原生数组的 map 方法。仅在 executeTransArray 中生效 (value: T, index: number, array: T[]) => T

自定义属性装饰器

const CustomDecorator = createFormatDecorator((values, shareValue, ...args) => {
    // ...Do something
    return values.name;
});

// type CustomeDecorator = (...args) => DecoratorFun

class User {
    @CustomDecorator('Hello')
    name!: string;
}

createFormatDecorator

属性 说明 类型
callback 装饰器执行回调 (values, shareValue, ...args) => any
keys 可选参数 执行键 ModelKey | ModelKey[]

callback

属性 说明 类型
values 源数据
注意:values 为源数据的直接引用,请勿在转换过程中对其进行修改
any
shareValue 共享数据
注意:shareValue 为共享数据的直接引用,请勿在转换过程中对其进行修改
any
args 在生成的装饰器中传入的参数 any[]

关于执行顺序

所有指令丛上到下依次执行,指令格式化的结果会被传递给下一个指令。

const toAge = createFormatDecorator((values: Test) => {
    return 9;
});

class Test {
    @toAge()            // 9
    @toBoolean()        // false
    @toString('7')      // '7'
    @toNumber(5)        // 7
    @Format(v => v + 1) // 8
    @Format(v => v - 7) // 1
    age!: number;
}

const res = executeTransform(Test, {});
console.log(res);   // { age: 1 }

关于执行键

executeTransformoptions 属性中提供了 key 属性,以下称为 rootKey 。 在所有指令中均提供了 keys 属性的入口,以下称为 propertyKeys

  • rootKey 不存在,则会执行所有不存在 propertyKeys 的指令。
  • rootKeypropertyKeys 同时存在,仅有 propertyKeys 包含 rootKey 的指令会被执行。
  • 不存在 propertyKeys 的指令会被无条件执行。
class User {
    @toString({ keys: 'submit' })
    name!: string;

    @toNumber()
    age!: number;
}

const user = {
    name: '张三',
    age: '18'
};

const formatUser = executeTransform(User, user, {
    key: 'submit'
});

上述示例中:

  • toNumber 指令会无条件执行。
  • executeTransform 中传入的 key'submit',则 toString 指令会被执行,否则将忽略 name 属性。

由于执行键的存在,我们可以方便的在同一个模板上定制多套格式化方法。如下:

class User {
    @toString()
    @toString({ defaultValue: '张三', keys: '1' })
    @toString({ defaultValue: '李四', keys: '2' })
    @toString({ defaultValue: '王五', keys: '3' })
    name!: string;

    @toNumber()
    age!: number;
}

注意:当一个属性拥有多个装饰器时,模板的可读性下降,且难以迭代。因此建议优先考虑模板继承策略进行个性化复用。如无必要请尽量避免使用执行键。

多装饰器管理

class-formatter 提供了 createBatchDecorators 方法用于对多装饰器进行管理。

createBatchDecorators

属性 说明 类型
...decorators 需要统一管理的装饰器 PropertyDecorator[]

通过 createBatchDecorators 方法,我们可以对上述案例进行管理:

const NameManage = createBatchDecorators(
    toString({ defaultValue: '张三', keys: '1' }),
    toString({ defaultValue: '李四', keys: '2' }),
    toString({ defaultValue: '王五', keys: '3' })
);

class User {
    @toString()
    @NameManage()
    name!: string;

    @toNumber()
    age!: number;
}

如上将所有拥有执行键的装饰器封装成 NameManage 装饰器,toString 作为默认格式化指令,NameManage 则根据执行键分发指令。

关于循环引用

  • 若多个模板间存在循环引用,则子模板中引用的父模板失效( typescript 自身限制)。
  • 若模板自身循环引用,则默认可转化深度为 50,超过该深度的转换会被忽略。
  • 若被转换对象存在循环引用,则忽略循环属性。

模板自循环

class Person {
    @toString()
    name!: string;

    @toType(Person)
    child!: Person;
}

const target = {
    name: '父亲',
    child: {
        name: 1
    }
}

// target.child 会被忽略
executeTransform(Person, {});

模板自循环理论上允许存在,但可能导致死循环。 为防止这种情况,且保证格式化顺利进行,class-formatter 限制了执行嵌套深度,默认 50。超过深度的数据会停止转换。 可以通过 options.deep 自行调整深度。

死循环例:

class Person {
    @toString()
    name!: string;

    @toArray(Person)
    childs: Person = [{}];
}

executeTransArray(Person, [{}], {
    deep: 50
});

关于混入

为实现更加灵活的模板组合方案,class-formatter 提供 mixins 方法实现多模板组合。

例如:

class A {
    @toString()
    a!: string;
}

class B {
    @toString()
    b!: string;
}

class C implements A, B {
    a!: string;
    b!: string;

    @toString()
    c!: string;
}

mixins(C, [A, B]);

如此 C 便即继承了 AB 的全部指令。

同时提供了 Mixins 类装饰器来简化混入。

class A {
    @toString('A')
    name!: string;
}

class B {
    @toString('B')
    name!: string;
}

@Mixins(A, B)
class C implements A, B {
    @toString()
    c!: string;

    name!: string;
}

const res = executeTransform(C, {});
// res => { name: 'A', c: '' }

Mixins 传参拥有优先级,当多个模板中存在相同的属性时,后面参数的指令将会 完全覆盖 前一个参数的指令。 上述示例中,A 模板的 name 属性的指令将会被 B 模板的 name 完全覆盖。

注意事项

关于使用

  • 所有转化规则均依赖指令,所有拥有指令的属性、方法、访问器均会被转换,其余属性、方法、访问器会被忽略。
  • 指令可以通过 Extend 在多个模板间继承。
  • 多个模板可通过 Mixins 组合成一个大模板,同时共享全部指令。
  • 子模板中声名的同名属性若拥有指令,则子模板的指令将会 完全覆盖 继承的指令。(即重写指令)
  • 子模板的 模板默认值 会覆盖父模板的 模板默认值

Package Sidebar

Install

npm i class-formatter

Weekly Downloads

5

Version

3.3.1

License

MIT

Unpacked Size

146 kB

Total Files

101

Last publish

Collaborators

  • friedrice