TS 应用案例分享 - 1
Published on February 18, 2023Updated on March 23, 2024
Loading content...
目前你在开发一系列的表单页面,对于进入表单的数据,目前是以下格式
TypeScriptinterface DataType { name: string age: number } interface DataType2 { name: string gender: number } interface DataType3 { name: string age: number address: string } // ...
在一波迭代后,发现目前规范的数据格式无法满足前端的展示需求,因此,后端更新了数据结构,目前进入前端的数据格式变成了这样
TypeScriptinterface NewDataType { name: { label: string value: string } age: { label: string value: number } } // NewDataType2 NewDataType3 ...
目前要改的字段有很多,而且有些 DataType
的 key 特别多,改起来很机械,有没有办法让这个过程变得简单一些呢?
引入范型来帮助我们完成这个过程
TypeScriptexport interface FormModelField<T> { label: string value: T } interface NewDataType { name: FormModelField<string> age: FormModelField<number> }
已经好了一些了,但是有些 DataType
的 key 特别多,不想每个都去改,有没有更短一点的办法呢?
分析下需求:
keyof https://www.typescriptlang.org/docs/handbook/2/keyof-types.html Mapped Types https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
TypeScripttype CreateNewDataType<T> = { [key in keyof T]: FormModelField<T[key]> }
OK 非常完美
解决了类型编写面临的重复性问题,接下来还要解决运行层的数据转换问题
TypeScript// newData -> data 或者部分 const newData: NewDataType = { name: { label: 'name', value: 'SedationH', }, age: { label: 'age-label', value: 123, }, } const data: DataType = { name: 'SedationH', age: 123, } const data2: DataType = { age: 123, }
TypeScriptfunction formValueExtractor(newData: NewDataType, fields: any[]) { const res = {} fields.forEach((field) => { if (field in newData) res[field] = newData[field].value }) return res } const newData: NewDataType = { name: { label: 'name', value: 'SedationH', }, age: { label: 'age-label', value: 123, }, } // eslint-disable-next-line no-console console.log(formValueExtractor(newData, ['name', 'age']), formValueExtractor(newData, ['age']), formValueExtractor(newData, []), // 希望有报错 // @ts-expect-error formValueExtractor(newData, ['ccc']), )
fields 和 newData 之间在类型系统上没啥关联,要这俩产生关联
TypeScriptinterface DataType { name: string age: number } export interface FormModelField<K> { label: string value: K } type CreateNewDataType<T> = { [key in keyof T]: FormModelField<T[key]> } type NewDataType = CreateNewDataType<DataType> type CreateDataTypeFromNewDataType<T extends CreateNewDataType<V>, V = object> = { [key in keyof T]: T[key]['value'] } function formValueExtractor<T extends CreateNewDataType<V>, V>(newData: T, fields: (keyof T)[]) { // 理解 T 作为类型的入参,一切要围着他来 const res = {} as CreateDataTypeFromNewDataType<T> fields.forEach((field) => { if (field in newData) res[field] = newData[field].value }) return res } const newData: NewDataType = { name: { label: 'name', value: 'SedationH', }, age: { label: 'age-label', value: 123, }, } // eslint-disable-next-line no-console console.log(formValueExtractor(newData, ['name', 'age']), formValueExtractor(newData, ['age']), formValueExtractor(newData, []), // 希望有报错 // @ts-expect-error formValueExtractor(newData, ['ccc']), ) formValueExtractor(newData, [""]) // https://github.com/SedationH/blog/issues/7