TypeScript

如何引入 React

  1. import * as React from 'react'
  2. import * as ReactDOM from 'react-dom'

这种引用方式被证明是最可靠的一种方式, 推荐使用。
而另外一种引用方式:

  1. import React from 'react'
  2. import ReactDOM from 'react-dom'

需要添加额外的配置:"allowSyntheticDefaultImports": true

函数式组件的声明方式

声明的几种方式
第一种:也是比较推荐的一种,使用 React.FunctionComponent,简写形式:React.FC:

  1. // Great
  2. type AppProps = {
  3. message: string
  4. }
  5. const App: React.FC<AppProps> = ({ message, children }) => (
  6. <div>
  7. {message}
  8. {children}
  9. </div>
  10. )

使用 React.FC 声明函数组件和普通声明以及 PropsWithChildren 的区别是:

  • React.FC 显式地定义了返回类型,其他方式是隐式推导的
  • React.FC 对静态属性:displayNamepropTypesdefaultProps 提供了类型检查和自动补全
  • React.FC 为 children 提供了隐式的类型(ReactElement | null),但是目前,提供的类型存在一些 issue(问题)

比如以下用法 React.FC 会报类型错误:

  1. const App: React.FC = props => props.children
  2. const App: React.FC = () => [1, 2, 3]
  3. const App: React.FC = () => 'hello'

解决方法:

  1. const App: React.FC<{}> = props => props.children as any
  2. const App: React.FC<{}> = () => [1, 2, 3] as any
  3. const App: React.FC<{}> = () => 'hello' as any
  4. // 或者
  5. const App: React.FC<{}> = props => (props.children as unknown) as JSX.Element
  6. const App: React.FC<{}> = () => ([1, 2, 3] as unknown) as JSX.Element
  7. const App: React.FC<{}> = () => ('hello' as unknown) as JSX.Element

在通常情况下,使用 React.FC 的方式声明最简单有效,推荐使用;如果出现类型不兼容问题,建议使用以下两种方式:
第二种:使用 PropsWithChildren,这种方式可以省去频繁定义 children 的类型,自动设置 children 类型为 ReactNode:

  1. type AppProps = React.PropsWithChildren<{ message: string }>
  2. const App = ({ message, children }: AppProps) => (
  3. <div>
  4. {message}
  5. {children}
  6. </div>
  7. )

第三种:直接声明:

  1. type AppProps = {
  2. message: string
  3. children?: React.ReactNode
  4. }
  5. const App = ({ message, children }: AppProps) => (
  6. <div>
  7. {message}
  8. {children}
  9. </div>
  10. )

Hooks

useState<T>

大部分情况下,TS 会自动推导 state 的类型:

  1. // `val`会推导为boolean类型, toggle接收boolean类型参数
  2. const [val, toggle] = React.useState(false)
  3. // obj会自动推导为类型: {name: string}
  4. const [obj] = React.useState({ name: 'sj' })
  5. // arr会自动推导为类型: string[]
  6. const [arr] = React.useState(['One', 'Two'])

使用推导类型作为接口/类型:

  1. export default function App() {
  2. // user会自动推导为类型: {name: string}
  3. const [user] = React.useState({ name: 'sj', age: 32 })
  4. const showUser = React.useCallback((obj: typeof user) => {
  5. return `My name is ${obj.name}, My age is ${obj.age}`
  6. }, []) return <div className="App">用户: {showUser(user)}</div>
  7. }

但是,一些状态初始值为空时(null),需要显示地声明类型:

  1. type User = {
  2. name: string
  3. age: number
  4. }const [user, setUser] = React.useState<User | null>(null)

useRef<T>

当初始值为 null 时,有两种创建方式:

  1. const ref1 = React.useRef<HTMLInputElement>(null)
  2. const ref2 = React.useRef<HTMLInputElement | null>(null)

