如何引入 React 1 2 3 import * as React from 'react' import * as ReactDOM from 'react-dom'
这种引用方式被证明 是最可靠的一种方式, 推荐使用 。
而另外一种引用方式:
1 2 3 import React from 'react' import ReactDOM from 'react-dom'
需要添加额外的配置:"allowSyntheticDefaultImports": true
函数式组件的声明方式 声明的几种方式
第一种:也是比较推荐 的一种,使用 React.FunctionComponent ,简写形式:React.FC:
1 2 3 4 5 6 7 8 9 10 11 type AppProps = { message : string } const App: React.FC<AppProps> = ({ message, children } ) => ( <div > {message} {children} </div > )
使用用 React.FC 声明函数组件 和普通声明 以及 PropsWithChildren 的区别是:
React.FC 显式地定义了返回类型 ,其他方式是隐式推导的
React.FC 对静态属性:displayName、propTypes、defaultProps 提供了类型检查和自动补全
React.FC 为 children 提供了隐式的类型 (ReactElement | null),但是目前,提供的类型存在一些 issue (问题)
比如以下用法 React.FC 会报类型错误:
1 2 3 const App: React.FC = props => props.childrenconst App: React.FC = () => [1 , 2 , 3 ]const App: React.FC = () => 'hello'
解决方法:
1 2 3 4 5 6 7 const App: React.FC<{}> = props => props.children as anyconst App: React.FC<{}> = () => [1 , 2 , 3 ] as anyconst App: React.FC<{}> = () => 'hello' as anyconst App: React.FC<{}> = props => (props.children as unknown) as JSX.Elementconst App: React.FC<{}> = () => ([1 , 2 , 3 ] as unknown) as JSX.Elementconst App: React.FC<{}> = () => ('hello' as unknown) as JSX.Element
在通常情况下,使用 React.FC 的方式声明最简单有效,推荐使用;如果出现类型不兼容问题,建议使用以下两种方式:
第二种:使用 PropsWithChildren ,这种方式可以为你省去频繁定义 children 的类型,自动设置 children 类型为 ReactNode:
1 2 3 4 5 6 7 type AppProps = React.PropsWithChildren<{ message : string }> const App = ({ message, children }: AppProps ) => ( <div > {message} {children} </div > )
第三种:直接声明:
1 2 3 4 5 6 7 8 9 10 11 type AppProps = { message : string children?: React.ReactNode } const App = ({ message, children }: AppProps ) => ( <div > {message} {children} </div > )
Hooks useState<T>
大部分情况下,TS 会自动为你推导 state 的类型:
1 2 3 4 5 6 const [val, toggle] = React.useState(false )const [obj] = React.useState({ name : 'sj' })const [arr] = React.useState(['One' , 'Two' ])
使用推导类型作为接口/类型:
1 2 3 4 5 6 7 8 export default function App ( ) { const [user] = React.useState({ name : 'sj' , age : 32 }) 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 ),需要显示地声明类型:
1 2 3 4 5 type User = { name : string age : number } const [user, setUser] = React.useState<User | null >(null )
useRef<T>
当初始值为 null 时,有两种创建方式:
1 2 const ref1 = React.useRef<HTMLInputElement>(null )const ref2 = React.useRef<HTMLInputElement | null >(null )
这两种的区别在于 :
第一种方式的 ref1.current 是只读的(read-only) ,并且可以传递给内置的 ref 属性,绑定 DOM 元素 ; 第二种方式的 ref2.current 是可变的 (类似于声明类的成员变量) 1 2 3 4 const ref = React.useRef(0 )React.useEffect(() => { ref.current += 1 }, [])
这两种方式在使用时,都需要对类型进行检查:
1 2 3 4 const onButtonClick = () => { ref1.current?.focus() ref2.current?.focus() }
在某种情况下,可以省去类型检查,通过添加 ! 断言 ,不推荐 :
1 2 3 4 5 6 7 8 9 function MyComponent ( ) { const ref1 = React.useRef<HTMLDivElement>(null !) React.useEffect(() => { doSomethingWith(ref1.current.focus()) }) return <div ref ={ref1} > etc </div > }
useEffect
useEffect 需要注意回调函数的返回值只能是函数或者 undefined
1 2 3 4 5 6 7 8 9 10 11 function App ( ) { React.useEffect(() => { }, []) React.useEffect(() => { return () => {} }, []) }
useMemo<T> / useCallback<T>
useMemo 和 useCallback 都可以直接从它们返回的值中推断出它们的类型
useCallback 的参数必须制定类型,否则 ts 不会报错,默认指定为 any
1 2 3 4 5 6 7 const value = 10 const result = React.useMemo(() => value * 2 , [value])const multiply = React.useCallback((value: number ) => value * multiplier, [ multiplier, ])
同时也支持传入泛型, useMemo 的泛型指定了返回值类型,useCallback 的泛型指定了参数类型
1 2 3 4 5 6 7 8 const result = React.useMemo<string>(() => 2 , [])const handleChange = React.useCallback< React.ChangeEventHandler<HTMLInputElement> >(evt => { console .log(evt.target.value) }, [])
自定义 Hooks 需要注意,自定义 Hook 的返回值如果是数组类型 ,TS 会自动推导为 Union 类型,而我们实际需要的是数组里里每一项的具体类型,需要手动添加 const 断言 进行处理:
1 2 3 4 5 6 7 8 9 10 function useLoading ( ) { const [isLoading, setState] = React.useState(false ) const load = (aPromise: Promise <any> ) => { setState(true ) return aPromise.then(() => setState(false )) } return [isLoading, load] as const }
如果使用 const 断言遇到问题 ,也可以直接定义返回类型:
1 2 3 4 5 6 7 8 9 10 11 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 需要处理,这里有一个方便的工具方法可以处理 tuple 返回值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function tuplify <T extends any []>(...elements: T ) { return elements } function useLoading ( ) { const [isLoading, setState] = React.useState(false ) const load = (aPromise: Promise <any> ) => { setState(true ) return aPromise.then(() => setState(false )) } return [isLoading, load] } function useTupleLoading ( ) { const [isLoading, setState] = React.useState(false ) const load = (aPromise: Promise <any> ) => { setState(true ) return aPromise.then(() => setState(false )) } return tuplify(isLoading, load) }
默认属性 defaultProps 大部分文章都不推荐 使用 defaultProps , 相关讨论可以点击参考链接
推荐方式:使用默认参数值 来代替默认属性:
1 2 3 4 type GreetProps = { age?: number } const Greet = ({ age = 21 }: GreetProps ) => { }
defaultProps 类型 TypeScript3.0+在默认属性 的类型推导上有了极大的改进,虽然尚且 存在一些边界 case 仍然存在问题 ,不推荐使用 ,如果有需要使用的场景,可参照如下方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type IProps = { name : string } const defaultProps = { age : 25 , } type GreetProps = IProps & typeof defaultProps const Greet = (props: GreetProps ) => <div > </div > Greet.defaultProps = defaultProps const TestComponent = (props: React.ComponentProps<typeof Greet> ) => { return <h1 /> } const el = <TestComponent name ="foo" />
Types or Interfaces 在日常的 react 开发中 interface 和 type 的使用场景十分类似
implements 与 extends 静态操作,不允许存在一种或另一种实现的情况,所以不支持使用联合类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Point { x : number = 2 y : number = 3 } interface IShape { area(): number } type Perimeter = { perimeter(): number } type RectangleShape = (IShape | Perimeter) & Point class Rectangle implements RectangleShape { x = 2 y = 3 area ( ) { return this .x + this .y } } interface ShapeOrPerimeter extends RectangleShape {}
使用 Type 还是 Interface? 有几种常用规则:
interface 和 type 在 ts 中是两个不同的概念,但在 React 大部分使用的 case 中,interface 和 type 可以达到相同的功能效果,type 和 interface 最大的区别 是:
type 类型不能二次编辑,而 interface 可以随时扩展1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface Animal { name : string } interface Animal { color : string } type Animal = { name : string } type Animal = { color : string }
获取未导出的 Type 某些场景下我们在引入第三方的库时会发现想要使用的组件并没有导出我们需要的组件参数类型或者返回值类型,这时候我们可以通过 ComponentProps/ ReturnType 来获取到想要的类型。
1 2 3 4 5 6 7 8 9 10 11 12 import { Button } from 'library' type ButtonProps = React.ComponentProps<typeof Button> type AlertButtonProps = Omit<ButtonProps, 'onClick' > const AlertButton: React.FC<AlertButtonProps> = props => ( <Button onClick ={() => alert('hello')} {...props} /> ) function foo ( ) { return { baz : 1 } } type FooReturn = ReturnType<typeof foo>
Props 通常我们使用 type 来定义 Props ,为了提高可维护性和代码可读性,在日常的开发过程中我们希望可以添加清晰的注释。
现在有这样一个 type
1 2 3 4 type OtherProps = { name : string color : string }
在使用的过程中,hover 对应类型会有如下展示
1 2 3 4 5 6 7 const OtherHeading: React.FC<OtherProps> = ({ name, color } ) => ( <h1 > My Website Heading</h1 > )
增加相对详细的注释,使用时会更清晰,需要注意,注释需要使用 /**/ , // 无法被 vscode 识别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 type Props = { color?: string children : React.ReactNode onClick : () => void } const Button: React.FC<Props> = ({ children, color = 'tomato' , onClick } ) => { return ( <button style ={{ backgroundColor: color }} onClick ={onClick} > {children} </button > ) }
常用 Props ts 类型 基础属性类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 type AppProps = { message : string count : number disabled : boolean names : string[] status : 'waiting' | 'success' obj : object obj2 : {} obj3 : { id : string title : string } objArr : { id : string title : string }[] dict1 : { [key: string]: MyTypeHere } dict2 : Record<string, MyTypeHere> onSomething: Function onClick : () => void onChange : (id: number ) => void onClick(event: React.MouseEvent<HTMLButtonElement>): void optional?: OptionalType }
常用 React 属性类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 export declare interface AppBetterProps { children : React.ReactNode functionChildren : (name: string ) => React.ReactNode style?: React.CSSProperties 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 style?: React.CSSProperties onChange?: React.FormEventHandler<HTMLInputElement> }
onChange change 事件,有两个定义参数类型的方法。
第一种方法使用推断的方法签名(例如:React.FormEvent <HTMLInputElement> :void
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import * as React from 'react' 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 提供的委托类型,两种方法均可。
1 2 3 4 5 6 7 8 9 10 11 import * as React from '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 ,如果目标表单有想要访问的自定义命名输入,可以使用类型扩展
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import * as React from 'react' 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 这个必传参数:
1 2 3 4 5 6 7 8 9 import * as React from 'react' export type Props = { name : string } const Counter: React.FC<Props> = props => { return <> </> } export default Counter
在其他引用它的组件中我们有两种方式获取到 Counter 的参数类型
第一种是通过 typeof 操作符(推荐 )
1 2 3 4 5 6 7 8 9 import Counter from './d-tips1' type PropsNew = React.ComponentProps<typeof Counter> & { age : number } const App: React.FC<PropsNew> = props => { return <Counter {...props } /> } export default App
第二种是通过在原组件进行导出
1 2 3 4 5 6 7 8 9 10 11 12 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 在比较函数类型时强制执行更严格的类型检查,但第一种声明方式下严格检查不生效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ✅ 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 dogComparer = animalComparer interface Comparer1<T> { compare(a: T, b : T): number } declare let animalComparer1: Comparer1<Animal> declare let dogComparer1: Comparer1<Dog> animalComparer1 = dogComparer dogComparer1 = animalComparer
事件处理 我们在进行事件注册时经常会在事件处理函数中使用 event 事件对象,例如当使用鼠标事件时我们通过 clientX 、clientY 去获取指针的坐标。
大家可能会想到直接把 event 设置为 any 类型,但是这样就失去了我们对代码进行静态检查的意义。
1 2 3 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 的类型别名来定义事件处理函数的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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> >
bivarianceHack 为事件处理函数的类型定义,函数接收一个 event 对象,并且其类型为接收到的泛型变量 E 的类型, 返回值为 void
关于为何是用 bivarianceHack 而不是(event: E): void,这与 strictfunctionTypes 选项下的功能兼容性有关。(event: E): void,如果该参数是派生类型,则不能将其传递给参数是基类的函数。
1 2 3 4 5 6 7 8 9 10 11 12 class Animal { private x: undefined } class Dog extends Animal { private d: undefined } type EventHandler<E extends Animal> = (event: E ) => void 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 泛型变量用于确定 then 方法时接收的第一个回调函数的参数类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 属性都是指定了传参格式,如果想不指定,而是想通过传入参数的类型去推导实际类型,这就要用到泛型。
1 2 3 4 5 6 7 8 const TestB = ({ name, name2 }: { name: string; name2?: string } ) => { return ( <div className ="test-b" > TestB--{name} {name2} </div > ) }
如果需要外部传入参数类型,只需 ->
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Props<T> = { name : T name2?: T } const TestC: <T>(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 时代我们会很轻易地甩出一行
由于其可以接受任意值,也就是说我们的函数的入参和返回值都应该可以是任意类型,如果不使用泛型,我们只能重复的进行定义
1 2 3 4 type idBoolean = (arg: boolean ) => boolean type idNumber = (arg: number ) => number type idString = (arg: string ) => string
如果使用泛型,我们只需要
1 2 3 4 5 6 7 8 function id <T >(arg: T ): T { return arg } const id1: <T>(arg: T ) => T = arg => { return arg }
需要被用到很多地方的时候,比如常用的工具泛型 Partial 。 功能是将类型的属性变成可选, 注意这是浅 Partial 。
1 type Partial<T> = { [P in keyof T]?: T[P] }
如果需要深 Partial 我们可以通过泛型递归来实现
1 2 3 4 5 6 type DeepPartial<T> = T extends Function ? T : T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T type PartialedWindow = DeepPartial<Window>