React+Typescript+Redux的使用步骤
目前的项目是基于Typescript下的React,接入Redux的话在部分接口声明和定义的地方是需要注意的,这里整理一下步骤并进行记录。
1. 安装Redux基本环境
我们目前需要使用的Redux相关库包括redux、react-redux、redux-saga,所以首先需要做的是通过npm或者yarn安装对应的库,注意这其中react-redux需要加上@types的前缀才行:
npm install --save @types/react-redux
这部分和是否TS环境下安装过程基本一致,只是注意有一些库需不需要@types声明就行了。
2. 为项目引入Redux
接着我们需要为我们的项目引入Redux,如果你之前从未使用过Redux,建议你先看看文章了解一下Redux的基本使用,
如果你已经了解了那么下面我就重点从需要注意的地方入手,这一章节只说一下Redux基础的文件创建过程。
2.1 rootReducer的创建
首先我们需要创建一个根Reducer,这个Reducer是通过combineReducers方法将各个子模块的Reducer进行整合(这部分其实和TS无关):
src/redux/reducers.tsx
import {combineReducers} from 'redux'; import {fileManagerReducer} from "../modules/workspace/containers/FileManager/reducer"; const rootReducer = combineReducers({ fileManager: fileManagerReducer }); export default rootReducer; export type RootState = ReturnType<typeof rootReducer>;复制代码
这里我们只写了一个子模块作为示例。
这里注意我们需要export的包括rootReducer还有一个类型:RootState,因为后面在进行mapStateToProps的过程中我们需要给传入的state声明一个类型,而这个RootState就是他的类型!
2.2 生成store
这部分跟TS关系也不大,我就直接贴代码了:
src/redux/index.tsx
import createSagaMiddleware from "redux-saga"; import {applyMiddleware, createStore, compose} from "redux"; import rootReducer from "./rootReducer"; import myMiddleware from "./middleware"; //添加中间件和调试工具 const sagaMiddleware = createSagaMiddleware(); const composeEnhancers = typeof window === 'object' && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; const store = createStore(rootReducer, composeEnhancers(applyMiddleware(myMiddleware, sagaMiddleware))); // sagaMiddleware.run(mainSaga); export default store;复制代码
我这边使用了中间件和Redux调试工具所以createStore多了一些参数。
2.3 包裹根组件
接着需要把根组件进行包裹,让其中的组件都可以使用Redux,这个是react-redux库的功能:
src/index.tsx
import React from "react"; import ReactDOM from "react-dom"; import {Provider} from 'react-redux'; import MainFrame from './components/MainFrame'; import store from "./redux"; ReactDOM.render( <Provider store={store}> <MainFrame/> </Provider>, document.getElementById('root') as HTMLElement );复制代码
通过以上步骤就为我们的项目引入了Redux了,而下面我将具体举一个实例来分析针对某一个具体模块使用redux的步骤和注意事项。
3. 子模块中使用Redux
接着我们需要在一个列表子模块中实现这样一个功能,列表可以渲染state中的一个数组,同时列表中需要提供一个新增按钮来新增一个测试数据,同时实时更新当前列表。
3.1 创建types文件
首先我们需要创建一个types文件,这个文件主要定义这个模块中使用到的state和action的接口类型,这也是TS中的特色之一:
//State export interface CardFile { id: string title: string creator: string desc: string } export interface FileManagerState { cardFiles: CardFile[] selectCardId: string } //Action export const MAKE_NEW_CARD = 'MAKE_NEW_CARD'; export const SELECT_CARD = 'SELECT_CARD'; interface MakeNewCardAction { type: typeof MAKE_NEW_CARD payload: CardFile } interface SelectCardAction { type: typeof SELECT_CARD selectCardId: number | string } export type FileManagerActionTypes = MakeNewCardAction | SelectCardAction;复制代码
这里如果有多个Action,最后声明的Types就用|来表示。
3.2 回顾一下Redux的工作流
接着我们需要根据Redux的工作流来分别定义Action和Reducer了,这里我们利用下面这张图简单回顾一下吧:
---图片系统抽风了,后面能显示的话我贴下图
如果一个组件需要读取Store中存储的数据,那么他需要获取store并getState来获取其中的数据。
如果一个组件需要修改Store中存储的数据,那么他需要创建一个Action并Disptach出去,传递到Reducer生成一个新的State从而重新渲染原先的界面。
这里具体详细的步骤就不展开说了,不清楚的可以再去Google一下
3.3 分别创建子模块的reducer和actions
reducer
src/modules/workspace/containers/FileManager/reducer.tsx
import {FileManagerActionTypes, FileManagerState, MAKE_NEW_CARD, SELECT_CARD} from './types' const initialState: FileManagerState = { cardFiles: [ { id: '100', title: 'CardNo1', creator: 'Joern', desc: '空' }, { id: '101', title: 'CardNo2', creator: 'Joern', desc: '空' }, { id: '102', title: 'CardNo3', creator: 'Joern', desc: '空' }, ], selectCardId: '100' }; export function fileManagerReducer( state = initialState, action: FileManagerActionTypes ): FileManagerState { switch (action.type) { case MAKE_NEW_CARD: return Object.assign({}, state, { cardFiles: [ ...state.cardFiles, action.payload ] }); case SELECT_CARD: console.log(action.selectCardId); return Object.assign({}, state, { selectCardId: action.selectCardId }); default: return state } }复制代码
因为使用了TS,所以对应的state和action都需要进行类型声明,这里的类型就是我们之前在types文件里面声明好的,这里直接使用就可以了。
这里我们关注Reducer接受到一个type为MAKE_NEW_CARD类型的Action时,他会在原先State基础上更新cardFiles对象,将action的payload传入生成一个新的数组。
actions
src/modules/workspace/containers/FileManager/actions.tsx
import {CardFile, FileManagerActionTypes, MAKE_NEW_CARD, SELECT_CARD} from './types' export function makeNewCard(newCard: CardFile): FileManagerActionTypes { return { type: MAKE_NEW_CARD, payload: newCard } } export function selectCard(selectedCardId: number | string): FileManagerActionTypes { return { type: SELECT_CARD, selectCardId: selectedCardId } }复制代码
这里就是定义了两个Action,注意需要声明好Action的类型,并且传参的使用也需要和对应的字段类型匹配。
3.4 子模块主文件的编写
我们的主文件是index文件,在这个文件里面我们需要处理读取state和新增发送action的逻辑,我们来看一下。
react-redux的映射函数
如果基于react-redux库我们知道需要定义两个映射的函数,反别将State映射到Props,将Action插入到Props:
// 将reducer中的状态插入到组件的 props 中 const mapStateToProps = (state: RootState) => { const {fileManager} = state; return { cardFiles: fileManager.cardFiles, selectCardId: fileManager.selectCardId } }; // 将对应action 插入到组件的props中 const mapDispatchToProps = (dispatch: Dispatch<AnyAction>) => { return { handleItemClick: (index: number | string) => { dispatch(selectCard(index)); }, makeNewCard: (newCard: CardFile) => { dispatch(makeNewCard(newCard)); } } };复制代码
这里有一个需要注意的地方,如果你使用的Reducer是基于combine来进行组合的,那么你的state的接口定义的是全局combine之后的Reducer,你需要获取具体的子Reducer才可以成功获取其中的state!
渲染列表
好了接着可以开始写TSX逻辑了,记得组件的Props先需要声明哈:
interface IProps { cardFiles: CardFile[]; handleItemClick: (index: number | string) => void makeNewCard: (card: CardFile) => void } //测试数据-添加用的 const testNewCard: CardFile = { creator: "New", desc: "111", id: "98", title: "NewCard" }; class FileManager extends Component<IProps> {...}复制代码
我们先实现拿到cardFiles来渲染List吧,这里直接贴代码:
class FileManager extends Component<IProps> { public render() { const {cardFiles, handleItemClick, makeNewCard} = this.props; console.log(this.props); return ( <div id="file-manager"> .... <div id="file-manager-list"> <List size="small" itemLayout="horizontal" dataSource={cardFiles} split={false} renderItem={(item, index) => ( // Antd的默认间距太大 <List.Item style={{padding: '0px'}} onClick={() => handleItemClick(item.id)}> <List.Item.Meta avatar={<Icon type="form"/>} title={ <div> <Icon type="file"/> <Button type={"link"}>{item.title}</Button> </div> } /> </List.Item> )} /> </div> </div> ) } }复制代码
直接通过this.props.cardFiles拿到就可以使用了,注意List中的item类型就是一个cardFile,如果使用IDE你会发现会有代码提示补齐,这也是使用TS的好处之一~
新增一个Card
接着我们希望新增一个Card并实时渲染出来,我们直接在TSX中嵌入这么一段:
<Button style={{fontSize: '12px'}} className={styles.newPage} type="link" icon="file-add" onClick={() => makeNewCard(testNewCard)}> 新建Card </Button>复制代码
这样就可以了,点击之后就会生成对应的action并传递给reducer进行cardFiles的新增,之后state会出现差异,于是对应的列表就会重新渲染拉。
注意点
这里还有一个注意事项就是onClick这些事件传递函数的不能这样写,会报错的:(图片上传不了,不知道为啥,其实就是你直接调用函数的话会报错,如果传递函数需要在TSX里面入参的话就要用箭头函数构造一遍!)
主要是因为返回类型的校验
有误,需要构造一个箭头函数解决!
以上基本就完成了一个基于React在TS环境下的Redux工作流的搭建工作了~
作者:JoernLee
链接:https://juejin.cn/post/7032451877761449991