Redux

一个可预测的状态容器.

原则

  • 单一数据源
  • state 是只读的
  • 使用纯函数进行修改

原理

将数据存储在 store 中,一个组件改变了 store 中的内容,其他组件就能知道 store 的变化,再来获取数据,实现了数据传递。

常用概念

state:数据集合.存储状态的值.
action: 改变 state 的指令.或者说行为.
reducer: 加工函数,action 将 state 放到 reducer 中处理,返回新的 state.
store:仓库,有多个 reducer 的总工厂.

核心 API

1
2
3
4
5
6
7
8
9
10
// 创建Store容器
const store = Redux.createStore(reducer);
// 创建用于处理状态的Reducer函数
function reducer(state = initialState, action) {}
// 获取状态
store.getState();
// 订阅状态
store.subscribe(function () {});
// 触发Action
store.dispatch({ type: "description..." });

工作流程

1.组件 Component 通过 dispatch 方法触发 Action
2.Store 接收 Action 并将 Action 分发给 Reducer
3.Reducer 根据 Action 类型对状态进行更改并将更改后的状态返回给 Store
4.组件订阅了 Store 中的状态,Store 中的状态更新会同步到组件
iShot2022-07-27 22.16.54.png

常用方法

store

getState:获取 state.
dispatch(action):更新 state.它内部有 reducer 方法,所以触发 dispatch 就能更新 state
subscribe(listener):注册监听器.
unsubscribe(listener):返回的函数注销监听器.

通过 store.getState()来了解工厂中商品的状态, 使用 store.dispatch 发送 action 指令更新 state.

原则

  • 不变性.
  • state 是不可变的,只是返回一个新的 state.

为什么非要返回一个新的 state 呢?react 的比较机制,Object.is.就是纯纯的比较两个对象是否相等.

避免变化的要点

  • 对数组使用 concat,slice 和…展开运算
  • 对对象使用 Object.assign 和…展开运算

顶级暴露方法

createStore

1
2
3
4
5
// src/js/store/index.js
import { createStore } from 'redux';
import rootReducer from ../reducers/index;
const store = createStore(rootReducers);
export default store;

从上面看出,store 是 createStore 创建的,参数是 rootReducers.

reducer

纯函数,有两个参数,当前 state 和 action.
改变 state 的唯一方法就是派发 action.
reducer 一定是同步的,为了就是保持纯洁性.异步操作有副作用,就是可能会出现不同的结果.比如发送请求,第一次可能成功,第二次可能失败,这就不可预测.这就是副作用.

新产生的 state 是当前 state 添加了新数据后的一个副本,前一个 state 根本没发生变化.

1
2
3
4
5
6
// src/js/reducers/index.js
const initialState = { articles: [] };
function rootReducers(state = initialState, action) {
return state;
}
export default rootReducer;

action

每个 action 需要一个 type 属性来描述状态怎样变化
最佳的实践是在一个函数里包裹每一个 action,这个函数就叫 action creator.

1
2
3
4
// src/js/actions/index.js
export function addArticle(payload){
return {type: "ADD_ARTICE",payload }
}:

因为字符串容易出错或者重复,建议将 action 的 type 声明为常量

1
2
// src/js/constants/action-types.js
export const ADD_ARTICLE = "ADD_ARTICLE";

再使用时引入即可

1
2
3
4
import { ADD_ARTICLE } from "../constants/action-types";
export function addArticle(payload) {
return { type: ADD_ARTICLE, payload };
}

combineReducer

combineReducer 就是将拆分的 render 合并到一起.返回一个可以返回各个 reducer 状态的组合版的 reducer.
然后可以对这个 reducer 调用createStore.
合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名

1
2
3
4
5
6
7
8
import { combineReducers } from "redux";
import todos from "./todos";
import counter from "./counter";

export default combineReducers({
todos,
counter,
});

compose

用来从右到左来组合多个函数.当需要把多个store enhancers依次执行时用到它.
为什么从右到左呢? 因为 middleware 在执行的时候就是从右到左传递 dispatch,然后在从左到右执行.

1
2
3
4
5
6
7
8
9
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import DevTools from "./containers/DevTools";
import reducer from "../reducers";

const store = createStore(
reducer,
compose(applyMiddleware(thunk), DevTools.instrument())
);

辅助函数 bindActionCreators

