Drew's Workbench
由一道ts的类型题想到的
15.02.20194 Min Read — In Code

本来是在网上闲逛看看现在有什么适配React Hooks的库, 发现有一个rxjs-hooks还行. 竟然是LeetCode开源的, 于是就点进去看看. 发现了一个hire的repo.

3, 4做得多了没什么新鲜感, 只能感叹下现在前端招聘确实越来越注重工程化和实用性了, 没想到一个OG网站的前端题竟然没有一个是算法的. 2确实很棒, 反手就把自己项目给改了, 虽然现有的部署方式并不需要这种优化.

题目1确实是挺头疼的一个实际问题. 本来以为在实际工作中用用扁平的基本类型就够用了, 但正如之前写Scala一样, 一入类型深似海啊, 各种头疼的TS Error. 工作中想写点骚类型看见什么keyof, Exclude, Extract之类就头大, 每次都疯狂搜半天. 做这个题时候正好顺便屡屡清楚.

这道题的描述挺多挺复杂的, 但其实要求精简一下就是: 写出一个改变某个interface的某些属性的类型, 继承其他的属性的类型.

改来改去后我的最终答案是:

// keys of non-function props: count, message...
type NonFuncKeys = Exclude<keyof EffectModule, keyof Connected>

// extract non-function types from original interface
type NonFuncProps = {
  [key in NonFuncKeys]: EffectModule[key]
}

// combine
type Result = Partial<NonFuncProps> & Connected

类型这种东西在js世界里很难说是严谨的, 所以满足要求的答案应该是不止一个, 并且还跟编译器版本有关. 所以关于答案就不深究了. 倒是几种关键字有必要理清除一下.


in

in关键字是用来生成Mapped types的. 作用类似js里的for ... in, 只不过针对的是属性key值的Index types, 实际上是所有公开属性名的Union type.

in把一个类型的所有属性map成any的例子:

interface Person {
  name: string
  age: number
}

type ToAny<T> = {
  [P in keyof T]: any
}
// or...
type ToAny2<T extends string | number | symbol> = {
  [P in T]: any
}

// map name/age to any
type Result = ToAny<Person>
// or
type Result = ToAny2<keyof Person>

keyof

keyof就是👆提到的index typequery operator, 类型经过keyof操作后就得到所有公开属性名的Union type

type PersonKeys = keyof Person // 'name' | 'age'

T[K]

这个方括号叫indexed access operator. 这个很好理解了, 经这个操作可以获取类型T的属性key为K的类型

type Age = Person['age'] // number

Nullable, NonNullable, Partial, Readonly, Required

挺常用的工具类型, 字面意思, 没啥难度. 但是Required的定义就比较骚了:

type Required<T> = {
    [P in keyof T]-?: T[P]; // -? 😲
};

Pick

这个就是看起来有点屌的类型的, 但其实定义并不复杂, 用法也很简单

type Droid = Pick<Person, 'name'>
type ClonePerson = Pick<Person, 'name' | 'age'>

还有一个Record, 刚好像Pick反过来, 生成一个所有属性都是目标类型的新类型. 不知道有啥用不写了.


Exclude, Extract

type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;

这两个最让人头大了, 之前用的时候经常得到一个never. 不过在搞懂了keyof之后就没什么问题了. 看定义就知道这两个类型的参数必须T extends U, 否则就会直接丢给你个never. 这也就减弱了这两个方法的可用性, 在两个类型没有继承关系的时候必须要经过keyof转为indexed typePick操作才能达到字面上的效果. 这也是为啥很多库里面类型声明文件中起手就是一个Omit或者Substract.


T extends U ? A: B

v2.8引入的语法. 感觉会挺实用. 但是条件语句表达方式有限, 期待扩展.


BONUS💰 ThisType<T>

这个是自己点进lib.d.ts里看到的, 文档里很难找到(但还是有). 刚看到觉得这个可以解决Vue中的很多问题, 因为Vue项目是this的重度用户. 一搜果然早在v2.5就已经用上了.


还有一些工具类型和关键字, 先不写了. 之前还有使用了@salesforce/ts-types, 还没来得及仔细看. 就都留到下一篇blog吧.