React + TypeScript 实践

主要内容包括准备知识、如何引入 React、函数式组件的声明方式、Hooks、useRef<T>、useEffect、useMemo<T> / useCallback<T>、自定义 Hooks、默认属性 defaultProps、Types or Interfaces、获取未导出的 Type、Props、常用 Props ts 类型、常用 React 属性类型、Forms and Events、onSubmit、Operators、Tips、不要在 type 或 interface 中使用函数声明、事件处理、Promise 类型、泛型参数的组件、什么时候使用泛型、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

准备知识

  • 熟悉 React
  • 熟悉 TypeScript (参考书籍:2ality’s guide[1], 初学者建议阅读:chibicode’s tutorial[2])
  • 熟读 React 官方文档 TS 部分[3]
  • 熟读 TypeScript playground React 部分[4]

验证环境

"umi": "^4.0.63"
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"typescript": "^5.0.0"

如何引入 React

import * as React from 'react'
import * as ReactDOM from 'react-dom'

这种引用方式被证明[5]是最可靠的一种方式, 推荐使用

而另外一种引用方式:

import React from 'react'
import ReactDOM from 'react-dom'

需要添加额外的配置:“allowSyntheticDefaultImports”: true (项目中虽然没手动加,在umi项目的./src/.umi/tsconfig.json中可以看到有这个配置项)

函数式组件的声明方式

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

// Great
type AppProps = {message: string,children?: React.ReactNode	//React18以后需要加children属性
}const App: React.FC<AppProps> = ({ message, children }) => (<div>{message}{children}</div>
)

需要注意的是React18以前不需要显式指定children属性,而根据官方文档,React18需要明确列出:

image

