背景
最近在看antd原始碼,也在自學一些typescript的語法以及使用,然後準備帶在學校的師弟做一個音樂播放器作為typescript的練習踩踩坑;
然後竟然沒想到在專案剛開始就深陷大坑--搭建react-redux專案流程,一下內容就是記錄我是怎樣從這個深坑中爬出來的過程。
開始?根本開始不起來
從專案建立之後就掉坑裡了。。。
建立專案
建立專案的流程很簡單,可有參考我之前寫的這篇文章
從mapStateToProps&mapDispatchToProps開始爬坑
剛開始還不是很熟悉typescript,所以我最開始的reducer, action, constant,檔案都是以.js結尾,只有元件的程式碼是以.tsx結尾的,然後就開始按照流程,將store中的state進行mapStateToProps以及actions進行mapDispatchToProps,誰想到這竟然是噩夢的開始,最開始我是這樣寫的程式碼:
import { bindActionCreators } from 'redux';
function mapStateToProps(state: {}) {
return {
player: state.PlayerStore,
};
}
function mapDispatchToProps(dispatch: Function) {
return {
playerActions: bindActionCreators(actions, dispatch)
};
}複製程式碼
然後就開始報錯了。。。
幾經折騰之後我又將程式碼改成了這樣,才得以過關
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)報錯提示
[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)複製程式碼
然後我改成
export default connect(mapStateToProps, mapDispatchToProps)(Player as {});複製程式碼
仍然報錯
[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的處理很友好。。總之,感覺寫起來很流暢啊。啊哈哈哈哈~~~