这两种的区别在于:

  • 第一种方式的 ref1.current 是只读的(read-only),并且可以传递给内置的 ref 属性,绑定 DOM 元素 ;
  • 第二种方式的 ref2.current 是可变的(类似于声明类的成员变量)

    1. const ref = React.useRef(0)
    2. React.useEffect(() => {
    3. ref.current += 1
    4. }, [])

    这两种方式在使用时,都需要对类型进行检查:

    1. const onButtonClick = () => {
    2. ref1.current?.focus()
    3. ref2.current?.focus()
    4. }

    在某种情况下,可以省去类型检查,通过添加 ! 断言,不推荐:

    1. // Bad
    2. function MyComponent() {
    3. const ref1 = React.useRef<HTMLDivElement>(null!)
    4. React.useEffect(() => {
    5. // 不需要做类型检查,需要人为保证ref1.current.focus一定存在
    6. doSomethingWith(ref1.current.focus())
    7. })
    8. return <div ref={ref1}> etc </div>
    9. }

    useEffect

    useEffect 需要注意回调函数的返回值只能是函数或者 undefined

    1. function App() {
    2. // undefined作为回调函数的返回值
    3. React.useEffect(() => {
    4. // do something...
    5. }, [])
    6. // 返回值是一个函数
    7. React.useEffect(() => {
    8. // do something...
    9. return () => {}
    10. }, [])
    11. }

    useMemo<T> / useCallback<T>

    useMemouseCallback 都可以直接从它们返回的值中推断出它们的类型
    useCallback 的参数必须制定类型,否则 ts 不会报错,默认指定为 any

    1. const value = 10
    2. // 自动推断返回值为 number
    3. const result = React.useMemo(() => value * 2, [value])
    4. // 自动推断 (value: number) => number
    5. const multiply = React.useCallback((value: number) => value * multiplier, [
    6. multiplier,
    7. ])

    同时也支持传入泛型, useMemo 的泛型指定了返回值类型,useCallback 的泛型指定了参数类型

    1. // 也可以显式的指定返回值类型,返回值不一致会报错
    2. const result = React.useMemo<string>(() => 2, [])
    3. // 类型“() => number”的参数不能赋给类型“() => string”的参数。
    4. const handleChange = React.useCallback<
    5. React.ChangeEventHandler<HTMLInputElement>
    6. >(evt => {
    7. console.log(evt.target.value)
    8. }, [])

    自定义 Hooks

    需要注意,自定义 Hook 的返回值如果是数组类型,TS 会自动推导为 Union 类型,而实际需要的是数组里里每一项的具体类型,需要手动添加 const 断言 进行处理:

    1. function useLoading() {
    2. const [isLoading, setState] = React.useState(false)
    3. const load = (aPromise: Promise<any>) => {
    4. setState(true)
    5. return aPromise.then(() => setState(false))
    6. }
    7. // 实际需要: [boolean, typeof load] 类型
    8. // 而不是自动推导的:(boolean | typeof load)[]
    9. return [isLoading, load] as const
    10. }

    如果使用 const 断言遇到问题,也可以直接定义返回类型:

    1. export function useLoading(): [
    2. boolean,
    3. (aPromise: Promise<any>) => Promise<any>
    4. ] {
    5. const [isLoading, setState] = React.useState(false)
    6. const load = (aPromise: Promise<any>) => {
    7. setState(true)
    8. return aPromise.then(() => setState(false))
    9. }
    10. return [isLoading, load]
    11. }

    如果有大量的自定义 Hook 需要处理,这里有一个方便的工具方法可以处理 tuple 返回值: ```typescript function tuplify(…elements: T) { return elements } function useLoading() { const [isLoading, setState] = React.useState(false) const load = (aPromise: Promise) => { setState(true) return aPromise.then(() => setState(false)) }

    // (boolean | typeof load)[] return [isLoading, load] }

function useTupleLoading() { const [isLoading, setState] = React.useState(false) const load = (aPromise: Promise) => { setState(true) return aPromise.then(() => setState(false)) }

// [boolean, typeof load] return tuplify(isLoading, load) }

  1. <a name="hQO1f"></a>
  2. ## 默认属性 `defaultProps`
  3. 大部分文章都不推荐使用 `defaultProps` , 相关讨论可以点击[参考链接](https://twitter.com/hswolff/status/1133759319571345408)<br />推荐方式:使用默认参数值来代替默认属性:
  4. ```typescript
  5. type GreetProps = { age?: number }
  6. const Greet = ({ age = 21 }: GreetProps) => {
  7. /* ... */
  8. }

