使用 Typescript 踩 react-redux 的坑

背景

最近在看antd源碼,也在自學一些typescript的語法以及使用,而後準備帶在學校的師弟作一個音樂播放器做爲typescript的練習踩踩坑;css

而後居然沒想到在項目剛開始就深陷大坑--搭建react-redux項目流程,一下內容就是記錄我是怎樣從這個深坑中爬出來的過程。node

開始?根本開始不起來

從項目建立以後就掉坑裏了。。。react

建立項目

建立項目的流程很簡單,可有參考我以前寫的這篇文章git

從mapStateToProps&mapDispatchToProps開始爬坑

剛開始還不是很熟悉typescript,因此我最開始的reducer, action, constant,文件都是以.js結尾,只有組件的代碼是以.tsx結尾的,而後就開始按照流程,將store中的state進行mapStateToProps以及actions進行mapDispatchToProps,誰想到這居然是噩夢的開始,最開始我是這樣寫的代碼:github

import { bindActionCreators } from 'redux';
  function mapStateToProps(state: {}) {
    return {
      player: state.PlayerStore,
    };
  }

  function mapDispatchToProps(dispatch: Function) {
    return {
      playerActions: bindActionCreators(actions, dispatch)
    };
  }複製代碼

而後就開始報錯了。。。typescript

1
1

幾經折騰以後我又將代碼改爲了這樣,才得以過關redux

import { bindActionCreators, Dispatch } from 'redux';
  function mapStateToProps(state: { PlayerStore: object }) {
    return {
      player: state.PlayerStore,
    };
  }

  function mapDispatchToProps(dispatch: Dispatch<{}>) {
    return {
      playerActions: bindActionCreators<{}>(actions, dispatch)
    };
  }複製代碼

爬坑react組件斷言類型

interface PlayerPropsClass {
    player: PlayerStateTypes;
    playerActions: actions.PlayerActionsTypes;
  }
  class Player extends React.Component<PlayerPropsClass, {}> {
    constructor(props: object) {
      super(props as PlayerPropsClass);
      this.state = {};
    }

    addOne = (num: number) => this.props.playerActions.count(num);

    subtractOne = (num: number) => this.props.playerActions.subtract(num);

    render() {
      const { countNumber } = this.props.player.toJS();
      return (
        <div className="player"> <span>{countNumber}</span> <button onClick={() => this.addOne(countNumber as number)}>點擊+1</button> <button onClick={() => this.subtractOne(countNumber as number)}>點擊-1</button> </div>
      );
    }
  }

  export default connect(mapStateToProps, mapDispatchToProps)(Player as any);複製代碼

這裏的(Player as any)報錯提示bash

[tslint] Type declaration of 'any' loses type-safety. Consider replacing it with a more precise type, the empty type ('{}'), or suppress this occurrence. (no-any)複製代碼

而後我改爲antd

export default connect(mapStateToProps, mapDispatchToProps)(Player as {});複製代碼

仍然報錯less

[ts]
  類型「{}」的參數不能賦給類型「ComponentType<{ player: object; } & { playerActions: {}; }>」的參數。
  不能將類型「{}」分配給類型「StatelessComponent<{ player: object; } & { playerActions: {}; }>」。
  類型「{}」提供的內容與簽名「(props: { player: object; } & { playerActions: {}; } & { children?: ReactNode; }, context?: any): ReactElement<any> | null」不匹配。複製代碼

哇心態爆炸,都不知道怎麼寫這個斷言。。。而後我想起了antd的一個例子,寫的是React.ReactElement,而後偶然間在網上找到了說的是在node_modules中有一個@type文件夾就是typescript對於node包中的相應的類型匹配,因而我就按照這個路徑node_modules/@types/react/index.d.ts,終於找到了React.ComponentType<T>,這個文件裏面還有不少的類型,想要了解的能夠本身去看看

export default connect(mapStateToProps, mapDispatchToProps)(Player as React.ComponentType<PlayerPropsClass>);複製代碼

