状态管理
Redux是一个专门用于做状态管理的JS库,在React项目中我们通常使用React-Redux
Redux的作用
引入Redux后可以将所有组件分为两大类
UI组件:只负责数据的呈现,不带有任何业务逻辑。
容器组件:只负责业务逻辑,不带有任何UI呈现。
Redux的适用场景
某个组件的状态需要让其他组件随时拿到(共享)
一个组件要改变另一个组件的状态
Redux的基本组成
Action
动作的对象
包含2个属性(
type
:字符串类型的唯一标识(必要属性),payload
:携带的数据(非必要属性))
const DemoAction = {type:'DemoAction',payload:1}
Action Creaters
- 用来生成Action的函数
const TODO = 'DemoAction';
const addTodo =(payload)=> {
return {
type: TODO,
payload
}
}
const action = addTodo(2);
Reducer
初始化状态,加工状态。
根据旧state和action生成新的state的函数。
const defaultState = 0;
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'DemoAction':
return state + action.payload;
default:
return state;
}
};
Dispatch
- 用来发出Action
const action = {type:'DemoAction',payload:1}
store.dispatch(action);
State
- 在获取Store中的数据时,Store会生成当前时间节点的数据快照,快照中的数据集合就是State。
import store from './store';
const state = store.getState()
Store
- 用来联系Action,State,Reducer的对象
import { createStore } from 'redux';
// 创建store
const store = createStore(reducer);
// 从store获取state
const state = store.getState();
// 执行一个Action
const action = {type:'DemoAction',payload:1}
store.dispatch(action);
Redux的工作原理
命名规则
划分方法 VS 可维护性
划分方法 | 粒度 | 可维护性 |
---|---|---|
按页面 | 小 | 低 |
按模块 | 中 | 高 |
放在一起 | 大 | 低 |
经对比得出,按模块划分粒度适中,可维护性高,所以我们选择按模块划分。
Action
Action 的命名 采用 [模块名] + 功能
的命名方式。
export const loadMockList = createAction('[MOCK] 加载示例列表');
Reducer
Reducer的创建 采用模块名 + Reducer
的方式。
Reducer的使用 采用 模块名
的方式。
// 创建
export const mockReducer = createReducer();
// 使用
const reducers = combineReducers<AppState>({
mock: mockReducer,
});
Redux Toolkit
Redux Toolkit 是 Redux 的工具集,工具集中提供的方法封装可以降低Redux的使用难度,改善我们的Redux代码。
常用API:
Redux Thunk
Redux Thunk 是 Redux 的中间件,用来解决Redux的异步调用问题。
原因:我们使用Redux的初衷是为了分离视图层(component)
和业务层(store)
,这导致我们需要在redux中调用接口获取数据,但是Redux本身是不支持异步的,于是就有了Redux Thunk来解决Redux中的异步调用。
Redux Thunk + Redux Toolkit 封装
为了解决Redux的异步调用 和 统一代码书写风格,我们使用Redux Thunk 和 Redux Toolkit进行封装。
import { Action, ActionCreator, AnyAction } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { createAction, PayloadActionCreator } from '@reduxjs/toolkit';
import { DataServiceError, DataServiceMetadata, DataServiceResult } from './models';
export interface DataHandleAction<T> extends Action<string> {
readonly success: boolean;
readonly payload: T;
readonly fromParams?: any;
readonly meta?: DataServiceMetadata;
readonly error?: DataServiceError;
}
export declare type DataHandleActionCreator<T> = PayloadActionCreator<DataHandleAction<T>>;
export declare type ErrorHandleActionCreator = PayloadActionCreator<DataHandleAction<any>>;
export declare type ThunkResult<R> = ThunkAction<R, any, undefined, any>;
/** 创建请求处理 Action */
export function createDateHandleAction<T extends {}>(type: string): DataHandleActionCreator<T> {
return createAction(type);
}
/** 创建错误处理 Action */
export function createErrorHandleAction(type: string): ErrorHandleActionCreator {
return createAction(type);
}
/**
* 数据服务请求 Action
* @params thunk 数据请求块
* @params successActionCreator 成功的 action
* @params errorActionCreator 失败的 action
* @params beginActionCreator? 在开始 异步任务之前要发出的action
* @params fromParams? 本次请求的参数,可能在后续的 reducer 处理时有用
*/
export function createDataServiceAction<T extends {}>(
thunk: Promise<DataServiceResult>,
successActionCreator: DataHandleActionCreator<T>,
errorActionCreator: ErrorHandleActionCreator,
beginActionCreator?: ActionCreator<AnyAction>,
fromParams?: any): ThunkResult<Promise<DataHandleAction<T>>> {
return dispatch => {
if (beginActionCreator) {
dispatch(beginActionCreator(fromParams));
}
return thunk
.then(result => {
if (!result.success) { throw result; }
const action = successActionCreator(result.result);
dispatch({
...action,
fromParams,
meta: result.meta,
success: result.success
});
return {...action, success: result.success};
})
.catch(result => {
console.log('createDataServiceAction error:', result);
const props = {
...result.error,
error: result.error,
fromParams,
success: result.success
};
dispatch(errorActionCreator(props));
return props;
});
};
}
使用方法
创建Action
通过
createAction
创建一般Action通过
createDateHandleAction
创建异步操作正常返回后执行的Action(处理返回数据)通过
createErrorHandleAction
创建异步操作错误返回后执行的Action(处理返回错误)通过
createDataServiceAction
创建异步Action
import { MockData} from "../../services/models";
import {
createDataServiceAction,
createDateHandleAction,
createErrorHandleAction
} from "../../services/utils/action";
import * as MockService from '../../services/mock';
import {createAction, createAsyncThunk} from '@reduxjs/toolkit';
export const loadMockList = createAction('[MOCK] 加载示例列表');
export const loadMockListSuccess = createDateHandleAction<MockData[]>('[MOCK] 加载示例列表成功');
export const loadMockListFaild = createErrorHandleAction('[MOCK] 加载示例列表失败');
export const loadMock = (query) => createDataServiceAction(
// 异步请求
MockService.getAll(query),
// 请求成功执行
loadMockListSuccess,
// 请求失败执行
loadMockListFaild,
// 请求之前执行
loadMockListBegin
);
创建Reducer
import { MockData} from "../../services/models";
import { DataServiceMetadata } from "../../services/data-result";
import { createReducer } from '@reduxjs/toolkit';
import { loadMockListFaild, loadMockListSuccess } from '../actions';
export interface MocksState {
list: MockData[];
loaded: boolean;
loading: boolean;
meta?: DataServiceMetadata;
ids: string[];
entities: { [key: string]: MockData };
}
const initalState: MocksState = {
list: [],
loaded: false,
loading: false,
ids: [],
entities: {}
};
export const mockReducer = createReducer(initalState, {
[loadMockListSuccess.type]: (state, { payload }) => {
state.loaded = true;
state.list = state.list.concat(payload);
payload.forEach((item: any) => state.entities[item.id] = item);
state.ids = state.list.map(item => item.id);
},
[loadMockListFaild.type]: (state) => {
state.loaded = true;
}
});
创建Store
通过
combineReducers
组合Reducer通过
combineReducers
创建Store
import {combineReducers, configureStore, getDefaultMiddleware} from '@reduxjs/toolkit';
import {mockReducer} from './reducers';
import {AppState} from './state';
export * from './state';
const reducers = combineReducers<AppState>({
mocks: mockReducer,
});
const store = configureStore({
reducer: reducers,
middleware: [...getDefaultMiddleware()],
devTools: process.env.NODE_ENV !== 'production'
});
export default store;
页面使用
connect
:将redux的State和Action与页面连接在一起。connect
函数带有两个参数:
mapStateToProps
:每次Store中State更改时调用。它接收整个Store的State,并返回此组件需要的State。mapDispatchToProps
:返回一个对象,这个对象具有dispatch
的完整功能。
'use strict';
import React, {useEffect, useState} from 'react';
import {connect} from 'react-redux';
import {AppState} from "../../../store";
import {ThunkDispatch} from "redux-thunk";
import {MocksState} from '../../../store/reducers';
import {AnyAction} from '@reduxjs/toolkit';
import {MockData} from '../../../services/models';
import {DataHandleAction} from '../../../services/utils/action';
import {loadMock} from "../../../store/actions";
export interface MocksDispatchProps {
loadMocks: () => Promise<DataHandleAction<MockData[]>>;
}
/**
* ReduxDemo
* @constructor
*/
// @ts-ignore
const Demo = ({list, loadMocks}) => {
const [count, setCount] = useState(0);
useEffect(() => {
loadData();
}, []);
useEffect(() => {
setCount(list.length);
}, [list]);
const loadData = async () => {
await loadMocks();
};
return (
<div>
<div>Count: {count}</div>
{list.map((i: MockData) => (
<div key={i.id}>{i.title}</div>
))}
<button onClick={loadData}> loadData </button>
</div>
);
};
const mapStateToProps = (state: AppState) => {
const {mocks} = state;
const {loaded, list} = mocks;
return {loaded, list};
};
const mapDispatchToProps = (dispatch: ThunkDispatch<MocksState, any, AnyAction>): MocksDispatchProps => {
return {
loadMocks: () => dispatch(loadMock())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Demo);