defaultProps 类型

TypeScript3.0+ 在默认属性 的类型推导上有了极大的改进,虽然尚且存在一些边界 case 仍然存在问题,不推荐使用,如果有需要使用的场景,可参照如下方式:

  1. type IProps = {
  2. name: string
  3. }
  4. const defaultProps = {
  5. age: 25,
  6. }
  7. // 类型定义
  8. type GreetProps = IProps & typeof defaultProps
  9. const Greet = (props: GreetProps) => <div></div>
  10. Greet.defaultProps = defaultProps
  11. // 使用
  12. const TestComponent = (props: React.ComponentProps<typeof Greet>) => {
  13. return <h1 />
  14. }
  15. const el = <TestComponent name="foo" />

Types or Interfaces

在日常的 react 开发中 interfacetype 的使用场景十分类似
implementsextends 静态操作,不允许存在一种或另一种实现的情况,所以不支持使用联合类型:

  1. class Point {
  2. x: number = 2
  3. y: number = 3
  4. }
  5. interface IShape {
  6. area(): number
  7. }
  8. type Perimeter = {
  9. perimeter(): number
  10. }
  11. type RectangleShape = (IShape | Perimeter) & Point
  12. class Rectangle implements RectangleShape {
  13. // 类只能实现具有静态已知成员的对象类型或对象类型的交集。
  14. x = 2
  15. y = 3
  16. area() {
  17. return this.x + this.y
  18. }
  19. }
  20. interface ShapeOrPerimeter extends RectangleShape {}
  21. // 接口只能扩展使用静态已知成员的对象类型或对象类型的交集

使用 Type 还是 Interface?

有几种常用规则:

  • 在定义公共 API 时(比如编辑一个库)使用 interface,这样可以方便使用者继承接口
  • 在定义组件属性(Props)和状态(State)时,建议使用 type,因为 type的约束性更强

interfacetype 在 ts 中是两个不同的概念,但在 React 大部分使用的 case 中,interfacetype 可以达到相同的功能效果,typeinterface 最大的区别是:

  • type 类型不能二次编辑,而 interface 可以随时扩展 ```typescript interface Animal { name: string }

// 可以继续在原有属性基础上,添加新属性:color interface Animal { color: string } /**/ type Animal = { name: string } // type类型不支持属性扩展 // Error: Duplicate identifier ‘Animal’ type Animal = { color: string }

  1. <a name="UqjGy"></a>
  2. ### 获取未导出的 Type
  3. 某些场景下在引入第三方的库时会发现想要使用的组件并没有导出需要的组件参数类型或者返回值类型,这时候可以通过 `ComponentProps`/ `ReturnType` 来获取到想要的类型。
  4. ```jsx
  5. // 获取参数类型
  6. import { Button } from 'library' // 但是未导出props type
  7. type ButtonProps = React.ComponentProps<typeof Button> // 获取props
  8. type AlertButtonProps = Omit<ButtonProps, 'onClick'> // 去除onClick
  9. const AlertButton: React.FC<AlertButtonProps> = props => (
  10. <Button onClick={() => alert('hello')} {...props} />
  11. )
  12. // 获取返回值类型
  13. function foo() {
  14. return { baz: 1 }
  15. }
  16. type FooReturn = ReturnType<typeof foo> // { baz: number }

Props

