巧用TypeScript
程序员文章站
2022-03-27 20:43:58
...
????巧用注释
/** A cool guy. */
interface Person {
/** A cool name. */
name: string,
}
通过/** */
形式的注释可以给 TS 类型做标记,编辑器会有更好的提示:
注:Don't do this:
????巧用注释 进阶
注释有很多规范的字段,基本和 JSDOC 一致。但不用着急翻文档,在 /** */
里输入 @
就可以看到丰富的选择:
????巧用 typeof
我们一般先写类型,再使用:
interface Opt {
timeout: number
}
const defaultOption: Opt = {
timeout: 500
}
有时候可以反过来:
const defaultOption = {
timeout: 500
}
type Opt = typeof defaultOption
当一个 interface 总有一个字面量初始值时,可以考虑这种写法以减少重复代码。
????巧用联合类型
Dinner
要么有 fish
要么有 bear
。
// ???? Not good.
interface Dinner1 {
fish?: number,
bear?: number,
}
// ???? Awesome!
type Dinner2 = {
fish: number,
} | {
bear: number,
}
一些区别:
let d1: Dinner1 = {} // Opps
d1 = {fish:1, bear:1} // Opps
let d2: Dinner2 = {} // Protected!
d2 = {fish:1, bear:1} // Protected!
if ('fish' in d2) {
// `d2` has `fish` and no `bear` here.
} else {
// `d2` has `bear` and no `fish` here.
}
????巧用查找类型
interface Person {
addr: {
city: string,
street: string,
num: number,
}
}
当需要使用 addr
的类型时,除了把类型提出来
interface Address {
city: string,
street: string,
num: number,
}
interface Person {
addr: Address,
}
还可以
Person["addr"] // This is Address.
有些场合后者会让代码更整洁、易读。
????巧用查找类型+泛型+keyof
interface API {
'/user': { name: string },
'/menu': { foods: Food[] },
}
const get = <URL extends keyof API>(url: URL): Promise<API[URL]> => {
return fetch(url).then(res => res.json())
}
上面的定义极大地增强了代码提示:
????巧用显式泛型
$('button')
是个 DOM 元素选择器,可是返回值的类型是运行时才能确定的,除了返回 any
,还可以
function $<T extends HTMLElement>(id: string):T {
return document.getElementById(id)
}
// Tell me what element it is.
$<HTMLInputElement>('input').value
函数泛型不一定非得自动推导出类型,有时候显式指定类型就好。
????巧用 DeepReadonly
type DeepReadonly<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
}
const a = { foo: { bar: 22 } }
const b = a as DeepReadonly<typeof a>
b.foo.bar = 33 // Hey, stop!
????巧用 Omit
import { Button, ButtonProps } from './components/button'
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
type BigButtonProps = Omit<ButtonProps, 'size'>
function BigButton(props: BigButtonProps) {
return Button({ ...props, size: 'big' })
}
高阶组件比较常用。
业务中,我们经常会写枚举和对应的映射:
type AnimalType = 'cat' | 'dog' | 'frog';
const AnimalMap = {
cat: { name: '猫', icon: '????'},
dog: { name: '狗', icon: '????' },
forg: { name: '蛙', icon: '????' },
};
注意到上面 forg 拼错了吗?
Record
可以保证映射完整:
type AnimalType = 'cat' | 'dog' | 'frog';
interface AnimalDescription { name: string, icon: string }
const AnimalMap: Record<AnimalType, AnimalDescription> = {
cat: { name: '猫', icon: '????'},
dog: { name: '狗', icon: '????' },
forg: { name: '蛙', icon: '????' }, // Hey!
};
还能有代码提示
如果你喜欢用 enum
,写法也一样的
enum AnimalType {
CAT = 'cat',
DOG = 'dog',
FROG = 'frog',
}
const AnimalMap: Record<AnimalType, AnimalDescription>
????巧用Partial
就是部分的意思,很常用:
const mergeOptions = (options: Opt, patch: Partial<Opt>) {
return { ...options, ...patch };
}
class MyComponent extends React.PureComponent<Props> {
defaultProps: Partial<Props> = {};
}
????巧用tsx+extends
在 .tsx
文件里,泛型可能会被当做 jsx 标签
const toArray = <T>(element: T) => [element]; // Error in .tsx file.
加 extends
可破
const toArray = <T extends {}>(element: T) => [element]; // No errors.
????巧用ClassOf
有时候,我们要传入类本身,而不是类的实例
abstract class Animal extends React.PureComponent {
/* Common methods here. */
}
class Cat extends Animal {}
class Dog extends Animal {}
// `AnimalComponent` must be a class of Animal.
const renderAnimal = (AnimalComponent: Animal) => {
return <AnimalComponent/>; // WRONG!
}
上面的代码是错的,因为 Animal
是实例类型,不是类本身。应该
interface ClassOf<T> {
new (...args: any[]): T;
}
const renderAnimal = (AnimalComponent: ClassOf<Animal>) => {
return <AnimalComponent/>; // Good!
}
renderAnimal(Cat); // Good!
renderAnimal(Dog); // Good!
????巧用类型查找+类方法
我们通常会在 React 组件中把方法传下去
class Parent extends React.PureComponent {
private updateHeader = (title: string, subTitle: string) => {
// Do it.
};
render() {
return <Child updateHeader={this.updateHeader}/>;
}
}
interface ChildProps {
updateHeader: (title: string, subTitle: string) => void;
}
class Child extends React.PureComponent<ChildProps> {
private onClick = () => {
this.props.updateHeader('Hello', 'Typescript');
};
render() {
return <button onClick={this.onClick}>Go</button>;
}
}
其实可以在 ChildProps
中直接引用类的方法
interface ChildProps {
updateHeader: Parent['updateHeader'];
}
两个好处:不用重复写方法签名,能从方法调用跳到方法定义 。
传统的跳转:
传统的跳转
神奇的跳转:
神奇的跳转
尤其在多层传递的场合,从孙子的一个方法调用,跳到爷爷的方法定义,爽。