React & Redux in TypeScript - 静态类型指南

翻译自 react-redux-typescript-guide,作者:Piotrek Witek

翻译自 TypeScriptでRedux Thunkを使う,作责:Yusuke Mori

参考文章 TypeScript 2.8下的终极React组件模式

概述:最近在学习 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

    or React.FC

    const MyComponent: React.FC = ...

  • 有状态组件(ClassComponent

    React.Component

    class MyComponent extends React.Component { ...

  • 组件Props

    React.ComponentProps

    获取组件(适用于ClassComponent、FunctionComponent)的Props的类型

    type MyComponentProps = React.ComponentProps;

  • React.FC | React.Component的联合类型

    React.ComponentType

    const withState = 

    ( WrappedComponent: React.ComponentType

    , ) => { ... 复制代码

  • React 要素

    React.ReactElement

    or JSX.Element

    表示React元素概念的类型 - DOM组件(例如

    )或用户定义的复合组件()

    const elementOnly: React.ReactElement =

    || ;

  • 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 = 
    复制代码
  • 通用的 React Event Handler

    React.ReactEventHandler

    const handleChange: React.ReactEventHandler = (ev) => { ... } 
    
    
    复制代码
  • 特殊的 React Event Handler

    React.MouseEvent | React.KeyboardEvent | React.TouchEvent

    const handleChange = (ev: React.MouseEvent) => { ... }
    
    
    复制代码

React 组件模式

  • Function Components - FC 纯函数组件(无状态)

    顾名思义,纯函数组件本身不具备 State,所以没有状态,一切通过 Props

    import React, { FC, ReactElement, MouseEvent  } from 'react'
    
    type Props = {
        label: string,
        children: ReactElement,
        onClick?: (e: MouseEvent) => void
    }
    
    const FunctionComponent: FC = ({ label, children, onClick }: Props) => {
        return (
            
    {label}:
    ) } 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) => void,
    }
    
    const FunctionComponent: FC = ({ label, children, onClick, ...resetProps }: Props) => {
        return (
            
    {label}:
    ) } export default FunctionComponent 复制代码

    默认属性

    如果,需要默认属性,可以通过默认参数值来处理

    import React, { FC, ReactElement, MouseEvent  } from 'react'
    
    type Props = {
        label?: string,
        children: ReactElement,
    }
    
    const FunctionComponent: FC = ({ label = 'Hello', children }: Props) => {
        return (
            
    {label}:
    ) } 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
    
    class ClassCounter extends Component {
        readonly state: State = initialState
    
        private handleIncrement = () => this.setState(Increment)
    
        render() {
            const { handleIncrement } = this;
            const { label } = this.props;
            const { count } = this.state;
    
            return (
                
    {label}: {count}
    ) } } 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 {
        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 (
                
    {label}: {count}
    ) } } export const Increment = (preState: State) => ({ count: preState.count + 1 }) export default ClassCounter 复制代码
  • 通用组件 Generic Components

    • 复用共有的逻辑创建组件
    • 常用于通用列表
    import React, { Component, ReactElement } from 'react'
    
    interface GenericListProps {
        items: T[],
        itemRenderer: (item: T, i: number) => ReactElement,
    }
    
    class GenericList extends Component, {}> {
        render() {
            const { items, itemRenderer } = this.props
    
            return 
    {items.map(itemRenderer)}
    } } 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 {
      
          render() {
              const { props: { children, PropRender } }: { props: Props } = this;
      
              return (
                  
      { PropRender && PropRender() } { children && children() }
      ) } } export default PropRender // 应用 (

      Prop Render

      )} > { () => (

      Child Render

      ) }
      复制代码
  • HOC(Higher-Order Components)

    简单理解为,接受React组件作为输入,输出一个新的React组件的组件的工厂函数。

    import * as React from 'react'
    
    interface InjectedProps {
        label: string
    }
    
    export const withState = (
        BaseComponent: React.ComponentType
    ) => {
        type HocProps = BaseProps & InjectedProps & {
            initialCount?: number
        }
        type HocState = {
            readonly count: number
        }
    
        return class Hoc extends React.Component {
            // 方便 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}
                        
                    
                )
            }
        }
    }
    复制代码

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: ThunkDispatchvoid, 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
type PropsType = {
    children?: ReactNode
}
type ComponentProps = StateToPropsType & DispatchToPropsType & PropsType

class Header extends Component {
    readonly state: StateType = initialState;

    render() {
        const { props: { handleAdd, handleDec, counter }, state: { name } } = this

        return (
            
计数:{counter}
) } 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): DispatchToPropsType => { return { handleAdd: () => { dispatch(Increment({ value: 2 })) }, handleDec: async () => { dispatch(asyncIncrement({ value: 10 })) } } } export default connect(mapStateToProps, mapDispatchToProps)(Header) 复制代码

转载于:https://juejin.im/post/5c984e6651882510d82b7986

你可能感兴趣的:(React & Redux in TypeScript - 静态类型指南)