翻译自 react-redux-typescript-guide,作者:Piotrek Witek
翻译自 TypeScriptでRedux Thunkを使う,作责:Yusuke Mori
概述:最近在学习 react&TypeScript,发现有许多的组件模式和方法需要去梳理和总结。所以选择一些文章用于沉淀和思考,记录下一些知识点,和大家探讨。
publish:2019-03-21
目录:
- 简介,环境配置(create-react-app)
- React - 关键类型(react-redux-typescript-guide )
- React - 组件模式(react-redux-typescript-guide & TypeScript 2.8下的终极React组件模式)
- Redux - 使用以及 Redux Thunk 使用(TypeScriptでRedux Thunkを使う)
- 总结
React - 关键类型
-
展示性组件(
FunctionComponent
)React.FunctionComponent<P>
orReact.FC<P>
。const MyComponent: React.FC<Props> = ...
-
有状态组件(
ClassComponent
)React.Component<P, S>
class MyComponent extends React.Component<Props, State> { ...
-
组件Props
React.ComponentProps<typeof Component>
获取组件(适用于ClassComponent、FunctionComponent)的Props的类型
type MyComponentProps = React.ComponentProps<typeof MyComponent>;
-
React.FC | React.Component的联合类型
React.ComponentType<P>
const withState = <P extends WrappedComponentProps>( WrappedComponent: React.ComponentType<P>, ) => { ... 复制代码
-
React 要素
React.ReactElement<P>
orJSX.Element
表示React元素概念的类型 - DOM组件(例如
)或用户定义的复合组件()const elementOnly: React.ReactElement = <div /> || <MyComponent />;
-
React Node
React.ReactNode
表示任何类型的React节点(基本上是ReactElement(包括Fragments和Portals)+ 原始JS类型 的合集)
const elementOrPrimitive: React.ReactNode = 'string' || 0 || false || null || undefined || <div /> || <MyComponent />; const Component = ({ children: React.ReactNode }) => ... 复制代码
-
React CSS属性
React.CSSProperties
代表着Style Object在 JSX 文件中(通常用于 css-in-js)
const styles: React.CSSProperties = { flexDirection: 'row', ... const element = <div style={styles} ... 复制代码
-
通用的 React Event Handler
React.ReactEventHandler<HTMLElement>
const handleChange: React.ReactEventHandler<HTMLInputElement> = (ev) => { ... } <input onChange={handleChange} ... /> 复制代码
-
特殊的 React Event Handler
React.MouseEvent<E>
|React.KeyboardEvent<E>
|React.TouchEvent<E>
const handleChange = (ev: React.MouseEvent<HTMLDivElement>) => { ... } <div onMouseMove={handleChange} ... /> 复制代码
React 组件模式
-
Function Components - FC 纯函数组件(无状态)
顾名思义,纯函数组件本身不具备
State
,所以没有状态,一切通过Props
import React, { FC, ReactElement, MouseEvent } from 'react' type Props = { label: string, children: ReactElement, onClick?: (e: MouseEvent<HTMLButtonElement>) => void } const FunctionComponent: FC<Props> = ({ label, children, onClick }: Props) => { return ( <div> <span> {label}: </span> <button type="button" onClick={onClick}> {children} </button> </div> ) } export default FunctionComponent 复制代码
扩展属性(spread attributes)
利用
...
对剩余属性进行处理import React, { FC, ReactElement, MouseEvent, CSSProperties } from 'react' type Props = { label: string, children: ReactElement, className?: string, style?: CSSProperties, onClick?: (e: MouseEvent<HTMLButtonElement>) => void, } const FunctionComponent: FC<Props> = ({ label, children, onClick, ...resetProps }: Props) => { return ( <div {...resetProps}> <span>{label}:</span> <button type="button" onClick={onClick}> {children} </button> </div> ) } export default FunctionComponent 复制代码
默认属性
如果,需要默认属性,可以通过默认参数值来处理
import React, { FC, ReactElement, MouseEvent } from 'react' type Props = { label?: string, children: ReactElement, } const FunctionComponent: FC<Props> = ({ label = 'Hello', children }: Props) => { return ( <div> <span> {label}: </span> <button type="button"> {children} </button> </div> ) } export default FunctionComponent 复制代码
-
Class Components
相对于FC,多了
state
,采用如下形式来定义Class Component这一部分的写法,与TypeScript 2.8下的终极React组件模式相同,觉得结构很清晰,复用。
import React, { Component } from 'react'; type Props = { label: string } const initialState = { count: 0 } type State = Readonly<typeof initialState> class ClassCounter extends Component<Props, State> { readonly state: State = initialState private handleIncrement = () => this.setState(Increment) render() { const { handleIncrement } = this; const { label } = this.props; const { count } = this.state; return ( <div> <span> {label}: {count} </span> <button type="button" onClick={handleIncrement}> {`Increment`} </button> </div> ) } } export const Increment = (preState: State) => ({ count: preState.count + 1 }) export default ClassCounter 复制代码
默认属性
处理 Class Component 的默认属性,主要有两种方法:
- 一是定义高阶组件,例如TypeScript 2.8下的终极React组件模式中,利用
withDefaultProps
来定义默认属性,涉及组件的属性的类型转换; - 二是利用
static props
以及componentWillReceiveProps
,处理默认属性。
具体业务中,视情况而定,第一中可以查看相关文章,这里介绍第二种
import React, { Component } from 'react'; type Props = { label: string, initialCount: number } type State = { count: number; } class ClassCounter extends Component<Props, State> { static defaultProps = { initialCount: 1, } // 依据 defaultProps 对 state 进行处理 readonly state: State = { count: this.props.initialCount, } private handleIncrement = () => this.setState(Increment) // 响应 defaultProps 的变化 componentWillReceiveProps({ initialCount }: Props) { if (initialCount != null && initialCount !== this.props.initialCount) { this.setState({ count: initialCount }) } } render() { const { handleIncrement } = this; const { label } = this.props; const { count } = this.state; return ( <div> <span> {label}: {count} </span> <button type="button" onClick={handleIncrement}> {`Increment`} </button> </div> ) } } export const Increment = (preState: State) => ({ count: preState.count + 1 }) export default ClassCounter 复制代码
- 一是定义高阶组件,例如TypeScript 2.8下的终极React组件模式中,利用
-
通用组件 Generic Components
- 复用共有的逻辑创建组件
- 常用于通用列表
import React, { Component, ReactElement } from 'react' interface GenericListProps<T> { items: T[], itemRenderer: (item: T, i: number) => ReactElement, } class GenericList<T> extends Component<GenericListProps<T>, {}> { render() { const { items, itemRenderer } = this.props return <div>{items.map(itemRenderer)}</div> } } export default GenericList 复制代码
-
Render Callback & Render Props
-
Render Callback,也被称为函数子组件,就是将 children 替换为 () => children;
-
Render Props,就是将 () => component 作为 Props 传递下去。
import React, { Component, ReactElement } from 'react'; type Props = { PropRender?: () => ReactElement, children?: () => ReactElement } class PropRender extends Component<Props, {}> { render() { const { props: { children, PropRender } }: { props: Props } = this; return ( <div> { PropRender && PropRender() } { children && children() } </div> ) } } export default PropRender // 应用 <PropsRender PropRender={() => (<p>Prop Render</p>)} > { () => (<p>Child Render</p>) } </PropsRender> 复制代码
-
-
HOC(Higher-Order Components)
简单理解为,接受React组件作为输入,输出一个新的React组件的组件的工厂函数。
import * as React from 'react' interface InjectedProps { label: string } export const withState = <BaseProps extends InjectedProps>( BaseComponent: React.ComponentType<BaseProps> ) => { type HocProps = BaseProps & InjectedProps & { initialCount?: number } type HocState = { readonly count: number } return class Hoc extends React.Component<HocProps, HocState> { // 方便 debugging in React-Dev-Tools static displayName = `withState(${BaseComponent.name})`; // 关联原始的 wrapped component static readonly WrappedComponent = BaseComponent; readonly state: HocState = { count: Number(this.props.initialCount) || 0, } handleIncrement = () => { this.setState({ count: this.state.count + 1 }) } render() { const { ...restProps } = this.props as any const { count } = this.state return ( <> {count} <BaseComponent onClick={this.handleIncrement} {...restProps} /> </> ) } } } 复制代码
Redux - 使用以及 Redux Thunk 使用
以如下形式来介绍Redux,主要是in-ts的使用:
- (prestate, action) => state;
- 使用Redux Thunk 来出来异步操作。
// store.js
type DataType = {
counter: number
}
const DataState: DataType = {
counter: 0
}
type RootState = {
Data: DataType
}
export default RootState
复制代码
// action.js
import { Action, AnyAction } from 'redux'
import { ThunkAction, ThunkDispatch } from 'redux-thunk'
import RootState from '../store/index'
type IncrementPayload = {
value: number
}
interface IncrementAction extends Action {
type: 'INCREMENT',
payload: IncrementPayload
}
export const Increment = ({ value }: IncrementPayload): IncrementAction => {
const payload = { value }
return {
type: 'INCREMENT',
payload
}
}
export type DecrementPayload = {
value: number;
};
export interface DecrementAction extends Action {
type: 'DECREMENT';
payload: DecrementPayload;
}
export type RootAction = IncrementAction & DecrementAction;
export const asyncIncrement = (
payload: IncrementPayload
): ThunkAction<Promise<void>, RootState, void, AnyAction> => {
return async (dispatch: ThunkDispatch<RootState, void, AnyAction>): Promise<void> => {
return new Promise<void>((resolve) => {
console.log('Login in progress')
setTimeout(() => {
dispatch(Increment(payload))
setTimeout(() => {
resolve()
}, 1000)
}, 3000)
})
}
}
复制代码
// reducer.js
import { DataState, DataType } from '../store/Data'
import { RootAction } from '../actions/'
export default function (state: DataType = DataState, { type, payload }: RootAction): DataType {
switch(type) {
case 'INCREMENT':
return {
...state,
counter: state.counter + payload.value,
};
default:
return state;
}
}
复制代码
// Hearder.js
import React, { Component, ReactNode } from 'react'
import RootState from '../store/index'
import { Dispatch, AnyAction } from 'redux'
import { ThunkDispatch } from 'redux-thunk'
import { connect } from 'react-redux'
import { Increment, asyncIncrement } from '../actions/'
const initialState = {
name: 'string'
}
type StateToPropsType = Readonly<{
counter: number
}>
type DispatchToPropsType = Readonly<{
handleAdd: () => void,
handleDec: () => void
}>
type StateType = Readonly<typeof initialState>
type PropsType = {
children?: ReactNode
}
type ComponentProps = StateToPropsType & DispatchToPropsType & PropsType
class Header extends Component<ComponentProps, StateType> {
readonly state: StateType = initialState;
render() {
const { props: { handleAdd, handleDec, counter }, state: { name } } = this
return (
<div>
计数:{counter}
<button onClick={handleAdd}>+</button>
<button onClick={handleDec}>-</button>
</div>
)
}
private handleClick = () => this.setState(sayHello);
}
const sayHello = (prevState: StateType) => ({
name: prevState.name + 'Hello world',
})
const mapStateToProps = (state: RootState, props: PropsType): StateToPropsType => {
return {
counter: state.Data.counter
}
}
const mapDispatchToProps = (dispatch: ThunkDispatch<RootState, void, AnyAction>): DispatchToPropsType => {
return {
handleAdd: () => {
dispatch(Increment({ value: 2 }))
},
handleDec: async () => {
dispatch(asyncIncrement({ value: 10 }))
}
}
}
export default connect<StateToPropsType, DispatchToPropsType, PropsType, RootState>(mapStateToProps, mapDispatchToProps)(Header)
复制代码