来自 react,第一个参数要进行的 aciton 操作,第二个是 dispatch.
这个函数的作用就是把 actionCreator(这个就是返回 aciton 对象的函数)和 dispatch 结合起来,形成可以直接触发的 dispatch 操作.
就比如它可以把下面的示例二转化为示例一就明白了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 示例一
const mapDispatchToProps = (dispatch) => ({
increment() {
dispatch({ type: "increment" });
},
decrement() {
dispatch({ type: "decrement" });
},
});

// 示例二
// 将上面的操作提出来使用bindActionCreators
bindActionCreators(
{
increment() {
return { type: "increment" };
},
decrement() {
return { type: "decrement" };
},
},
dispatch
);

将 action 操作提出来

1
2
3
//actions.ts
export const increment = () => ({ type: "increment" });
export const decrement = () => ({ type: "decrement" });

再引入修改

1
2
3
4
5
6
7
8
const mapStateToProps = (state) => ({
count: state.count,
});

const mapDispatchToProps = (dispatch) => ({
...bindActionCreators(counterActions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
1
bindActionCreators(mapDispatchToProps, this.store.dispatch);

react-redux

react-redux 主要是用来连接 react 和 redux 的,redux 不仅仅可以用在 react 中,还可以用在别的框架中.
作用: 将 store 中的 state 和组件连接在一起.

核心

  • <Provider store>
  • connect([mapStateToProps],[mapDIspatchToProps],[mergeProps],[options])

理解: Provider 包裹 react 应用,让它可以感知到整个 Redux 的 store.
Provider 中的任何一个组件,只有被 connect 过的组件才可以使用 state 中的数据.
它允许将 store 中的数据作为 props 绑定到组件上.
connect 方法将 store 上的 getState 和 dispatch 包装成组件的 props.

1
2
3
4
5
6
7
8
9
10
11
import { Provider } from 'react-redux';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StricMode>
<Provider store={store}>
<App />
<Provider>
</React.StricMode>
)

connect

四个参数,mapStateToProps,mapDispatchToProps,mergeProps 和 options.
作用分别为:

  • mapStateToProps:将 store 中的数据作为 props 绑定到组件上.
  • mapDispatchToProps(dispatch, ownProps): dispatchProps:将 action 作为 props 绑定到组件上.

1.connect 方法会帮助我们订阅 store,当 store 中的状态发生更改的时候,会帮助我们重新渲染组件
2.connect 方法可以让我们获取 store 中的状态,将状态通过组件的 props 属性映射给组件
3.connect 方法可以让我们获取 dispatch 方法

mapStateToProps

声明这个函数,这个函数是要把 state 绑定到 props 上.就要把相对应的想要传递的 state 中的相关参数传递出来.
这个函数传递出来的参数是和它相连接的函数所需要的参数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from "react";
import { connect } from "react-redux";
const mapStateToProps = (state) => {
return { articles: state.articles };
};
const ConnectedList = ({ articles }) => (
<ul className="list-group list-group-flush">
{articles.map((el) => (
<li className="list-group-item" key={el.id}>
{el.title}
</li>
))}
</ul>
);
const List = connect(mapStateToProps)(ConnectedList);
export default List;

mapDispatchToProps

下面这个 form 组件本身有 state 可以只进行 action 的 dispatch 操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import React, { Compoent } from 'react';
import { connect } from 'react-redux';
import uuidv from 'uuid';
import { addArticle } from "../actions/index";
const mapDispatchToProps = (dispatch) => {
return {
addArticle: article => dispatch(addArticle(article))
}
}
class ConnectedForm extends Component {
constructor(){
super()
this.state = {
titlte: ''
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event){
this.setState({ [event.target.id]: event.target.value });
}
handleSubmit(event){
event.preventDefault();
const { title } = this.state;
const id = uuidv();
this.props.addArticle({ title: id });
this.setState({ title: "" });
}
render(){
const { title } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
className="form-control"
id="title"
value={title}
onChange={this.handleChange}
</div>
<button type="submit" className="btn btn-success btn-bg">SAVE</button>
</form>
);
}
}

const Form = connect(null, mapDispatchToProps)(ConnectedForm);
export default Form;

上面例子中handleSubmit方法触发 action.

Hooks

现在官方默认推荐使用 Hooks 方案替代上面的 connect().因为更简单明了.配合 ts 更是起飞.

useSelector

作用: 在函数组件中获取 state.
替代:mapStateToProps
允许传入一个选择器函数从 redux state 中取需要的状态数据并返回。示例:

1
const data: any = useSelector(selector: Selector, eqaulityFn?: Function)
1
const num = useSelector((state) => state.num);
  • 选择器 Selector 函数可以返回任意类型的数据,而不仅限于 对象

  • 当 dispath 一个 action 的时候,useSelector() 函数将对 Selector 选择器函数的返回值和上一个状态的返回值进行比较,如果 不同就会强制渲染组件,相同则不会

  • Selector 选择器 没有 接收 ownProps 属性

  • useSelector 默认比较更新 方式 为 ===, 如果需要浅层比较可以传入 第二个参数 equalityFn

    Tips: 不要在 Selector 选择器函数中 使用 props 属性, 否则可能会导致错误, 原因如下

  • 选择器函数依赖 props 的 某个数据

  • dispatch action 会导致父组件重新渲染,并传递新的 props

  • 子组件渲染时,选择器函数的执行时机会在 新 props 传入之前先执行,此时就会可能导致选择器函数返回错误数据,甚至报错 Zombile Child 是另一种 旧 props 导致异常的情况, 具体可以查看 常用警告

useDispatch

作用:返回 store 的 dispatch 方法,可以手动调用 dispatch 方法。
替代:mapDispatchToProps

1
const dispatch = useDispatch();

useStore

这个 Hook 返回 redux <Provider>组件的 store 对象的引用。

Tips: 大多数情况用不到这个 api, 取数据使用 useSelector() 代替

1
2
3
4
5
6
7
8
9
10
import React from "react";
import { useStore } from "react-redux";

export const CounterComponent = ({ value }) => {
const store = useStore();

// EXAMPLE ONLY! Do not do this in a real app.
// The component will not automatically update if the store state changes
return <div>{store.getState()}</div>;
};

Hooks 组件与 Connect 组件的对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from "react";
import { connect } from "react-redux";
import { addCount } from "./store/counter/actions";
export const Demo = ({ count, addCount }) => {
return (
<div>
      <div>Count: {count}</div>
      <button onClick={addCount}>Count</button>
    
</div>
);
};
const mapStateToProps = (state) => ({
count: state.counter.count,
});
const mapDispatchToProps = { addCount };

// content 链接
export default connect(mapStateToProps, mapDispatchToProps)(Demo);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { addCount } from "./store/counter/actions";

export const Demo = () => {
const count = useSelector((state) => state.counter.count);
const dispatch = useDispatch();

return (
<div>
      <div>Count: {count}</div>
      <button onClick={() => dispatch(addCount())}>Count</button>
    
</div>
);
};

中间件 middleware

最最重要不要忘记return next(action)
中间件就是一个函数,带有科里化.是对 dispatch 方法的改造,在发出 action 和执行 reducer 之间,添加的功能。

1
export default (store) => (next) => (action) => {};

例如查询文章标题的禁用词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { ADD_ARTICLE } from "../constants/action-types";

const forbiddenWords = ["spam", "money"];

export function forbiddenWordsMiddleware({ dispatch }){
return function(aciton){
if(action.type === ADD_TYPE){
const foundWord = forbiddenWords.filter(word =>
action.payload.title.includes(word)
);
if(foundWord.length){
return dispatch({ type: "FOUND_BAD_WORD"});
}
}
return next(action);
}:
}

开发中间件

中间件接收 store 为参数,返回一个 next 为参数的函数,该函数又返回一个 action 为参数的函数.最后通过 next 将 action 操作返回.
普通同步方法 actionCreator 函数使用 action 返回的是 action 对象,
异步方法返回的是函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// logger.js
export default store => next => action => {
console.log(action)
console.log(store)
next(action)
}

// 开发异步中间件
// 通过判断action的类型,判断是异步还是普通类型
// 异步代码需要放在传递进来的函数中
// 异步代码需要把dispatch传递出去
export default ({dispatch}) => next => action => {
if( typeof action = 'function') {
return action(dispatch)
}
next(action)
}

// 使用这个中间的异步函数也要传入dispatch
export const increment_async = payload => dispatch => {
setTimeout(() => {
dispatch(increment(payload))
},2000)
}

这是编写中间件,不过大多数中间件都有现程的,仅做了解.

工作流程

image.png

使用中间件

和 reducer 一起放到 store 中就行.

1
2
3
4
5
6
7
8
9
10
import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers/index";
import { forbiddenWordsMiddleware } form "../middleware";

const store = createStore(
rootReducer,
applyMiddleware(forbiddenWordsMiddleware)
);

export default store;

异步操作中间件

即使不使用中间件,redux 依旧可以使用异步,只是 react-thunk 可以更好的将异步操作的细节隐藏.让书写更加的方便.
异步操作放在 action creator 中.
上面只是写法,真正有更好用的react-thunk.

reudx-thunk

1
2
3
4
5
6
7
8
9
//src/js/store/index.js
import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers/index";
import { forbiddenWordsMiddleware } from "../middleware";
import thunk from "redux-thunk";
const store = createStrore(rootReducer,
applyMiddleware(forbiddenWordsMiddleware, thunk))
);
export default store;

重新写一下异步的 action 操作

1
2
3
4
5
6
7
8
9
10
//src/js/actions/index.js
export default getData(){
return function(dispatch){
retrun fetch("https://jsonplaceholder.typicode.com/posts")
.then(response => response.json())
.then(json => {
dispatch({type: "DATA_LOADED",payload: json});
});
};
}

在 reducer 中添加对应的 type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
	//src/js/reducers/index.js
import { ADD_ARTICLE,DATA_LOADED } from '../constants/action-types";
const initialState = {
articles: [],
remoteArticles: []
};
function rootReducer(state = initialState, action){
if(action.type === ADD_ARTICLE){
return Object.assign({}, state, {
articles: state.articles.concat(action.payload)
});
}
if(action.type ==== DATA_LOADED){
return Object.assign({}, state, {
remoteArticles: state.remoteArticles.concat(action.payload)
});
}
return state;
}
export default rootReducer;

redux-saga

这个属于 redux-thunk 的进阶版本,它可以将异步操作从 actionCreator 中提取出来单独放在一个文件中.

使用

1
2
3
4
5
//创建redux-saga中间件
import createSagaMiddleware from "redux-saga";
const sagaMiddleware = createSagaMiddleware();
// 注册 sagaMiddleware
createStore(reducer, createMiddleware(sagaMiddleware));
1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用saga接收action执行异步操作
import { takeEvery, put, delay } from "redux-saga";
// takeEvery用来接收action
// put用来触发action
// 迭代器写法
function* increment_async_fn() {
yield delay(2000);
yield put(increment(10));
}
export default function* conuterSaga() {
// 接收action,第一个参数是类型,第二个是调用函数
yield takeEvery(INCREAMENT_ASYNC, increment_async_fn);
}
1
2
3
// 启动 saga
import postSaga from "./store/sagas/post.saga";
sagaMiddleware.run(postSaga);

拆分合并

1
2
3
4
5
6
//将多个saga操作拆分后,使用all合并
import { all } from "redux-saga/effects";

export default function* rootSaga() {
yield all([counterSaga(), modalSaga()]);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 创建reducer
import { handleActions as createReducer } from 'redux-actions'
import { increment_action, decrement_action } from '../actions/counter.action';

const handleAddProductLocalCart = (state, action) => {
// 深拷贝原有数据
const newState = JSON.parse(JSON.stringify(state))
newState.push({action.payload})
// 将修改过的返回
return newState
}
const handleSaveCart = (state, action) => action.payload

const counterReducer = createReducer({
[addProductLocalCart]: handleAddProductLocalCart,
[saveCart]: handleSaveCart
},initialState)
export default counterReducer;

redux-actions

用于简化 reducer 和 action 代码

1
2
3
4
5
6
//创建action
import { createAction } from "redux-actions";
const increment_action = createAction("increment");
// 相当于
// const increment_action = () =>({type: increment})
const decrement_action = createAction("decrement");
1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建reducer
import { handleActions as createReducer } from "redux-actions";
import { increment_action, decrement_action } from "../actions/counter.action";

const initialState = { count: 0 };
const counterReducer = createReducer(
{
[increment_action]: (state, action) => ({ count: state.count + 1 }),
[decrement_action]: (state, action) => ({ count: state.count - 1 }),
},
initialState
);
export default counterReducer;

Redux Toolkit

这是一个官方推荐的高效的开箱即用的 redux 工具箱.
它是简化 redux 的开发,无需手动书写大量样板代码.自带很多常用的插件,比如redux thunk,reselect.immer.

安装

1
pnpm add @reduxjs/toolkit

常用 api

configureStore()

封装了 createStore,简化配置项,提供一些现成的默认配置项。它可以自动组合 slice 的 reducer,可以添加任何 Redux 中间件,默认情况下包含 redux-thunk,并开启了 Redux DevTools 扩展。

1
2
3
4
5
6
7
8
9
import { configureStore } from "@reduxjs/toolkit";

export const rootReducer = {
projectList: projectListSlice.reducer,
};

export const store = configureStore({
reducer: rootReducer,
});

createReducer()

帮你将 action type 映射到 reducer 函数,而不是编写 switch…case 语句。另外,它会自动使用 immer 库来让你使用普通的 mutable 代码编写更简单的 immutable 更新,例如 state.todos[3].completed = true。

createAction()

生成给定 action type 字符串的 action creator 函数。该函数本身已定义了 toString(),因此可以代替常量类型使用。

createSlice()

接收一组 reducer 函数的对象,一个 slice 切片名和初始状态 initial state,并自动生成具有相应 action creator 和 action type 的 slice reducer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { createSlice } from "@reduxjs/toolkit";

// 或者
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment(state) {
state.value++;
},
decrement(state) {
state.value--;
},
incrementByAmount(state, action: PayloadAction<number>) {
state.value += action.payload;
},
},
});
// 导出
const { reducer: TodosReducer, actions } = createSlice();
export const { addTodo } = actions;
export default TodosReducer;