React17中**@types/react**的对FC的定义,帮我们默认声明了:

    interface FunctionComponent<P = {}> {(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;propTypes?: WeakValidationMap<P> | undefined;contextTypes?: ValidationMap<any> | undefined;defaultProps?: Partial<P> | undefined;displayName?: string | undefined;}type PropsWithChildren<P> = P & { children?: ReactNode | undefined };

React18中**@types/react**的对FC的定义,取消了默认声明:

    interface FunctionComponent<P = {}> {(props: P, context?: any): ReactElement<any, any> | null;propTypes?: WeakValidationMap<P> | undefined;contextTypes?: ValidationMap<any> | undefined;defaultProps?: Partial<P> | undefined;displayName?: string | undefined;}

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

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

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

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

实测截图:

ts-4.png

ts-2.png

ts-3.png

解决方法:

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

在通常情况下,使用 React.FC 的方式声明最简单有效,推荐使用;如果出现类型不兼容问题,建议使用以下两种方式:

第二种:使用 PropsWithChildren,这种方式可以为你省去频繁定义 children 的类型,自动设置 children 类型为 ReactNode:

type AppProps = React.PropsWithChildren<{ message: string }>
const App = ({ message, children }: AppProps) => (<div>{message}{children}</div>
)

第三种:直接声明:

type AppProps = {message: stringchildren?: React.ReactNode
}const App = ({ message, children }: AppProps) => (<div>{message}{children}</div>
)

Hooks

useState<T>

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

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

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

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

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

  const [user, setUser] = React.useState(null)const showUser = React.useCallback((obj: typeof user) => {return `My name is ${obj.name}, My age is ${obj.age}`;	//ts报错:“obj”可能为 “null”。}, []);//改成显示声明
type User = {name: stringage: number
}		const [user, setUser] = React.useState<User|null>(null)const showUser = React.useCallback((obj: typeof user) => {//obj可能是null,需要判断下if (null === obj) return '--';return `My name is ${obj.name}, My age is ${obj.age}`;}, []);

useRef<T>

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

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

这两种的区别在于

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

为了验证只读属性,将HTMLInputElement改成number:

  const ref1 = React.useRef<number>(null);const ref2 = React.useRef<number | null>(null);ref1.current = 1;	//ts报错:无法为“current”赋值,因为它是只读属性。ref2.current = 2;

但是如果初始值不为null,那ref.current是可变的:

//写不写<number>都可以,跟useState一样也有类型推导
const ref = React.useRef(0)
React.useEffect(() => {ref.current += 1
}, [])

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

const onButtonClick = () => {ref1.current?.focus()ref2.current?.focus()
}

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

// Bad
function MyComponent() {const ref1 = React.useRef<HTMLDivElement>(null!);	//null!断言React.useEffect(() => {//  不需要做类型检查,需要人为保证ref1.current.focus一定存在doSomethingWith(ref1.current.focus())})return <div ref={ref1}> etc </div>
}

useEffect

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

function App() {// undefined作为回调函数的返回值React.useEffect(() => {// do something...}, []);// 返回值是一个函数React.useEffect(() => {// do something...return () => {};}, []);
}

useMemo<T> / useCallback<T>

useMemo 和 useCallback 都可以直接从它们返回的值中推断出它们的类型。

useCallback 的参数必须指定类型,否则 ts 会报错,默认指定为 any:

  const value = 10, multiplier = 20;// 自动推断result返回值为 numberconst result = React.useMemo(() => value * 2, [value]);// 自动推断multiply 类型为 (value: number) => numberconst multiply = React.useCallback((value: number) => value * multiplier,[multiplier]);

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

  // useMemo也可以显式的指定返回值类型,返回值不一致会报错//类型“() => number”的参数不能赋给类型“() => string”的参数。const result = React.useMemo<string>(() => 2, []);  //ts报错:不能将类型“number”分配给类型“string”。//传入的React.ChangeEventHandler<HTMLInputElement>指定了evt的类型const handleChange1 = React.useCallback<React.ChangeEventHandler<HTMLInputElement>>((evt) => {console.log(evt.target.value);}, []);//等同于(自己推断的,未经过实际项目验证)const handleChange2 = React.useCallback((evt: React.ChangeEvent<HTMLInputElement>) => {console.log(evt.target.value);},[]);

自定义 Hooks

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

//未使用as const推断的类型:function useLoading(): (boolean | ((aPromise: Promise<any>) => Promise<void>))[]
//使用as const后推断的类型:function useLoading(): readonly [boolean, (aPromise: Promise<any>) => Promise<void>]
function useLoading() {const [isLoading, setState] = React.useState(false);const load = (aPromise: Promise<any>) => {setState(true);return aPromise.then(() => setState(false));};// 实际需要: [boolean, typeof load] 类型// 而不是自动推导的:(boolean | typeof load)[]return [isLoading, load] as const;
}

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

export function useLoading(): [boolean,(aPromise: Promise<any>) => Promise<any>
] {const [isLoading, setState] = React.useState(false);const load = (aPromise: Promise<any>) => {setState(true);return aPromise.then(() => setState(false));};return [isLoading, load];
}

如果有大量的自定义 Hook 需要处理,这里有一个方便的工具方法可以处理 数组 返回值:

//实际上就是把参数数组直接返回
function tuplify<T extends any[]>(...elements: T) {return elements;
}//function useLoading(): (boolean | ((aPromise: Promise<any>) => Promise<void>))[]
function useLoading() {const [isLoading, setState] = React.useState(false);const load = (aPromise: Promise<any>) => {setState(true);return aPromise.then(() => setState(false));};// (boolean | typeof load)[]return [isLoading, load];
}//function useTupleLoading(): [boolean, (aPromise: Promise<any>) => Promise<void>]
function useTupleLoading() {const [isLoading, setState] = React.useState(false);const load = (aPromise: Promise<any>) => {setState(true);return aPromise.then(() => setState(false));};// [boolean, typeof load]return tuplify(isLoading, load);
}

默认属性 defaultProps

大部分文章都不推荐使用 defaultProps , 相关讨论可以点击参考链接[8]

推荐方式:使用默认参数值来代替默认属性:

type GreetProps = { age?: number }
const Greet = ({ age = 21 }: GreetProps) => {/* ... */
}

defaultProps 类型

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

type IProps = {name: string;
};
const defaultProps = {age: 25,
};// 类型定义, 合并类型
type GreetProps = IProps & typeof defaultProps;
const Greet = (props: GreetProps) => (<div>{props.age}{props.name}</div>
);
//写不写好像都可以,TS都没有报错,TS版本号^5.0.0
Greet.defaultProps = defaultProps;// 使用 将Gree的默认类型作为类型定义传递
const TestComponent = (props: React.ComponentProps<typeof Greet>) => {return (<div>{props.age}{props.name}</div>);
};const el = <TestComponent name='foo' age={11} />;

Types or Interfaces

在日常的 react 开发中 interface 和 type 的使用场景十分类似

implements 与 extends 静态操作,不允许存在一种或另一种实现的情况,所以不支持使用联合类型:

class Point {x: number = 2y: number = 3
}
interface IShape {area(): number
}
type Perimeter = {perimeter(): number
}
//联合类型,既可以是IShape,也可以是Perimeter,类型未知
type RectangleShape = (IShape | Perimeter) & Point;class Rectangle implements RectangleShape {// TS报错:类只能实现具有静态已知成员的对象类型或对象类型的交集。x = 2y = 3area() {return this.x + this.y}
}//TS报错: 接口只能扩展使用静态已知成员的对象类型或对象类型的交集。
interface ShapeOrPerimeter extends RectangleShape {}

使用 Type 还是 Interface?

有几种常用规则:

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

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

  • type 类型不能二次编辑,而 interface 可以随时扩展
interface Animal {name: string;
}// 可以继续在原有属性基础上,添加新属性:color
interface Animal {color: string;
}
/********************************/
type Animal = {name: string;
};
// type类型不支持属性扩展
// Error: Duplicate identifier 'Animal'
type Animal = {color: string;
};

获取未导出的 Type

某些场景下我们在引入第三方的库时会发现想要使用的组件并没有导出我们需要的组件参数类型或者返回值类型,这时候我们可以通过 ComponentProps/ ReturnType 来获取到想要的类型。

// 获取参数类型
import { Button } from 'antd' // 但是未导出props type
type ButtonProps = React.ComponentProps<typeof Button> // 获取props
type AlertButtonProps = Omit<ButtonProps, 'onClick'> // 去除onClick属性,Omit:TS提供的方法,去除类型中某些项//即AlertButton用了Button的属性,但是又去除了onClick
const AlertButton: React.FC<AlertButtonProps> = props => (<Button onClick={() => alert('hello')} {...props} />
)const AlertButton2 = () => (//TS报错:不能将类型“{ onClick: () => void; }”分配给类型“IntrinsicAttributes & AlertButtonProps”。//类型“IntrinsicAttributes & AlertButtonProps”上不存在属性“onClick”。<AlertButton onClick={ () => alert('hello')}/>
)
// 获取返回值类型
function foo() {return { baz: 1 };
}
type FooReturn = ReturnType<typeof foo>; // { baz: number }

Props

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

现在有这样一个 type:

type OtherProps = {name: string;color: string;
};const OtherHeading: React.FC<OtherProps> = ({ name, color }) => (<h1>My Website Heading</h1>
);

在使用的过程中,鼠标移到对应类型会有如下展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HyXwVetF-1688693203724)(https://s2.loli.net/2023/07/06/Soi185ZQ2pGUDBC.png)]

ts-hover-2.png

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

/*** @param color color param description* @param children children param description* @param onClick onClick param description*/
type Props = {/** color description */color?: string/** children description*/children: React.ReactNode/** onClick description*/onClick: () => void
}const Button: React.FC<Props> = ({ children, color = 'tomato', onClick }) => {return (<button style={{ backgroundColor: color }} onClick={onClick}>{children}</button>)
}

鼠标再次移到对应类型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jfBt8zlP-1688693203726)(https://s2.loli.net/2023/07/06/HshnAwpJMjF4zVB.png)]

ts-hover-4.png

常用 Props ts 类型

基础属性类型

//MyTypeHere、OptionalType为任意定义的类型
type AppProps = {message: stringcount: numberdisabled: boolean/** array of a type! */names: string[]/** string literals to specify exact string values, with a union type to join them together */status: 'waiting' | 'success'/** 任意需要使用其属性的对象(不推荐使用,但是作为占位很有用) */obj: object/** 作用和`object`几乎一样,和 `Object`完全一样 */obj2: {}/** 列出对象全部数量的属性 (推荐使用) */obj3: {id: stringtitle: string}/** array of objects! (common) */objArr: {id: stringtitle: string}[]/** 任意数量属性的字典,具有相同类型*/dict1: {[key: string]: MyTypeHere}/** 作用和dict1完全相同 */dict2: Record<string, MyTypeHere>/** 任意完全不会调用的函数 */onSomething: Function/** 没有参数&返回值的函数 */onClick: () => void/** 携带参数的函数 */onChange: (id: number) => void/** 携带点击事件的函数 */onClick(event: React.MouseEvent<HTMLButtonElement>): void/** 可选的属性 */optional?: OptionalType
}

常用 React 属性类型

//通用的,相对比较友好的类型定义
export declare interface AppBetterProps {children: React.ReactNode // 一般情况下推荐使用,支持所有类型 GreatfunctionChildren: (name: string) => React.ReactNodestyle?: React.CSSProperties // 传递style对象onChange?: React.FormEventHandler<HTMLInputElement>
}//比较一般的类型定义
export declare interface AppProps {children1: JSX.Element // 差, 不支持数组children2: JSX.Element | JSX.Element[] // 一般, 不支持字符串children3: React.ReactChildren // 忽略命名,不是一个合适的类型,工具类类型children4: React.ReactChild[] // 很好children: React.ReactNode // 最佳,支持所有类型 推荐使用functionChildren: (name: string) => React.ReactNode // recommended function as a child render prop typestyle?: React.CSSProperties // 传递style对象onChange?: React.FormEventHandler<HTMLInputElement> // 表单事件, 泛型参数是event.target的类型
}

Forms and Events

onChange

change 事件,有两个定义参数类型的方法。

第一种方法使用推断的方法签名(例如:React.FormEvent <HTMLInputElement> :void

type changeFn = (e: React.FormEvent<HTMLInputElement>) => void;const App: React.FC = () => {const [state, setState] = React.useState('');const onChange: changeFn = (e) => {setState(e.currentTarget.value);};return (<div><input type='text' value={state} onChange={onChange} /></div>);
};

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

const App: React.FC = () => {const [state, setState] = React.useState('');const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {setState(e.currentTarget.value);};return (<div><input type='text' value={state} onChange={onChange} /></div>);
};

onSubmit

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

const App: React.FC = () => {const onSubmit = (e: React.SyntheticEvent) => {e.preventDefault();// 类型扩展const target = e.target as typeof e.target & {password: { value: string };}; const password = target.password.value;};return (<form onSubmit={onSubmit}><div><label>Password:<input type='password' name='password' /></label></div><div><input type='submit' value='Log in' /></div></form>);
};

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 这个必传参数:

// counter.tsx
import * as React from 'react'
export type Props = {name: string;
};
const Counter: React.FC<Props> = (props) => {return <></>;
};
export default Counter;

在其他引用它的组件中我们有两种方式获取到 Counter 的参数类型

第一种是通过 typeof 操作符(推荐):

// Great
import Counter from './d-tips1'
//获取Counter的类型并添加新的属性age
type PropsNew = React.ComponentProps<typeof Counter> & {age: number
}
const App: React.FC<PropsNew> = props => {return <Counter {...props} />
}
export default App

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

import Counter, { Props } from './d-tips1'
type PropsNew = Props & {age: number
}
const App: React.FC<PropsNew> = props => {return (<><Counter {...props} /></>)
}
export default App

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

保持一致性,类型/接口的所有成员都通过相同的语法定义。

–strictFunctionTypes 在比较函数类型时强制执行更严格的类型检查,但第二种声明方式下严格检查不生效。

✅ 尽量使用这种方式
interface ICounter {start: (value: number) => string
}❌ 最好别用这种方式
interface ICounter1 {start(value: number): string
}

示例:

interface Animal {}
interface Dog extends Animal {wow: () => void
}//使用这种类型定义检查更严格
interface Comparer<T> {compare: (a: T, b: T) => number
}
declare let animalComparer: Comparer<Animal>
declare let dogComparer: Comparer<Dog>
animalComparer = dogComparer // Error	不能将类型“Comparer<Dog>”分配给类型“Comparer<Animal>”。 类型 "Animal" 中缺少属性 "wow",但类型 "Dog" 中需要该属性
dogComparer = animalComparer // Ok//使用这种类型定义检查较宽松,容易引发未知问题
interface Comparer1<T> {compare(a: T, b: T): number
}
declare let animalComparer1: Comparer1<Animal>
declare let dogComparer1: Comparer1<Dog>
animalComparer1 = dogComparer1 // Ok
dogComparer1 = animalComparer1 // Ok

事件处理

我们在进行事件注册时经常会在事件处理函数中使用 event 事件对象,例如当使用鼠标事件时我们通过 clientXclientY 去获取指针的坐标。

大家可能会想到直接把 event 设置为 any 类型,但是这样就失去了我们对代码进行静态检查的意义。

function handleEvent(event: any) {console.log(event.clientY)
}

试想下当我们注册一个 Touch 事件,然后错误的通过事件处理函数中的 event 对象去获取其 clientY 属性的值,在这里我们已经将 event 设置为 any 类型,导致 TypeScript 在编译时并不会提示我们错误, 当我们通过 event.clientY 访问时就有问题了,因为 Touch 事件的 event 对象并没有 clientY 这个属性。

通过 interface 对 event 对象进行类型声明编写的话又十分浪费时间,幸运的是 React 的声明文件提供了 Event 对象的类型声明。

Event 事件对象类型

  • ClipboardEvent<T = Element> 剪切板事件对象
  • DragEvent<T =Element> 拖拽事件对象
  • ChangeEvent<T = Element> Change 事件对象
  • KeyboardEvent<T = Element> 键盘事件对象
  • MouseEvent<T = Element> 鼠标事件对象
  • TouchEvent<T = Element> 触摸事件对象
  • WheelEvent<T = Element> 滚轮时间对象
  • AnimationEvent<T = Element> 动画事件对象
  • TransitionEvent<T = Element> 过渡事件对象

事件处理函数类型

当我们定义事件处理函数时有没有更方便定义其函数类型的方式呢?答案是使用 React 声明文件所提供的 EventHandler 类型别名,通过不同事件的 EventHandler 的类型别名来定义事件处理函数的类型

type EventHandler<E extends React.SyntheticEvent<any>> = {bivarianceHack(event: E): void
}['bivarianceHack']
type ReactEventHandler<T = Element> = EventHandler<React.SyntheticEvent<T>>
type ClipboardEventHandler<T = Element> = EventHandler<React.ClipboardEvent<T>>
type DragEventHandler<T = Element> = EventHandler<React.DragEvent<T>>
type FocusEventHandler<T = Element> = EventHandler<React.FocusEvent<T>>
type FormEventHandler<T = Element> = EventHandler<React.FormEvent<T>>
type ChangeEventHandler<T = Element> = EventHandler<React.ChangeEvent<T>>
type KeyboardEventHandler<T = Element> = EventHandler<React.KeyboardEvent<T>>
type MouseEventHandler<T = Element> = EventHandler<React.MouseEvent<T>>
type TouchEventHandler<T = Element> = EventHandler<React.TouchEvent<T>>
type PointerEventHandler<T = Element> = EventHandler<React.PointerEvent<T>>
type UIEventHandler<T = Element> = EventHandler<React.UIEvent<T>>
type WheelEventHandler<T = Element> = EventHandler<React.WheelEvent<T>>
type AnimationEventHandler<T = Element> = EventHandler<React.AnimationEvent<T>>
type TransitionEventHandler<T = Element> = EventHandler<React.TransitionEvent<T>
>

用法:

const App = () => {const onClick: React.MouseEventHandler<HTMLDivElement> = (e) => {alert(e);};return <div onClick={onClick}>TEST</div>;
};

bivarianceHack 为事件处理函数的类型定义,函数接收一个 event 对象,并且其类型为接收到的泛型变量 E 的类型, 返回值为 void

关于为何是用 bivarianceHack 而不是(event: E): void,这与 strictfunctionTypes 选项下的功能兼容性有关。(event: E): void,如果该参数是派生类型,则不能将其传递给参数是基类的函数。

示例(不太理解其实):

class Animal {private x: undefined;
}
class Dog extends Animal {private d: undefined;
}
type EventHandler<E extends Animal> = (event: E) => void;
// fails under strictFunctionTyes 不能将类型“(o: Dog) => void”分配给类型“EventHandler<Animal>”。参数“o”和“event” 的类型不兼容。类型 "Animal" 中缺少属性 "d",但类型 "Dog" 中需要该属性。
let z: EventHandler<Animal> = (o: Dog) => {}; 
type BivariantEventHandler<E extends Animal> = {bivarianceHack(event: E): void;
}['bivarianceHack'];
let y: BivariantEventHandler<Animal> = (o: Dog) => {};

Promise 类型

在做异步操作时我们经常使用 async 函数,函数调用时会 return 一个 Promise 对象,可以使用 then 方法添加回调函数。Promise<T> 是一个泛型类型,T 泛型变量用于确定 then 方法时接收的第一个回调函数的参数类型。

//这里用泛型T主要是可扩展,毕竟result可能是任何类型
type IResponse<T> = {message: string;result: T;success: boolean;
};
async function getResponse(): Promise<IResponse<number[]>> {return {message: '获取成功',result: [1, 2, 3],success: true,};
}getResponse().then((response) => {console.log(response.result);
});

首先声明 IResponse 的泛型接口用于定义 response 的类型,通过 T 泛型变量来确定 result 的类型。然后声明了一个 异步函数 getResponse 并且将函数返回值的类型定义为 Promise<IResponse<number[]>> 。最后调用 getResponse 方法会返回一个 promise 类型,通过 then 调用,此时 then 方法接收的第一个回调函数的参数 response 的类型为,{ message: string, result: number[], success: boolean} 。

泛型参数的组件

下面这个组件的 name 属性都是指定了传参格式,如果想不指定,而是想通过传入参数的类型去推导实际类型,这就要用到泛型。

const TestB = ({ name, name2 }: { name: string; name2?: string }) => {return (<div className='test-b'>TestB--{name}{name2}</div>);
};

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

type Props<T> = {name: T;name2?: T;
};
const TestC: <T extends React.ReactNode>(props: Props<T>
) => React.ReactElement = ({ name, name2 }) => {return (<div className='test-b'>TestB--{name}{name2}</div>);
};const TestD = () => {return (<div><TestC<string> name='123' /></div>);
};

什么时候使用泛型

当你的函数,接口或者类:

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

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

const id = arg => arg

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

type idBoolean = (arg: boolean) => boolean
type idNumber = (arg: number) => number
type idString = (arg: string) => string

如果使用泛型,我们只需要

function id<T>(arg: T): T {return arg
}// 或
const id1: <T>(arg: T) => T = arg => {return arg
}
  • 需要被用到很多地方的时候,比如常用的工具泛型 Partial。(没懂)

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

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

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

type DeepPartial<T> = T extends Function? T: T extends object? { [P in keyof T]?: DeepPartial<T[P]> }: T
type PartialedWindow = DeepPartial<Window>

参考资料

[1]

2ality’s guide: http://2ality.com/2018/04/type-notation-typescript.html

[2]

chibicode’s tutorial: https://ts.chibicode.com/todo/

[3]

TS 部分: https://reactjs.org/docs/static-type-checking.html#typescript

[4]

React 部分: http://www.typescriptlang.org/play/index.html?jsx=2&esModuleInterop=true&e=181#example/typescript-with-react

[5]

被证明: https://www.reddit.com/r/reactjs/comments/iyehol/import_react_from_react_will_go_away_in_distant/

[6]

一些 issue: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/33006

[7]

问题: https://github.com/babel/babel/issues/9800

[8]

参考链接: https://twitter.com/hswolff/status/1133759319571345408

[9]

TypeScript3.0+: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html

[10]

存在一些边界 case 仍然存在问题: https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/issues/61

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/12933.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TensorFlow: mode.save()报错 non-trackable object: (None, None)

问题描述 环境&#xff1a;tensorflow2.2.0 执行model.save(), 报错 ...non-trackable object: (None, None)... 解决办法 搞了半天没有找到好的办法&#xff0c;只能通过升级搞定 pip uninstall tensorflow pip install tensorflow2.6.0 pip install --upgrade keras2.6…

数据结构--线性表(顺序表、单链表、双链表、循环链表、静态链表)

前言 学习所记录&#xff0c;如果能对你有帮助&#xff0c;那就泰裤辣。 目录 1.线性表概念 定义 基本操作 2.顺序表 定义 顺序表的实现--静态分配 动态分配 顺序表的特点 顺序表的插入和删除 顺序表的查找 按位查找 按值查找 3.单链表 定义 单链表的初始化 不带…

在Windows环境下安装Elasticsearch 8.8.2

Elasticsearch是一种开源的分布式搜索和分析引擎&#xff0c;被广泛应用于构建实时搜索、日志分析、数据可视化等应用。本文将详细介绍如何在Windows环境下安装和配置Elasticsearch 8。 安装Elasticsearch 步骤1&#xff1a;准备工作 在开始安装之前&#xff0c;确保已满足以…

gitee注册以及使用的简单教程

目录 1.gitee是什么&#xff1f; 2. gitee怎么注册? 3.gitee创建仓库 4.gitee怎么提交代码? 5. git的三板斧 1.gitee是什么&#xff1f; 基于Git的代码托管和研发协作平台上面可以托管个人或者公司的代码和开源项目。国外有github&#xff0c;国内有giteegithub经常出现…

docker安装postgresql

docker run --name postgres -e POSTGRES_PASSWORD123456 -p 5432:5432 -v /mydata/postgres/pgdata:/var/lib/postgresql/data -d postgres 修改postgresql最大连接数 vim /mydata/postgres/pgdata/postgresql.conf 附:常用连接数查看命令 -- 1.查看当前配置的最大连接数 s…

4通道AD采集子卡模块有哪些推荐?

FMC134是一款4通道3.2GSPS&#xff08;2通道6.4GSPS&#xff09;采样率12位AD采集FMC子卡模块&#xff0c;该板卡为FMC标准&#xff0c;符合VITA57.4规范&#xff0c;可以作为一个理想的IO模块耦合至FPGA前端&#xff0c;16通道的JESD204B接口通过FMC连接器连接至FPGA的高速串行…

为了实现上网自由,我做了一个多功能串口服务器

项目作者&#xff1a;小华的物联网嵌入式之旅 介绍&#xff1a;从事电气自动化行业&#xff0c;多次获得物联网设计竞赛&#xff0c;爱好嵌入式设计开发&#xff0c;物联网开发。 设计方案思路的由来&#xff0c;是因为我们现在的开发板基本需要通过串口与WIFI模组或以太网模…

Redis 从入门到精通【进阶篇】之Redis事务详解

文章目录 0.前言1.Redis 事务基本流程 1.事务详解1.1. 开始事务1.2. 命令入队1.3. 执行事务1.6. 带 WATCH 的事务1.7. WATCH 命令的实现1.8. WATCH 的触发1.9. 事务的 ACID 性质 2.总结2.1. 在事务和非事务状态下2.2. 小结2.3. 为什么Redis 的事务并不是真正的原子操作2.4. 为什…

SpringBoot学习——追根溯源servlet是啥,tomcat是啥,maven是啥 springBoot项目初步,maven构建,打包 测试

目录 引出追根溯源&#xff0c;过渡衔接servlet是啥&#xff1f;tomcat是啥&#xff1f; 前后端开发的模式1.开发模式&#xff1a;JavaWeb&#xff1a;MVC模型2.Web&#xff1a;Vue&#xff0c;MVVC模型3.后端相关3.1 同步与异步3.2 Controller层3.3 Service层&#xff1a;要加…

【C++初阶(三)】引用详解(对比指针)

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C初阶之路⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习排序知识   &#x1f51d;&#x1f51d; 引用 1. 前言2. 引用的概念3. 引用的特性4. …

Node.js知识梳理(二)——进阶

以下内容来自对《从前端到全栈》一书的学习记录~ 学习的时候用的是V16.20.0&#xff0c;18之后的语法差别还是有的~ 请求优化 我们在请求资源的时候&#xff0c;是需要做优化的&#xff0c;这里的优化涉及到了缓存。浏览器的缓存策略有两种&#xff1a; 强缓存协商缓存 关于…

Valve 签约开源 Linux 图形驱动开发者

导读据外媒 phoronix 报道&#xff0c;Valve 最近聘用了著名开源 Linux 图形驱动开发者 Alyssa Rosenzweig&#xff0c;以改进开源 Linux 图形驱动程序堆栈&#xff0c;增强 Linux 游戏生态系统。 Alyssa Rosenzweig 多年来在 Panfrost 开源、逆向工程 Arm Mali 图形驱动程序方…