React常见写法
贴出一份Github上star数较多的教程,react的TS写法教程。
React内置的一些类型
react本身已经有完善的TS声明文件了,所以里面有些可以拿来就用,不需要自己定义,这里说一些常见的。
React.FC<Props>,定义函数组件用。React.Component<Props, State>,定义类组件用。React.ReactNode,用来定义各种 react 节点,比如<div />,<MyComp />,this is a just a text,null,0…React.CSSProperties,用来定义内联style的。React.HTMLProps<HTMLXXXELEMENT>,获取内建 html 元素的属性。React.PropsWithChildren,如果需要显示使用 children,使用该类型创建 props,如:React.PropsWithChildren<{a: string}>。React.ReactEventHandler<HTMLXXXELEMENT>,用来描述元素的事件处理函数。可以自行推导出适合的事件函数描述。React.ChangeEventHandler<HTMLXXXELEMENT>,最常见的如 input 的 change 事件函数描述,人为的判定类型。区别于上面的,这里的 event 可用 event.target.value,上面的是 event.currentTarget.value。类似还有MouseEventHandler,MouseEventHandler等。
React的常见写法
组件写法
类写法
interface IProps {name: string;}interface IState {age: number;}// 这里接收2个类型参数,分别限定组件的props和stateclass ClsComp extends React.Component<IProps, IState> {state = {age: 18}}
函数写法
// 函数组件无自己的状态,因此只需要propsconst FcComp: React.FC<{ prop1: string }> = ({ prop1 }) => {return <div>{prop1}</div>}
默认props写法
组件的props我们经常会写一些默认值,省去一些体力活。
类组件形式写法
interface IProps extends defaultProps {age: number;}// 使用typeof获取到类组件的默认参数类型。这里就是{name: string}type defaultProps = typeof DefaultPropsComp.defaultPropsclass DefaultPropsComp extends React.Component<IProps> {static defaultProps = {name: 'jack'}echoProps = () => {// 这里也可以获得TS提示const { age, name } = this.props}render() {return <p>this is how to set default props of class component</p>}}
函数组件形式写法
const defaultProps = {work: 'doctor'}interface IProps extends defaultPropsType {age: number;}type defaultPropsType = typeof defaultPropsfunction DefaultPropsFC(props: IProps) {// 这里可以获得提示const { work, age } = propsreturn <p>{`The ${work} is ${age}`}</p>}// 注意:一定要挂载默认属性,否则调用组件的地方默认值也需要传入DefaultPropsFC.defaultProps = defaultProps
常见的如style等写法
/* 组件的定义 */interface IProps extends DefaultProps {descText: string // 基本的类型style?: React.CSSProperties // 可以在调用组件的地方,获得style的完美提示。additionDom?: React.ReactNode // 接收任何类型的React节点renderReactNode?: () => React.ReactNode // 接收一个以函数方式返回的React节点onChange: React.FormEventHandler<HTMLInputElement> // 优先推荐这种写法,让TS自己推断。下面的方法定义同样可以。// onChange: (event: React.ChangeEvent<HTMLInputElement>) => voidbuttonSize: 'small' | 'middle' | 'big' // 可选参数}type DefaultProps = typeof UsefulReactProp.defaultPropsinterface IState {value: string}export default class UsefulReactProp extends React.Component<IProps, IState> {static defaultProps = {initVal: '输入内容'}// 定义一个dom引用。使用如下定义方式static aDom = React.createRef<HTMLDivElement>()// 定义一个类属性,可以在需要时在赋值使用static classProp?: string// 定义一个类属性,在定义时即复制static classProp2 = 'hello'state = {value: this.props.initVal}defaultOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {const { onChange } = this.propsthis.setState({value: e.target.value})this.classProp = 'hello world'onChange(e)}render() {const { onChange, style, additionDom, renderReactNode } = this.propsconst { value } = this.state// 如下代码提示只有3种上述定义过的值。const btnSize =this.props.buttonSize === 'big'? '100px': this.props.buttonSize === 'small'? '60px': '80px;'return (<div><input type="text" value={value} onChange={this.defaultOnChange} /><p className="desc" style={style}>这是个带样式的提醒</p><div className="additional">{additionDom}</div><div className="by-func-rcnode">{renderReactNode ? renderReactNode() : null}</div><button style={{ width: btnSize }}>按钮尺寸:联合类型提示</button></div>)}}/* 组件的调用 */// 书写可以获得提示。<UsefulReactPropdescText="一个简单的描述"onChange={e => {console.log(e.currentTarget.value)}}style={{ fontSize: 14, fontWeight: 'bold' }}additionDom={<p>this is additional tag</p>}renderReactNode={() => {return <p>这是通过函数返回的ReactNode</p>}}buttonSize="big"/>
React中断言
断言,即认定,确定,以及肯定。当一个值可能是多种类型,不同类型又有不同方法时,我们调用时就会出现方法不存在另外类型上,报错,这个时候就需要断言,认定一个值是我需要的类型,按照认定的类型来使用。React中的断言用 as 语法,而不是 <string>a 语法,因为尖括号语法跟JSX语法有冲突。
type UserA = {name: string,age: number}type UserB = {name: string,sex: string}type IProps = {user: UserA | UserB}class AssertComp extends React.Component<IProps, {}> {render() {// 这里如果不做类型断言,会提示UserA上没有sex属性// const user = this.props.userconst userB = this.props.user as UserBconst userA = this.props.user as UserAreturn <div><p>this is a user: {userA.age}</p><p>this is a user: {userB.sex}</p></div>}}
Context写法
Context是React的一个特性,可以避免在嵌套组件中一层层传递props。常用的Redux库中的 Provider 组件就是利用了Context特性。
这里使用 React.createContext 类型实现。
/*ctx.tsx该文件,定义了我们需要的context对象*/export type Theme = React.CSSPropertiestype Themes = {dark: Themelight: Theme}export const themes: Themes = {dark: {color: 'black',backgroundColor: 'white'},light: {color: 'white',backgroundColor: 'black'}}// 这里定义Context的签名export type ThemeContextProps = { theme: Theme; toggleTheme?: () => void }// createContext<T>需要一个类型,把定义好的Context签名传递进去const ThemeContext = React.createContext<ThemeContextProps>({theme: themes.light})/*themeButton.tsx最终的消费组件(消费上面定义的context对象)*/import ThemeContext from './ctx'// contextType v16.6+支持export default class ToggleThemeButton extends React.Component<{}> {static contextType = ThemeContext// 后面需要通过this.context访问到上下文,所以定义一个类属性// 这里使用!明确告诉TS,该参数一定有。// 这里不能使用?,因为context是必选项context!: React.ContextType<typeof ThemeContext>render() {// 前面context必须这么写,这里才能获取TS提示const { theme, toggleTheme } = this.contextreturn (<button style={theme} onClick={toggleTheme} {...this.props}>button</button>)}}/*provider组件context对象的提供组件,相对于消费组件来说的。*/import ThemeContext, { Theme, themes } from './ctx'import ThemeButton from './themeButton'export default class ThemeProvider extends React.Component<{}, State> {state: State = {theme: themes.light}toggleTheme = () => {this.setState(state => ({theme: state.theme === themes.light ? themes.dark : themes.light}))}render() {const { theme } = this.stateconst { toggleTheme } = thisreturn (<ThemeContext.Provider value={{ theme, toggleTheme }}><ThemeButton /></ThemeContext.Provider>)}}
高阶组件HOC
先理解高阶函数,一个接收函数P作为参数,并且返回一个新的函数N的函数F,这个F即叫做高阶函数。同理,高阶组件,一个接收组件P作为参数,并且返回一个新的组件N的组件F,这个F叫做高阶组件。常见的如React-Redux库中的 connect 方法
这里写一个例子,一个 withState 函数,接收一个没有 count 状态的组件,包装后,返回一个带有 count 状态的组件。
/* withState.tsx */export interface InjectedProps {count: number,onIncrement: () => void}export const withState = <IProp extends InjectedProps>(WrappedComp: React.ComponentType<IProp>) => {// 使用Omit结合keyof,实现排除掉IProp和InjectedProps交集的部分(count, onIncrement)type HocProps = Omit<IProp, keyof InjectedProps> & {// 这里可以做HOC的Props的扩展。initialCount?: number}type HocState = { readonly count: number }return class Hoc extends React.Component<HocProps, HocState> {// 组件的名字,方便调试static displayName = `withState(${WrappedComp.name})`state: HocState = {count: Number(this.props.initialCount) || 0}handleInc = () => {this.setState({count: this.state.count + 1})}render() {const { ...restProps } = this.propsconst { count } = this.statereturn (<WrappedCompcount={count}onIncrement={this.handleInc}// HOC组件,应该透传与HOC无关的Prop给被包裹的组件。这里的写法是因为TS本身的BUG,如果不断言,则类型报错。{...(restProps as IProp)}></WrappedComp>)}}}/*FcCounter.tsx暴露一个函数组件,函数组件没有自己的状态。*/export const FcCounter: React.FC<Props> = props => {const { label, count, onIncrement } = propsconst handleIncrement = () => {onIncrement()}return (<div><span>{label}:{count}</span><button type="button" onClick={handleIncrement}>{`Increment`}</button></div>)}/* 调用组件 */import { FcCounter } from './FcCounter'import { withState } from './withState'const HocCounterWithState = withState(FcCounter)<HocCounterWithState initialCount={10} label="HocCounterWithState" />
通用组件写法
通用组件复用性较高,如 <List> 这种类型的,只是个列表容器,具体的列表每一项自定义。主要思想就是利用泛型。
// 组件定义interface IProps<T> {lists: T[]renderItem: (item: T) => React.ReactNode // 最好都用ReactNode,而不是JSX.Element。}class List<T> extends React.Component<IProps<T>, {}> {render() {const {lists, renderItem} = this.propsreturn (<><ul>{lists.map(renderItem)}</ul></>)}}// 调用组件。注意:这里List传入了一个类型number,限制了泛型T的类型。<List<number> lists={[1, 2, 3]} renderItem={item => <li key={item}>{item}</li>}/>
useRef+useImperativeHandle+forwardRef写法
函数组件没有实例,如何在父组件拿到函数子组件的方法呢?
// 父组件import { useRef } from 'react';import Filter from './components/Filter';type FilterHandle = React.ElementRef<typeof Filter> // *function Parent() {const FilterRef = useRef<FilterHandle>(null) // *return <Filter ref={FilterRef} /> // *}// 子组件import { useImperativeHandle, forwardRef } from 'react';import {Form} from 'antd'// 自定义返回的函数组件方法type FilterHandle = {getFieldsValue: () => {[key: string]: any}}type FilterProps = {}// TS定义下是一个ForwardRef组件const Filter: React.ForwardRefRenderFunction<FilterHandle, FilterProps> = (props, ref) => {const [form] = Form.useForm();// 生成返回给父组件的自定义实例值useImperativeHandle(ref, () => ({getFieldsValue() {// 可以暴露出form的一些方法return form.getFieldsValue()}}));return (<Form className={styles.wrap} form={form}><Row gutter={12}><Col span={6}><Form.Item label="账号名称" name="name"><Input placeholder="请输入账号名称" /></Form.Item></Col><Col span={18} style={{ textAlign: 'right' }}><Form.Item><Space size={16}><Button>重置</Button><Button type="primary">查询</Button></Space></Form.Item></Col></Row></Form>);}export default forwardRef(Filter);