createAsyncThunk

接收一个 action type 字符串和一个返回值为 promise 的函数, 并生成一个 thunk 函数,这个 thunk 函数可以基于之前那个 promise ,dispatch 一组 type 为 pending/fulfilled/rejected 的 action。

createEntityAdapter

生成一系列可复用的 reducer 和 selector,从而管理 store 中的规范化数据。

createSelector

来源于 Reselect 库,重新 export 出来以方便使用。

工具 RTK Query

类似于 React Query.
Redux Toolkit 更是提供一个新的 RTK Query 数据请求 API。RTK Query 是为 Redux 打造数据请求和缓存的强有力的工具。 它设计出来就是为了 web 应用中加载数据的通用用例,免得手动去写数据请求和缓存的逻辑。

Redux 状态持久化

所谓状态持久化就是将状态与本地存储联系起来,达到刷新或者关闭重新打开后依然能得到保存的状态。

1
2
3
yarn add redux-persist
// 或者
npm i redux-persist

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// store.js
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import { persistStore, persistReducer } from "redux-persist"; // **
import storage from "redux-persist/lib/storage"; // **
import reducer from "./reducer";

const persistConfig = {
// **
key: "root", // 储存的标识名
storage, // 储存方式
whitelist: ["persistReducer"], //白名单 模块参与缓存
};

