TypeScript:带属性关联的泛型对象解构问题研究

简介: ## 背景 ### 利用泛型进行属性关联 大家在业务中一定很熟悉这样的场景,针对某个action,传递一个指定类型的payload。 有了ts之后,我们会期望用泛型将action和payload的对应关系约束起来。 例如下面这段demo ``` typescript type Payloads = { people: { name: string, age: number };

背景

利用泛型进行属性关联

大家在业务中一定很熟悉这样的场景,针对某个action,传递一个指定类型的payload。
有了ts之后,我们会期望用泛型将action和payload的对应关系约束起来。
例如下面这段demo

type Payloads = {
  people: { name: string, age: number };
  machine: { id: string, price: number };
}
type Keys = keyof Payloads;

type Foo<T extends Keys, P> = {
  key: T;
  payload: P;
  info: string;
};
type AllFoos = { [K in Keys]: Foo<K, Payloads[K]> };
type AnyFoo = AllFoos[Keys];

11cb5d912244cc3374892f9e3991b021.png
这里我首先定义了action和payload结构关联的map,然后创建类型ALLFoos,利用泛型对象将action的key和对应的payload结构关联起来。最后输出的AnyFoo类型就是所有有效Foo类型的联合类型。

现在我就可以利用ts的类型检查,确保key和payload的关系约束了。

const makeFoo = (key: Keys): AnyFoo => {
  if (key === 'people') {
    return {
      key,
      payload: { name: 'xinyuehtx', age: 18 },
      info: '帅'
    }
  } else {
    return {
      key,
      payload: { id: '终结者', price: 998 },
      info: '莫得感情'
    }
  }
}

如果我们将“终结者”的属性名称改成“name”,ts就会帮我们报错
image.png

属性关联类型解构

那么现在问题来了,假如我要新增一个对外开放的API,要从Foo这个类型中去掉info属性,但是又不能改变Foo现有的结构。这个需求常见于内部方法对外开放时,去除一些隐私信息。毕竟面对第三方,˙终结者不希望别人知道他是一个莫得感情的机器,我也不希望别人发现我很帅。

第一时间我想到的方案如下,建立一个Bar类型,定义如Foo,但是去掉了info属性。
然后从foo对象中选取特定属性进行赋值。

type Bar<T extends Keys, P> = {
  key: T;
  payload: P;
};

type AllBars = { [K in Keys]: Bar<K, Payloads[K]> };
type AnyBar = AllBars[Keys];

const makeBarFromFoo = (foo: AnyFoo): AnyBar => ({
  key: foo.key,
  payload: foo.payload,
})

image.png
但是这里ts报错了。为什么呢?在这里,ts能够识别foo.key是一个'people'|'machine'的联合类型,foo.payload也是对应payload结构的联合类型,但是他们之间的关联丢失了。
ts不能消除people对应{id,price}的可能性。

解决方案

  • 通过as强转:
    这个是最偷懒的方案,直接跳过检查,通过人工约束。可行,但是非到万不得已不要用
  • 在实现代码中判断payload属性
const makeBarFromFoo = (foo: AnyFoo): AnyBar => {
  if (foo.key === 'people' && foo.payload.name && foo.payload.age) {
    return {
      key: foo.key,
      payload: foo.payload,
    }
  }
  //...剩余代码
}

这种方法对于小范围情况还可以,但是对于大量类型处理就会有点力不从心了

  • 终极方案rest解构
    这里我们的本质需求是从一个对象中获取关联的两个属性,且确保关联不丢失。这个需求换种思路,就是从指定对象中去除一些属性,返回剩余的。这个正好就是rest解构的场景。

所以就是如下的代码

const makeBarFromFoo = (foo: AnyFoo): AnyBar => {
  const { info, ...rest } = foo;
  return rest
}

image.png
ts识别也正常,没有报错


参考链接:

相关文章
|
2月前
|
JavaScript 前端开发 编译器
TypeScript【泛型1、泛型2、声明合并、命名空间 、模块1、模块2、声明文件简介】(五)-全面详解(学习总结---从入门到深化)
TypeScript【泛型1、泛型2、声明合并、命名空间 、模块1、模块2、声明文件简介】(五)-全面详解(学习总结---从入门到深化)
85 0
|
2月前
|
JavaScript 编译器
TypeScript中泛型在函数和类中的应用
【4月更文挑战第23天】TypeScript的泛型在函数和类中提供了灵活性,允许处理多种数据类型。了解泛型是掌握TypeScript类型系统的关键。
|
2月前
|
JavaScript
react+typescript通过window.xxx挂载属性报错的解决方案
react+typescript通过window.xxx挂载属性报错的解决方案
85 0
|
25天前
|
JavaScript
TypeScript 泛型类型
TypeScript 泛型类型
|
25天前
|
JavaScript Java 编译器
TypeScript 泛型
TypeScript 泛型
|
27天前
|
JavaScript 算法 开发者
16.【TypeScript 教程】TypeScript 泛型(Generic)
16.【TypeScript 教程】TypeScript 泛型(Generic)
20 2
|
5天前
|
JavaScript 安全
TypeScript类型(数字、字符串、字面量、数组、元组、枚举、对象等)
TypeScript类型(数字、字符串、字面量、数组、元组、枚举、对象等)
6 0
|
13天前
|
JavaScript 安全 编译器
TypeScript 基础学习笔记:泛型 <T> vs 断言 as
TypeScript 基础学习笔记:泛型 <T> vs 断言 as
21 0
|
25天前
|
JavaScript 编译器
typescript 泛型约束
typescript 泛型约束
|
25天前
|
JavaScript 前端开发 C++
typescript 只读属性
typescript 只读属性