Skip to main content

状态管理

Redux是一个专门用于做状态管理的JS库,在React项目中我们通常使用React-Redux

Redux的作用

引入Redux后可以将所有组件分为两大类

  1. UI组件:只负责数据的呈现,不带有任何业务逻辑。

  2. 容器组件:只负责业务逻辑,不带有任何UI呈现。

Redux的适用场景

  1. 某个组件的状态需要让其他组件随时拿到(共享)

  2. 一个组件要改变另一个组件的状态

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);