通常使用 type 来定义 Props,为了提高可维护性和代码可读性,在日常的开发过程中希望可以添加清晰的注释。
现在有这样一个 type

  1. type OtherProps = {
  2. name: string
  3. color: string
  4. }

在使用的过程中,hover 对应类型会有如下展示

  1. // type OtherProps = {
  2. // name: string;
  3. // color: string;
  4. // }
  5. const OtherHeading: React.FC<OtherProps> = ({ name, color }) => (
  6. <h1>My Website Heading</h1>
  7. )

增加相对详细的注释,使用时会更清晰,需要注意,注释需要使用 /**/// 无法被 vscode 识别

  1. // Great
  2. /**
  3. * @param color color
  4. * @param children children
  5. * @param onClick onClick
  6. */
  7. type Props = {
  8. /** color */
  9. color?: string
  10. /** children */
  11. children: React.ReactNode
  12. /** onClick */
  13. onClick: () => void
  14. }
  15. // type Props
  16. // @param color — color
  17. // @param children — children
  18. // @param onClick — onClick
  19. const Button: React.FC<Props> = ({ children, color = 'tomato', onClick }) => {
  20. return (
  21. <button style={{ backgroundColor: color }} onClick={onClick}>
  22. {children}
  23. </button>
  24. )
  25. }

常用 Props ts 类型

基础属性类型

  1. type AppProps = {
  2. message: string
  3. count: number
  4. disabled: boolean
  5. /** array of a type! */
  6. names: string[]
  7. /** string literals to specify exact string values, with a union type to join them together */
  8. status: 'waiting' | 'success'
  9. /** 任意需要使用其属性的对象(不推荐使用,但是作为占位很有用) */
  10. obj: object
  11. /** 作用和`object`几乎一样,和 `Object`完全一样 */
  12. obj2: {}
  13. /** 列出对象全部数量的属性 (推荐使用) */
  14. obj3: {
  15. id: string
  16. title: string
  17. }
  18. /** array of objects! (common) */
  19. objArr: {
  20. id: string
  21. title: string
  22. }[]
  23. /** 任意数量属性的字典,具有相同类型*/
  24. dict1: {
  25. [key: string]: MyTypeHere
  26. }
  27. /** 作用和dict1完全相同 */
  28. dict2: Record<string, MyTypeHere>
  29. /** 任意完全不会调用的函数 */
  30. onSomething: Function
  31. /** 没有参数&返回值的函数 */
  32. onClick: () => void
  33. /** 携带参数的函数 */
  34. onChange: (id: number) => void
  35. /** 携带点击事件的函数 */
  36. onClick(event: React.MouseEvent<HTMLButtonElement>): void
  37. /** 可选的属性 */
  38. optional?: OptionalType
  39. }

常用 React 属性类型

  1. export declare interface AppBetterProps {
  2. children: React.ReactNode // 一般情况下推荐使用,支持所有类型 Great
  3. functionChildren: (name: string) => React.ReactNode
  4. style?: React.CSSProperties // 传递style对象
  5. onChange?: React.FormEventHandler<HTMLInputElement>
  6. }
  7. export declare interface AppProps {
  8. children1: JSX.Element // 差, 不支持数组
  9. children2: JSX.Element | JSX.Element[] // 一般, 不支持字符串
  10. children3: React.ReactChildren // 忽略命名,不是一个合适的类型,工具类类型
  11. children4: React.ReactChild[] // 很好
  12. children: React.ReactNode // 最佳,支持所有类型 推荐使用
  13. functionChildren: (name: string) => React.ReactNode // recommended function as a child render prop type
  14. style?: React.CSSProperties // 传递style对象
  15. onChange?: React.FormEventHandler<HTMLInputElement> // 表单事件, 泛型参数是event.target的类型
  16. }

Forms and Events

onChange

