目的,更多React类型,在项目中使用有更强的约束性,利于后期维护。
引用React
React17(也就是create-react-app4.0开始默认打开plugin-transform-react-jsx,或者plugin支持不用手动引入React’)之后不用引入React✅
import * as React from 'react'
import * as ReactDOM from 'react-dom'
React.FC
React.FunctionComponent=React.FC,显式地定义了返回类型,其他方式是隐式推导的
displayName、propTypes、defaultProps提供了类型检查和自动补全
为children提供了隐式的类型(ReactElement | null)
如果出现类型不兼容问题:还有一个替代品
PropsWithChildren
,或者显示声明children?: React.ReactNode
type AppProps = {message: string}✅//用type,children会自动有隐式类型
//替代品,此时不写下面的React.FC,自动设置 children 类型为 ReactNode
//type AppProps = React.PropsWithChildren<{ message: string }>
//这个AppProps默认为空对象{}
const App: React.FC<AppProps> = ({ message, children }) => (
<div>
{message}
{children}
</div>
)
ReactElement和ReactNode和JSX.Element
React.component<P,S>
,React.PureComponent<P,S,SS>
,这个SS
是getSnapshotBeforeUpdate
的返回值
类组件的
render
方法返回ReactNode
,而函数组件返回ReactElement
因为ReactNode可以是undefined等更多类型
JSX.Element 是一个 ReactElement,props 和 type的泛型类型是 any。
ReactElement 是一个具有type和props的对象。
JSX.Element ≈ ReactElement ⊂ ReactNode
常用Hooks类型的一些技巧
总结:
useState自动推导,或者传入泛型
useRef自动推导,或者传入泛型,如(<HTMLInputElement | null>)
useEffect固定,返回值只能是undefined和()=>{}
useMemo 和 useCallback 自动推导返回值 从它们返回的值中推断出它们的类型 也可传入泛型多此一举
useCallback 参数必须指定类型,否则ts不会报错,默认指定为 any
使用推导类型作为接口/类型:
const [user] = React.useState({ name: 'sj', age: 32 })//自动推导出类型
const showUser = React.useCallback((obj: typeof user) => {//这里typeof使用推导出的类型✅
return `My name is ${obj.name}, My age is ${obj.age}`
}, [])
初始状态为null时候,需要显示声明类型:
type User = {name:string}
const [user, setUser] = React.useState<User | null>(null)
useRef的类型:
const ref1 = React.useRef<HTMLInputElement>(null)//ref1.current 是只读的
const ref2 = React.useRef<HTMLInputElement | null>(null)//ref2.current 是可变的✅
//可是这样声明貌似不能使用ref来保存setTimeout❌
const ref1 = React.useRef<any>(null)//暂时这样吧
//一个场景focus
const onButtonClick = () => {
ref2.current?.focus()//可以使用可选链
}
//其他
// 存储div dom
const divRef = React.useRef<HTMLDivElement | null>(null);
// 存储button dom
const buttonRef = React.useRef<HTMLButtonElement | null>(null);
// 存储a dom
const linkRef = React.useRef<HTMLLinkElement | null>(null);
useMemo和useCallback:
// 自动推断 (value: number) => number
const multiply = React.useCallback((value: number) => value * multiplier, [
multiplier,
])
//泛型
//同时也支持传入泛型, useMemo 的泛型指定了返回值类型,useCallback 的泛型指定了参数类型
const handleChange = React.useCallback<
React.ChangeEventHandler<HTMLInputElement>
>(evt => {
console.log(evt.target.value)
}, [])
自定义Hooks:
//自定义 Hook 的返回值如果是数组类型,TS 会自动推导为 Union 类型,解决:const断言
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 //这个hook把isLoading和一个函数返回出去✅
}
//大量的自定义 Hook 需要处理,这里有一个方便的工具方法可以处理 tuple 返回值
function tuplify<T extends any[]>(...elements: T) {
return elements
}
...
//[boolean, typeof load]
把上面例子 return [isLoading, load] as const 改为=> return tuplify(isLoading, load)
defaultProps
推荐方式:使用默认参数值来代替默认属性
看了字节的那个例子,…有点麻烦,还要交叉类型,表示再也不会用defaultProps了
注意的是有这样一个类型React.ComponentProps
const TestComponent = (props: React.ComponentProps<typeof Greet>) => {
return <h1 />
}
Types or Interfaces
巧用type:
let user = { name: 'Lucy', age: 20 }
type User = typeof user;✅//user{name:string,age:number}
选择:
- 在定义公共 API 时(比如编辑一个库)使用 interface,这样可以方便使用者继承接口
- 在定义组件属性(Props)和状态(State)时,建议使用 type,因为 type的约束性更强
区别:
-
type 类型不能二次编辑,而 interface 可以随时扩展
type Animal = { name: string } // type类型不支持属性扩展 // Error: Duplicate identifier 'Animal' type Animal = { color: string } //但interface能
-
type 可以定义更多类型,interface只能定义对象
注释:
/**
* @param color color
*/
Props
常用:
type AppProps = {
status: 'waiting' | 'success'//字面量加联合类型
obj: {//列出对象的全部属性,这里也可以写个interface
id: string
title: string
}
/** array of objects! (common) */
objArr: {✅//曾几何时,一度不知道这样写== 数组里面多个对象
id: string
title: string
}[]
/** 携带点击事件的函数 */
onClick(event: React.MouseEvent<HTMLButtonElement>): void
//可选
onClick?: () => void
}
React属性:
export declare interface AppBetterProps {
children: React.ReactNode // 一般情况下推荐使用,支持所有类型 Great
functionChildren: (name: string) => React.ReactNode
style?: 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 type
style?: React.CSSProperties // 传递style对象
onChange?: React.FormEventHandler<HTMLInputElement> // 表单事件, 泛型参数是event.target的类型
}
Forms and Events
onChange:
import * as React from 'react'
//第一种·
type changeFn = (e: React.FormEvent<HTMLInputElement>) => void//这样定义下面onchange的参数和返回值
const App: React.FC = () => {
const [state, setState] = React.useState('')
const onChange: changeFn = e => {
setState(e.currentTarget.value)
}
//第二种,强制使用 @types / react 提供的委托类型
const onChange: React.ChangeEventHandler<HTMLInputElement> = e => {✅
setState(e.currentTarget.value)
}
return (
<div>
<input type="text" value={state} onChange={onChange} />
</div>
)
}
onSubmit:
const onSubmit = (e: React.SyntheticEvent) => {✅
e.preventDefault()
const target = e.target as typeof e.target & {
password: { value: string }
} // 类型扩展---表示看不懂---
const password = target.password.value
}
Operators(运算符)
常用的操作符,常用于类型判断 ---- 只会用常用的 = =
- typeof and instanceof: 用于类型区分
- keyof: 获取object的key
- O[K]: 属性查找
- [K in O]: 映射类型
- + or - or readonly or ?: 加法、减法、只读和可选修饰符
- x ? Y : Z: 用于泛型类型、类型别名、函数参数类型的条件类型
- !: 可空类型的空断言
- as: 类型断言
- is: 函数返回类型的类型保护
Tips
😥使用查找类型访问组件属性类型
这个场景我理解是:子组件必须接受一个name参数,父组件要把它的props传入子组件,那么父组件要拿到子组件这个必要的类型来定义自己的Iprops
总的来说就是
React.ComponentProps<typeof Counter>
拿到子组件必要类型,再用交叉类型&
,定义自己的Iprops
通过查找类型减少 type 的非必要导出,如果需要提供复杂的 type,应当提取到作为公共 API 导出的文件中。
现在我们有一个 Counter 组件,需要 name 这个必传参数:
// counter.tsx
import * as React from 'react'
export type Props = {//app要用,所以export😎
name: string
}
const Counter: React.FC<Props> = props => {
return <></>
}
export default Counter
引用它的组件中我们有两种方式获取到 Counter 的参数类型
第一种是通过 typeof 操作符(推荐)
import Counter from './d-tips1'
type PropsNew = React.ComponentProps<typeof Counter> & {✅
age: number//这是app组件要的
}
const App: React.FC<PropsNew> = props => {
return <Counter {...props} />
}
export default App
事件处理
通过 interface 对 event 对象进行类型声明编写的话十分浪费时间, React 的声明文件提供了 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 的类型别名来定义事件处理函数的类型
😭信息量直接拉满
//bivarianceHack 为事件处理函数的类型定义,函数接收一个 event 对象,并且其类型为接收到的泛型变量 E 的类型, 返回值为 void
//关于为何是用bivarianceHack而不是(event: E): void,这与strictfunctionTypes选项下的功能兼容性有关。(event: E): void,如果该参数是派生类型,则不能将其传递给参数是基类的函数。
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>
>
Promise 类型
在做异步操作时我们经常使用 async 函数,函数调用时会 return 一个 Promise 对象,可以使用 then 方法添加回调函数。Promise 是一个泛型类型,T 泛型变量用于确定 then 方法时接收的第一个回调函数的参数类型
。
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)
})
泛型参数的组件
这个是通过传入的类型泛型 判断props类型
下面这个组件的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>(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>
)
}
什么时候使用泛型
需要作用到很多类型的时候,举个🌰
const id = arg => arg//bad
//bad 重复定义了
type idBoolean = (arg: boolean) => boolean
type idNumber = (arg: number) => number
type idString = (arg: string) => string
// ...
//good✅✅✅✅✅✅
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>
TS内置类型
interface Old {
name: string
age?: number
gender: string
}
interface Old2 extends Old {
six: number
}
const a: Old2 = {
six: 1,
name:'1',
gender:'1'
}
type New0 = Partial<Old>
type New1 = Omit<Old, 'name'>
type New2 = Pick<Old, 'age'>
type New3 = Required<Old>
type New4 = Readonly<Old>
type A = number | string | boolean
type B = number | boolean
type New5 = Exclude<A, B>//排除
type New6 = Extract<A, B>//交集
type New7 = Record<keyof Old, string>//全变为string
总结一下可能常用的
const ref2 = React.useRef<HTMLInputElement | null>(null)
return [isLoading, load] as const //自定义hooks
type AppProps = {
status: 'waiting' | 'success'//字面量string类型
objArr: {//数组包对象类型
id: string
title: string
}[]
}
children: React.ReactNod//比较全面
React.CSSProperties // 传递style对象
type changeFn = (e: React.FormEvent<HTMLInputElement>) => void
const onSubmit = (e: React.SyntheticEvent) => {}
type Iobj = {//定义对象
[type: string]: string
}
//使用泛型==
const TestC: <T>(props: Props<T>) => React.ReactElement = ({ name, name2 }) => { return }
<TestC<string> name="123" />
参考