likes
comments
collection
share

Typescript类型常用使用技巧1

作者站长头像
站长
· 阅读数 28

枚举类型怎么定义?

有这么一个常量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 const

type 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...//
}

<div onClick={onClick}><div>

这里很多会定义为e:any 然后冒泡的函数是啥,阻止冒泡stop什么什么,再去查一下。

其实想一想,如果能知道<div>的props的类型定义,我可以直接定义onClick这个函数,从而e就有类型了,不仅可以检查代码,还可以得到友好的提示。JSX提供了这样一个查询组件props的泛型:

// 获得div标签的props的类型 (内置组件,例如 div、a、p、input等等)

const DivPropsType = JSX.IntrinsicElements<'div'>

// 获得自定义组件的props

const CustomPropsType = JSXElementConstructor<CustomComponent>

为了减少记忆的负担,React对这2个泛型又进行了一步包装:

type ComponentProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> =

        T extends JSXElementConstructor<infer P>

            ? P

            : T extends keyof JSX.IntrinsicElements

                ? JSX.IntrinsicElements[T]

                : {};

所以上面的事件类型定义就变得非常简单了:

import { ComponentProps } from 'react'

const onClick: ComponentProps<'div'>['onClick'] = (e)=>{// e的类型被自动推导

    e.//会得到代码提示

}

<div onClick={onClick}><div>

同样,我们在引入自定义组件时,也不需要单独引入它的props类型,直接使用这个泛型即可:

import { type ComponentProps } from 'react'

const onClick: ComponentProps<typeof Custom>['onClick'] = (e)=>{// e的类型被自动推导

    e.//会得到代码提示

}

<Custom onClick={onClick}></Custom>

怎样定义一个字符串类型,既有枚举,又可以自由输入?

这种应用场景非常常见,我这样写标题,可能表达得不清晰,举一个例子:

我需要定义一个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<number>(0)

这种方式定义的valueRef的类型是MutableRefObject 可变的引用对象

除了这种方式,还有一种不可变的,对应的类型是RefObject 只读的引用对象 感觉这俩就是const和let一样

看一下区别

import { useRef, type MutableRefObject, type RefObject } form 'react'



const valueRef1: MutableRefObject<number> = useRef(0)

const valueRef2: RefObject<number> = useRef(0)



valueRef1.current = 1; // 正常

valueRef2.current = 1; // 报错,不能赋值: 无法分配到 "current" ,因为它是只读属性。

所以我们在定义几种场景时,应区分是手动赋值还是自动赋值,并使用不同的类型

例如用来封装一个useUUID的hook

import { useRef, type RefObject } form 'react'



// 定义只读的ref

export const useUUID = ()=>{

    const uuidRef: RefObject<string> = useRef('uuid' + Date.now().toString(16));

    

    return uuidRef.current

}

例如引用一个div的ref

import { useRef, type RefObject } form 'react'



const divRef: RefObject<HTMLDivElement> = useRef();



<div ref={divRef}></div>

forwardRef的类型该怎样定义以及引用时类型该怎样定义?

根据官方的推荐,在定义forwardRef时,将类型定义在高阶函数中(注意⚠️props的类型和ref类型位置相反)

const ComA = forwardRef<{dddd: string}, {age?: number}>((props, ref)=>{
  useImperativeHandle(ref, ()=>{
    return {
      dddd: "1"
    }
  })
  return <div></div>
})

在引入时,typeof ComA 得到的是一个ref和props的交叉类型,所以只需访问出ref的类型即可

const ComB = ()=>{
  const tRef: ComponentProps<typeof ComA>['ref'] = useRef();
  
  return <ComA ref={tRef}></ComA>
}

React在此处设计的类型是ComponentProps<typeof ComA>['ref'] 返回的是一个React.Ref泛型

type Ref<T> = RefCallback<T> | RefObject<T> | null;

正好兼容了ref的3种情况,使用函数接收(createRef),useRef引用,和初始空值。而且还是个只读的ref 👍

然后访问tRef时,此时tRef即是已经收窄的类型,具有友好的提示和取值限制。

Typescript类型常用使用技巧1

写在最后的话

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

Typescript类型常用使用技巧1