const persistedReducer = persistReducer(persistConfig, reducer); // **

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
persistedReducer,
composeEnhancers(applyMiddleware(thunk))
); // **
const persistor = persistStore(store); // **

export {
// **
store,
persistor,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom";
import { PersistGate } from "redux-persist/integration/react"; // **

import { store, persistor } from "./store"; // **
import "antd/dist/antd.min.css";
import "./index.css";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
{/* 使用PersistGate //** */}
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter>
<App />
</BrowserRouter>
</PersistGate>
</Provider>
</React.StrictMode>
);

注意此时的模块是在白名单之内,这样 persist_reducer 的状态就会进行持久化处理了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// persist_reducer.js
import { DECREMENT } from "./constant";

const defaultState = {
count: 1000,
title: "redux 持久化测试",
};

const reducer = (preState = defaultState, actions) => {
const { type, count } = actions;
switch (type) {
case DECREMENT:
return { ...preState, count: preState.count - count * 1 };
default:
return preState;
}
};

export default reducer;

immutable.js 与状态持久化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// count_reducer.js
import { INCREMENT } from "./constant";
import { Map } from "immutable";
// 简单的结构用Map就行 复杂使用fromJs 读取和设置都可以getIn setIn ...
const defaultState = Map({
// **
count: 0,
title: "计算求和案例",
});