爬坑action和reducer

有了以上的經驗,再加上在這裏看了一些代碼,我決定把actions和reducer也改成ts結尾的文件;

  • src/reducers/index.ts

    import { combineReducers, createStore } from 'redux';
      import PlayerStore from '../containers/Player/reducer';
    
      // 這個是用來使用到mapStateToProps函數的地方給state進行類型檢測的
      export interface AppStoreType {
        PlayerStore: object;
      }
    
      const rootReducer = combineReducers({
        PlayerStore,
      });
    
      export default () => {
        return createStore(rootReducer);
      };複製代碼
  • containers/Demo/reducer.ts

    import * as PlayerTypes from './constant';
      import { fromJS } from 'immutable';
      // 因爲使用到immutable,state上面就會使用到immutable的函數,因此須要將其也作成類型檢測
      import { ImmutableFuncType } from '../../constant';
    
      interface ActionType {
        type: string;
        countNumber?: number;
      }
    
      interface StoreType {
        countNumber: number;
      }
      // 把當前容器的state組,而後拋出提供使用
      export type PlayerStateTypes = StoreType & ImmutableFuncType;
    
      const PlayerStore: PlayerStateTypes = fromJS({
        countNumber: 0,
      });
    
      export default (state = PlayerStore, action: ActionType) => {
        switch (action.type) {
          case PlayerTypes.COUNT:
            return state.update('countNumber', () => fromJS(action.countNumber));
          default:
            return state;
        }
      };複製代碼
  • containers/Demo/action.ts

    import * as PlayerTypes from './constant';
    
      export const count = (num: number) => ({
        type: PlayerTypes.COUNT,
        countNumber: num + 1,
      });
    
      export const subtract = (num: number) => ({
        type: PlayerTypes.COUNT,
        countNumber: num - 1,
      });
      // 拋出actions函數的類型以供mapDispatchToProps使用
      export interface PlayerActionsTypes {
        count: Function;
        subtract: Function;
      }複製代碼
  • containers/Demo/index.tsx

    import * as React from 'react';
      import './style.css';
      import { connect } from 'react-redux';
      import { bindActionCreators, Dispatch } from 'redux';
      import * as actions from './action';
      import { PlayerStateTypes } from './reducer';
      import { AppStoreType } from '../../reducers';
    
      interface PlayerPropsClass {
        player: PlayerStateTypes;
        playerActions: actions.PlayerActionsTypes;
      }
    
      function mapStateToProps(state: AppStoreType) {
        return {
          player: state.PlayerStore,
        };
      }
    
      function mapDispatchToProps(dispatch: Dispatch<{}>) {
        return {
          playerActions: bindActionCreators<{}>(actions, dispatch)
        };
      }
    
      class Player extends React.Component<PlayerPropsClass, {}> {
        constructor(props: object) {
          super(props as PlayerPropsClass);
          this.state = {};
        }
    
        addOne = (num: number) => this.props.playerActions.count(num);
    
        subtractOne = (num: number) => this.props.playerActions.subtract(num);
    
        render() {
          const { countNumber } = this.props.player.toJS();
          return (
            <div className="player"> <span>{countNumber}</span> <button onClick={() => this.addOne(countNumber as number)}>點擊+1</button> <button onClick={() => this.subtractOne(countNumber as number)}>點擊-1</button> </div>
          );
        }
      }
    
      export default connect(mapStateToProps, mapDispatchToProps)(Player as React.ComponentType<PlayerPropsClass>);複製代碼

結尾

經過這個小練習還真的是感覺到了typescript的好處,那就是在編譯時就報錯,並且可以知道報錯緣由,不須要在編譯完成之後再去進行錯誤點的查找,確實節省了不少時間,尤爲是對於JavaScript代碼的undefined和null的處理很友好。。總之,感受寫起來很流暢啊。啊哈哈哈哈~~~

相關文章
相關標籤/搜索