Typescript类型常用使用技巧1

枚举类型怎么定义?
有这么一个常量FieldTypes,代表表单项类型,是输入框还是开关还是其他等等,只列举两个

enum FieldTypes { INPUT = 'input', SWITCH = 'switch' // ... }

此时子组件需要接收一个typeprops,类型的值为FieldTypes中的定义的值,即'input' | 'switch'等等
解法一:
typescript中enum可以作为类型约束某个变量,如果这么做,那么给这个propstype赋值时,必须从enum中去取,保证了数据的来源以及数据类型的统一性,缺点是在某些场景下不是特别灵活
const _type: FieldTypes = FieldTypes.INPUT // ? correct const _type: FieldTypes = 'input' // ? error

解法二:
使用只读的 object 来替代enum的作用
const FieldTypesObj = { INPUT: 'input', SWITCH: 'switch' } as const // <-- 关键技巧 1: as consttype Type = typeof FieldTypesObj // <-- 关键技巧 2: 用 typeof 关键字从 FieldTypesObj 反向创建同名类型const _type1: Type[keyof Type] = 'input' // ? correct const _type2: Type[keyof Type] = FieldTypes.SWITCH // ? correct

常量断言(语法写作 as const)是 TypeScript 3.4发布的新特性,这里对它进行简单的解释:
先看下面例子:
let str = 'ghostwang' // str: string const str = 'ghostwang' // str: 'ghostwang' let str = 'ghostwang' as const // str: 'ghostwang'const obj1 = { name: 'ghostwang' } // const obj: { name: string; } const obj2 = { name: 'ghostwang' } as const // const obj: { readonly name: "ghostwang"; }const arr = [1, 2] // const arr: number[] const arr2 = [1, 2] as const // const arr: readonly [1, 2]

看出区别来了么,使用as const会告诉编译器为表达式推断出它能推断出的最窄或最特定的类型。如果不使用它,编译器将使用其默认类型推断行为,这可能会导致更广泛或更一般的类型,并且此时他的属性是只读的(当然还是有办法能修改)
事件类型该怎么定义?
相信有人在定义事件的时候有时候不知道怎么去定义,例如下面的场景,div点击时阻止冒泡
const onClick = (e)=>{// 这里e的类型是什么? e...// }

这里很多会定义为e:any 然后冒泡的函数是啥,阻止冒泡stop什么什么,再去查一下。
【Typescript类型常用使用技巧1】其实想一想,如果能知道的props的类型定义,我可以直接定义onClick这个函数,从而e就有类型了,不仅可以检查代码,还可以得到友好的提示。JSX提供了这样一个查询组件props的泛型:
// 获得div标签的props的类型 (内置组件,例如 div、a、p、input等等)const DivPropsType = JSX.IntrinsicElements<'div'>// 获得自定义组件的propsconst CustomPropsType = JSXElementConstructor

为了减少记忆的负担,React对这2个泛型又进行了一步包装:
type ComponentProps =T extends JSXElementConstructor? P: T extends keyof JSX.IntrinsicElements? JSX.IntrinsicElements[T]: {};

所以上面的事件类型定义就变得非常简单了:
import { ComponentProps } from 'react'const onClick: ComponentProps<'div'>['onClick'] = (e)=>{// e的类型被自动推导e.//会得到代码提示}

同样,我们在引入自定义组件时,也不需要单独引入它的props类型,直接使用这个泛型即可:
import { type ComponentProps } from 'react'const onClick: ComponentProps['onClick'] = (e)=>{// e的类型被自动推导e.//会得到代码提示}

怎样定义一个字符串类型,既有枚举,又可以自由输入?
这种应用场景非常常见,我这样写标题,可能表达的不清晰,举一个例子:
我需要定义一个color类型,在开发者输入时,可以提示输入 "red" 和 "blue",但是除了red和blue也可以自由输入其他字符串
Typescript类型常用使用技巧1
文章图片

但是这样定义类型的话,除了red和blue,其他都输入不了。都会报错。
如果加上string,直接什么都不提示了
Typescript类型常用使用技巧1
文章图片

所以需要定义一个既包含red和blue,又包含除了red和blue之外的字符串
Typescript类型常用使用技巧1
文章图片

我输入了white,也不会报错
Typescript类型常用使用技巧1
文章图片

ref的类型该怎么定义?
ref的应用场景常用来储存一些改变不会引起重新渲染、用来引用forwardRef的组件、引用内置组件使用。
import { useRef } form 'react'const valueRef = useRef(0)

这种方式定义的valueRef的类型是MutableRefObject 可变的引用对象
除了这种方式,还有一种不可变的,对应的类型是RefObject 只读的引用对象 感觉这俩就是const和let一样
看一下区别
import { useRef, type MutableRefObject, type RefObject } form 'react'const valueRef1: MutableRefObject = useRef(0)const valueRef2: RefObject = useRef(0)valueRef1.current = 1; // 正常valueRef2.current = 1; // 报错,不能赋值: 无法分配到 "current" ,因为它是只读属性。

所以我们在定义几种场景时,应区分是手动赋值还是自动赋值,并使用不同的类型
例如用来封装一个useUUID的hook
import { useRef, type RefObject } form 'react'// 定义只读的refexport const useUUID = ()=>{const uuidRef: RefObject = useRef('uuid' + Date.now().toString(16)); return uuidRef.current}

例如引用一个div的ref
import { useRef, type RefObject } form 'react'const divRef: RefObject = useRef();

forwardRef的类型该怎样定义以及引用时类型该怎样定义?
根据官方的推荐,在定义forwardRef时,将类型定义在高阶函数中(注意??props的类型和ref类型位置相反)
const ComA = forwardRef<{dddd: string}, {age?: number}>((props, ref)=>{ useImperativeHandle(ref, ()=>{ return { dddd: "1" } }) return })

在引入时,typeof ComA 得到的是一个ref和props的交叉类型,所以只需访问出ref的类型即可
const ComB = ()=>{ const tRef: ComponentProps['ref'] = useRef(); return }

React在此处设计的类型是ComponentProps['ref'] 返回的是一个React.Ref泛型
type Ref = RefCallback | RefObject | null;

正好兼容了ref的3种情况,使用函数接收(createRef),useRef引用,和初始空值。而且还是个只读的ref
然后访问tRef时,此时tRef即是已经收窄的类型,具有友好的提示和取值限制。
Typescript类型常用使用技巧1
文章图片

写在最后的话
至此,结合最近组内小伙伴分享的一些ts类型使用技巧,在此总结并分享给更多的人,感谢阅读~

    推荐阅读