const reducer = (preState = defaultState, actions) => {
const { type, count } = actions;
switch (type) {
case INCREMENT:
// return { ...preState, count: preState.count + count * 1 }
return preState.set("count", preState.get("count") + count * 1); // **
default:
return preState;
}
};

export default reducer;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 函数组件
const dispatch = useDispatch();
const { count, title } = useSelector(
(state) => ({
count: state.countReducer.get("count"),
title: state.countReducer.get("title"),
}),
shallowEqual
);
const handleAdd = () => {
const { value } = inputRef.current;
dispatch(incrementAction(value));
};
const handleAddAsync = () => {
const { value } = inputRef.current;
dispatch(incrementAsyncAction(value, 2000));
};

存在的问题

当我们使用了 redux-persist 它会每次对我们的状态保存到本地并返回给我们,但是如果使用了 immutable 进行处理,把默认状态改成一种它内部定制 Map 结构,此时我们再传给 redux-persist,它倒是不挑食能解析,但是它返回的结构变了,不再是之前那个 Map 结构了而是普通的对象,所以此时我们再在 reducer 操作它时就报错了,如下案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { memo } from "react";
import { useDispatch, useSelector, shallowEqual } from "react-redux";
import { incrementAdd } from "../store/persist_action";

const ReduxPersist = memo(() => {
const dispatch = useDispatch();
const { count, title } = useSelector(
({ persistReducer }) => ({
count: persistReducer.get("count"),
title: persistReducer.get("title"),
}),
shallowEqual
);
return (
<div>
<h2>ReduxPersist----{title}</h2>
<h3>count:{count}</h3>
<button onClick={(e) => dispatch(incrementAdd(10))}>-10</button>
</div>
);
});

