TypeScript 3.0 + React + Redux 最佳實踐

首先聲明, 這篇文章是想說明一下最新版本的 TypeScript(3.0) 的新特性帶來的極大的 React 開發體驗提高. 而不是如何利用 TypeScript 開發 React 應用.react

這個特性就是對defaultProps的支持, 在 TypeScript 的 Wiki 中有說明, 具體能夠參考這裏: Support for defaultProps in JSX.git


在3.0版本以前, 咱們在開發 React 應用, 尤爲是在配合 redux 一類 HOC 庫的時候, 常常用到諸如 connect(TodoList), withRouter(TodoList) 之類的封裝. 而這些函數其實均可以用裝飾器的方式來調用, 好比:github

export interface TodoListProps extends RouteComponentProps<{}> {
  todos: Todo[];
}

@withRouter
@connect(mapStateToProps)
export class TodoList extends PureComponent<TodoListProps, {}> {
  render() {
    return null
  }
}

可是在結合 TypeScript 的時候, 這中間有個問題, 就是裝飾器會自動注入一些 props 給組件, 這一部分屬性不須要外部傳入, 所以是可選的, 在strictNullCheck屬性開啓的時候, 就會出現屬性衝突. 由於 TS 給不容許裝飾器修改被裝飾的對象的類型, 所以在 props 定義中爲required屬性依然爲required.redux

好比對於上面的例子, 在實例化TodoList這個組件的時候, 必須要傳入全部的TodoListProps所定義的屬性, 不然會有TS2322錯誤.react-router

而在 TS 3.0 中, 能夠聲明defaultProps屬性來代表某些屬性對外部組件而言是可選的. 好比:app

@withRouter
@connect((state) => ({ todos: state.todos })
export class TodoList extends PureComponent<TodoListProps, {}> {
  static defaultProps: TodoListProps
  render() {
    return null
  }
}

這裏的static defaultProps: TodoListProps代表, 全部的TodoList的 props TodoListProps 對外部組件都是可選的. 這就意味着外部組件能夠什麼屬性都不用傳也不會有錯誤. 同時內部而言全部的屬性都是NotNullable.函數


綜上, 一般狀況下, 咱們的一個組件會有一部分屬性由裝飾器注入, 而另外一部分則須要外部實例化時傳入, 所以, 能夠將一個組件的 props 接口聲明成兩層結構, 第一層爲由裝飾器注入的部分, 第二層則爲完整的屬性接口, 而後將defaultProps設置成爲第一層接口便可. 好比:ui

export interface TodoListInnerProps extends RouteComponentProps<{}> {
  todos: Todo[];
}

export interface TodoListProps extends TodoListInnerProps {
  className?: string;
  onLoad?(): void;
}

@withRouter
@connect((state) => ({ todos: state.todos })
export class TodoList extends PureComponent<TodoListProps, {}> {
  static defaultProps: TodoListInnerProps
  render() {
    return null
  }
}

須要注意的是:spa

  1. TypeScript 要 3.0.1以上
  2. @types/react 要最新版
  3. withRouter, connect 等函數在 @types中, 簽名有問題, 須要手動修改一下:code

    import { ComponentClass } from 'react'
    import {
      connect as nativeConnect,
      MapDispatchToPropsParam,
      MapStateToPropsParam
    } from 'react-redux'
    import { withRouter as nativeWithRouter } from 'react-router'
    
    export type ComponentDecorator<P = any> = <T extends ComponentClass<P>>(WrappedComponent: T) => T
    
    export const connect: <P, S = Todo>(
      mapState: MapStateToPropsParam<Partial<P>, P, S>,
      mapDispatch?: MapDispatchToPropsParam<Partial<P>, P>
    ) => ComponentDecorator = nativeConnect as any
    
    export const withRouter: ComponentDecorator = nativeWithRouter as any
相關文章
相關標籤/搜索