最近在看antd源码,也在自学一些typescript的语法以及使用,而后准备带在学校的师弟作一个音乐播放器做为typescript的练习踩踩坑;css
而后居然没想到在项目刚开始就深陷大坑--搭建react-redux项目流程,一下内容就是记录我是怎样从这个深坑中爬出来的过程。node
从项目建立以后就掉坑里了。。。react
建立项目的流程很简单,可有参考我以前写的这篇文章git
刚开始还不是很熟悉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
几经折腾以后我又将代码改为了这样,才得以过关redux
import { bindActionCreators, Dispatch } from 'redux';
function mapStateToProps(state: { PlayerStore: object }) {
return {
player: state.PlayerStore,
};
}
function mapDispatchToProps(dispatch: Dispatch<{}>) {
return {
playerActions: bindActionCreators<{}>(actions, dispatch)
};
}复制代码
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>);复制代码
有了以上的经验,再加上在这里看了一些代码,我决定把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的处理很友好。。总之,感受写起来很流畅啊。啊哈哈哈哈~~~