export default ReduxPersist;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { DECREMENT } from "./constant";
import { fromJS } from "immutable";

const defaultState = fromJS({
count: 1000,
title: "redux 持久化测试",
});

const reducer = (preState = defaultState, actions) => {
const { type, count } = actions;
switch (type) {
case DECREMENT:
return preState.set("count", preState.get("count") - count * 1);
default:
return preState;
}
};

export default reducer;

此时会出现报错,提示没有 get 方法.

解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React, { memo } from "react";
import { useDispatch, useSelector, shallowEqual } from "react-redux";
import { incrementAdd } from "../store/persist_action";

const ReduxPersist = memo(() => {
const dispatch = useDispatch();
// **
const { count, title } = useSelector(
({ persistReducer: { count, title } }) => ({
count,
title,
}),
shallowEqual
);

//const { count, title } = useSelector(
// ({ persistReducer }) => ({
// count: persistReducer.get("count"),
// title: persistReducer.get("title"),
// }),
// shallowEqual
// );

return (
<div>
<h2>ReduxPersist----{title}</h2>
<h3>count:{count}</h3>
<button onClick={(e) => dispatch(incrementAdd(10))}>-10</button>
</div>
);
});

export default ReduxPersist;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { DECREMENT } from "./constant";
import { fromJS } from "immutable";

const defaultState = {
// **
count: 1000,
title: "redux 持久化测试",
};

const reducer = (preState = defaultState, actions) => {
const { type, count } = actions;
let mapObj = fromJS(preState); // **
switch (type) {
case DECREMENT:
// return preState.set('count', preState.get('count') - count * 1)
return mapObj.set("count", mapObj.get("count") - count * 1).toJS(); // **
default:
return preState;
}
};

export default reducer;

思路

由于 redux-persist 处理每次会返回普通对象,所以我们只能等要在 reducer 中处理状态时,我们先将其用 immutable 处理成它内部定制 Map 结构,然后我们再进行 set 操作修改,最后我们又将 Map 结构转换为普通对象输出,这样就完美的解决了这个问题。

Redux 和 VueX 的区别

相同点

  • 都是全局共享 state
  • 流程一致: 定义全局 state, 触发修改 state
  • 原句注入 store

不同点

  • 从实现原理上来说:
    • Redux 使用的是不可变数据,而 Vuex 的数据是可变的。Redux 每次都是用新的 state 替换旧的 state,而 Vuex 是直接修改
    • Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的,而 Vuex 其实和 Vue 的原理一样,是通过 getter/setter 来比较的
  • 从表现层来说:
    • vuex 定义了 state、getter、mutation、action 四个对象;redux 定义了 state、reducer、action。
    • vuex 中 state 统一存放,方便理解;reduxstate 依赖所有 reducer 的初始值
    • vuex 有 getter,目的是快捷得到 state;redux 没有这层,react-redux mapStateToProps 参数做了这个工作。
    • vuex 中 mutation 只是单纯赋值(很浅的一层);redux 中 reducer 只是单纯设置新 state(很浅的一层)。他俩作用类似,但书写方式不同
    • vuex 中 action 有较为复杂的异步 ajax 请求;redux 中 action 中可简单可复杂,简单就直接发送数据对象({type:xxx, your-data}),复杂需要调用异步 ajax(依赖 redux-thunk 插件)。
    • vuex 触发方式有两种 commit 同步和 dispatch 异步;redux 同步和异步都使用 dispatch

通俗点理解就是,vuex 弱化 dispatch,通过 commit 进行 store 状态的一次更变;取消了 action 概念,不必传入特定的 action 形式进行指定变更;弱化 reducer,基于 commit 参数直接对数据进行转变,使得框架更加简易;

共同思想

  • 单一数据源
  • 变化可以预测