change 事件,有两个定义参数类型的方法。
第一种方法使用推断的方法签名(例如:React.FormEvent <HTMLInputElement> :void

  1. import * as React from 'react'
  2. type changeFn = (e: React.FormEvent<HTMLInputElement>) => void
  3. const App: React.FC = () => {
  4. const [state, setState] = React.useState('')
  5. const onChange: changeFn = e => {
  6. setState(e.currentTarget.value)
  7. }
  8. return (
  9. <div>
  10. <input type="text" value={state} onChange={onChange} />
  11. </div>
  12. )
  13. }

第二种方法强制使用 @types / react 提供的委托类型,两种方法均可。

  1. import * as React from 'react'const App: React.FC = () => {
  2. const [state, setState] = React.useState('')
  3. const onChange: React.ChangeEventHandler<HTMLInputElement> = e => {
  4. setState(e.currentTarget.value)
  5. }
  6. return (
  7. <div>
  8. <input type="text" value={state} onChange={onChange} />
  9. </div>
  10. )
  11. }

onSubmit

如果不太关心事件的类型,可以直接使用 React.SyntheticEvent,如果目标表单有想要访问的自定义命名输入,可以使用类型扩展

  1. import * as React from 'react'
  2. const App: React.FC = () => {
  3. const onSubmit = (e: React.SyntheticEvent) => {
  4. e.preventDefault()
  5. const target = e.target as typeof e.target & {
  6. password: { value: string }
  7. } // 类型扩展
  8. const password = target.password.value
  9. }
  10. return (
  11. <form onSubmit={onSubmit}>
  12. <div>
  13. <label>
  14. Password:
  15. <input type="password" name="password" />
  16. </label>
  17. </div>
  18. <div>
  19. <input type="submit" value="Log in" />
  20. </div>
  21. </form>
  22. )
  23. }

Operators

常用的操作符,常用于类型判断

  • typeof and instanceof: 用于类型区分
  • keyof: 获取 object 的 key
  • O[K]: 属性查找
  • [K in O]: 映射类型
    • or - or readonly or ?: 加法、减法、只读和可选修饰符
  • x ? Y : Z: 用于泛型类型、类型别名、函数参数类型的条件类型
  • !: 可空类型的空断言
  • as: 类型断言
  • is: 函数返回类型的类型保护

    Tips

    使用查找类型访问组件属性类型

    通过查找类型减少 type 的非必要导出,如果需要提供复杂的 type,应当提取到作为公共 API 导出的文件中。
    现在有一个 Counter 组件,需要 name 这个必传参数:

    1. // counter.tsx
    2. import * as React from 'react'
    3. export type Props = {
    4. name: string
    5. }
    6. const Counter: React.FC<Props> = props => {
    7. return <></>
    8. }
    9. export default Counter

    在其他引用它的组件中有两种方式获取到 Counter 的参数类型
    第一种是通过 typeof 操作符(推荐)

    1. // Great
    2. import Counter from './d-tips1'
    3. type PropsNew = React.ComponentProps<typeof Counter> & {
    4. age: number
    5. }
    6. const App: React.FC<PropsNew> = props => {
    7. return <Counter {...props} />
    8. }
    9. export default App

    第二种是通过在原组件进行导出

    1. import Counter, { Props } from './d-tips1'
    2. type PropsNew = Props & {
    3. age: number
    4. }
    5. const App: React.FC<PropsNew> = props => {
    6. return (
    7. <>
    8. <Counter {...props} />
    9. </>
    10. )
    11. }
    12. export default App

    不要在 type 或 interface 中使用函数声明

    保持一致性,类型/接口的所有成员都通过相同的语法定义。
    —strictFunctionTypes 在比较函数类型时强制执行更严格的类型检查,但第一种声明方式下严格检查不生效。

    1. interface ICounter {
    2. start: (value: number) => string
    3. }

    1. interface ICounter1 {
    2. start(value: number): string
    3. }

    🌰

    1. interface Animal {}
    2. interface Dog extends Animal {
    3. wow: () => void
    4. }
    5. interface Comparer<T> {
    6. compare: (a: T, b: T) => number
    7. }
    8. declare let animalComparer: Comparer<Animal>
    9. declare let dogComparer: Comparer<Dog>
    10. animalComparer = dogComparer // Error
    11. dogComparer = animalComparer // Ok
    12. interface Comparer1<T> {
    13. compare(a: T, b: T): number
    14. }
    15. declare let animalComparer1: Comparer1<Animal>
    16. declare let dogComparer1: Comparer1<Dog>
    17. animalComparer1 = dogComparer // Ok
    18. dogComparer1 = animalComparer // Ok

    事件处理

    在进行事件注册时经常会在事件处理函数中使用 event 事件对象,例如当使用鼠标事件时通过 clientX、clientY 去获取指针的坐标。
    大家可能会想到直接把 event 设置为 any 类型,但是这样就失去了对代码进行静态检查的意义。

    1. function handleEvent(event: any) {、
    2. console.log(event.clientY)
    3. }

    试想下当注册一个 Touch 事件,然后错误的通过事件处理函数中的 event 对象去获取其 clientY 属性的值,在这里已经将 event 设置为 any 类型,导致 TypeScript 在编译时并不会提示错误, 当通过 event.clientY 访问时就有问题了,因为 Touch 事件的 event 对象并没有 clientY 这个属性。
    通过 interface 对 event 对象进行类型声明编写的话又十分浪费时间,幸运的是 React 的声明文件提供了 Event 对象的类型声明。

    Event 事件对象类型

  • ClipboardEvent 剪切板事件对象

  • DragEvent 拖拽事件对象
  • ChangeEvent Change 事件对象
  • KeyboardEvent 键盘事件对象
  • MouseEvent 鼠标事件对象
  • TouchEvent 触摸事件对象
  • WheelEvent 滚轮时间对象
  • AnimationEvent 动画事件对象
  • TransitionEvent 过渡事件对象

    事件处理函数类型

    当定义事件处理函数时有没有更方便定义其函数类型的方式呢?答案是使用 React 声明文件所提供的 EventHandler 类型别名,通过不同事件的 EventHandler 的类型别名来定义事件处理函数的类型
    1. type EventHandler<E extends React.SyntheticEvent<any>> = {
    2. bivarianceHack(event: E): void
    3. }['bivarianceHack']
    4. type ReactEventHandler<T = Element> = EventHandler<React.SyntheticEvent<T>>
    5. type ClipboardEventHandler<T = Element> = EventHandler<React.ClipboardEvent<T>>
    6. type DragEventHandler<T = Element> = EventHandler<React.DragEvent<T>>
    7. type FocusEventHandler<T = Element> = EventHandler<React.FocusEvent<T>>
    8. type FormEventHandler<T = Element> = EventHandler<React.FormEvent<T>>
    9. type ChangeEventHandler<T = Element> = EventHandler<React.ChangeEvent<T>>
    10. type KeyboardEventHandler<T = Element> = EventHandler<React.KeyboardEvent<T>>
    11. type MouseEventHandler<T = Element> = EventHandler<React.MouseEvent<T>>
    12. type TouchEventHandler<T = Element> = EventHandler<React.TouchEvent<T>>
    13. type PointerEventHandler<T = Element> = EventHandler<React.PointerEvent<T>>
    14. type UIEventHandler<T = Element> = EventHandler<React.UIEvent<T>>
    15. type WheelEventHandler<T = Element> = EventHandler<React.WheelEvent<T>>
    16. type AnimationEventHandler<T = Element> = EventHandler<React.AnimationEvent<T>>
    17. type TransitionEventHandler<T = Element> = EventHandler<
    18. React.TransitionEvent<T>
    19. >
    bivarianceHack 为事件处理函数的类型定义,函数接收一个 event 对象,并且其类型为接收到的泛型变量 E 的类型, 返回值为 void
    关于为何是用 bivarianceHack 而不是(event: E): void,这与 strictfunctionTypes 选项下的功能兼容性有关。(event: E): void,如果该参数是派生类型,则不能将其传递给参数是基类的函数。
    1. class Animal {
    2. private x: undefined
    3. }
    4. class Dog extends Animal {
    5. private d: undefined
    6. }
    7. type EventHandler<E extends Animal> = (event: E) => void
    8. let z: EventHandler<Animal> = (o: Dog) => {} // fails under strictFunctionTyes
    9. type BivariantEventHandler<E extends Animal> = {
    10. bivarianceHack(event: E): void
    11. }['bivarianceHack']
    12. let y: BivariantEventHandler<Animal> = (o: Dog) => {}

    Promise 类型

    在做异步操作时经常使用 async 函数,函数调用时会 return 一个 Promise 对象,可以使用 then 方法添加回调函数。Promise<T> 是一个泛型类型,T 泛型变量用于确定 then 方法时接收的第一个回调函数的参数类型。 ```jsx type IResponse = { message: string result: T success: boolean } async function getResponse(): Promise> { return { message: ‘获取成功’, result: [1, 2, 3], success: true, } }

getResponse().then(response => { console.log(response.result) })

  1. 首先声明 IResponse 的泛型接口用于定义 response 的类型,通过 T 泛型变量来确定 result 的类型。然后声明了一个 异步函数 `getResponse` 并且将函数返回值的类型定义为 `Promise<IResponse<number[]>>` 。最后调用 `getResponse` 方法会返回一个 promise 类型,通过 then 调用,此时 `then` 方法接收的第一个回调函数的参数 response 的类型为,`{ message: string, result: number[], success: boolean}`
  2. <a name="dyjDe"></a>
  3. ### 泛型参数的组件
  4. 下面这个组件的 name 属性都是指定了传参格式,如果想不指定,而是想通过传入参数的类型去推导实际类型,这就要用到泛型。
  5. ```jsx
  6. const TestB = ({ name, name2 }: { name: string; name2?: string }) => {
  7. return (
  8. <div className="test-b">
  9. TestB--{name}
  10. {name2}
  11. </div>
  12. )
  13. }

如果需要外部传入参数类型,只需 ->

  1. type Props<T> = {
  2. name: T
  3. name2?: T
  4. }
  5. const TestC: <T>(props: Props<T>) => React.ReactElement = ({ name, name2 }) => {
  6. return (
  7. <div className="test-b">
  8. TestB--{name}
  9. {name2}
  10. </div>
  11. )
  12. }
  13. const TestD = () => {
  14. return (
  15. <div>
  16. <TestC<string> name="123" />
  17. </div>
  18. )
  19. }

什么时候使用泛型

当函数,接口或者类:

  • 需要作用到很多类型的时候,举个 🌰

当需要一个 id 函数,函数的参数可以是任何值,返回值就是将参数原样返回,并且其只能接受一个参数,在 js 时代会很轻易地甩出一行

  1. const id = arg => arg

由于其可以接受任意值,也就是说函数的入参和返回值都应该可以是任意类型,如果不使用泛型,只能重复的进行定义

  1. type idBoolean = (arg: boolean) => boolean
  2. type idNumber = (arg: number) => number
  3. type idString = (arg: string) => string
  4. // ...

如果使用泛型,只需要

  1. function id<T>(arg: T): T {
  2. return arg
  3. }
  4. // 或
  5. const id1: <T>(arg: T) => T = arg => {
  6. return arg
  7. }
  • 需要被用到很多地方的时候,比如常用的工具泛型 Partial

功能是将类型的属性变成可选, 注意这是浅 Partial

  1. type Partial<T> = { [P in keyof T]?: T[P] }

如果需要深 Partial 可以通过泛型递归来实现

  1. type DeepPartial<T> = T extends Function
  2. ? T
  3. : T extends object
  4. ? { [P in keyof T]?: DeepPartial<T[P]> }
  5. : T
  6. type PartialedWindow = DeepPartial<Window>