翻译自 react-redux-typescript-guide,做者:Piotrek Witekcss
翻译自 TypeScriptでRedux Thunkを使う,做责:Yusuke Morihtml
参考文章 TypeScript 2.8下的终极React组件模式react
概述:最近在学习 react&TypeScript,发现有许多的组件模式和方法须要去梳理和总结。因此选择一些文章用于沉淀和思考,记录下一些知识点,和你们探讨。git
publish:2019-03-21github
目录:typescript
展现性组件(FunctionComponent
)redux
React.FunctionComponent<P>
or React.FC<P>
。数组
const MyComponent: React.FC<Props> = ...
app
有状态组件(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>
or JSX.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} ... />
复制代码
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 的默认属性,主要有两种方法:
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
复制代码
通用组件 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,主要是in-ts的使用:
// 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)
复制代码