Skip to main content

Redux 的工作原理与使用

一、Redux 的定义

Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。

1、设计思想

  • Web 应用是一个状态机,视图与状态是一一对应的;
  • 所有的状态保存在一个对象中。

2、原理

Redux 把公共数据都放在 store 中,这样当一个组件改变了 store 的数据,其他组件可以感知到 store 的变化并取得最新数据,从而间接的实现了这些数据的传递。

当组件需要修改 store 数据时,由于 Redux 只有一个单一的数据源,而且这个数据源是只读的,修改这个数据源必须通过 dispatch action,根据 action 的 type 和 text 匹配 Reducer,Reducer 会结合上一个 state 和 action 做出响应,返回新的 store,从而实现数据的修改。

3、应用场景

当组件有以下场景时,可以考虑使用 Redux:

  • 某个组件需要共享状态时
  • 某个状态需要在任何地方都可以被拿到时
  • 一个组件需要改变另一个组件的状态时
  • 一个组件需要改变全局状态时(语言切换、夜间模式、用户登陆等)

4、核心概念

4-1、Store

Store 就是保存数据的地方,可以把它看成一个容器,整个应用只能有一个 Store。Redux 提供 createStore 函数来创建 Store。

createStore(reducer, [preloadedState], [enhancer])
  • reducer (Function): 接收两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树。
  • preloadedState (any): 初始时的 state。
  • enhancer (Function): 用于组合 store creator 的高阶函数,返回一个新的强化过的 store creator。可以用中间间、时间旅行、持久化等来增强 store。Redux 中唯一内置的 store enhander 是 applyMiddleware()

举个例子:

import { createStore } from 'redux'
const store = createStore(fn)

上面 createStore 接受另一个函数 fn 作为参数,返回新生成的 Store 对象。

4-2、State

Store 对象包含所有数据,可以通过 store.getState() 拿到某个时点的数据,这种时点的数据集合,就叫做 State

import { createStore } from 'redux'
const store = createStore(fn)

const state = store.getState()

Redux 规定 一个 State 对应一个 View。只要 State 相同,View 就相同。

4-3、Action

State 的变化,会导致 View 的变化。但用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。

Action 是一个对象。其中的 type 属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。

const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
}

上面 Action 的名称是 ADD_TODO,它携带的信息是字符串 Learn Redux

可以这么理解,Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action,它会运送数据到 Store。

4-4、Action Creator

View 要发送多少种消息,就会有多少种 Action。如果都手写会很麻烦,可以定义一个函数来生成 Action,这个函数就叫 Action Creator

bindActionCreators(actionCreators, dispatch)
  • actionCreators (Function | Object): 一个 Action Creator,或 value 是 Action Creator 的对象;
  • dispatch (Function): 一个由 Store 实例提供的 dispatch 函数。

举个例子:

SomeComponent.js
import { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'

import * as TodoActionCreators from './TodoActionCreators'

class TodoListContainer extends Component {
constructor(props) {
super(props)

const { dispatch } = props

this.boundActionCreators = bindActionCreators(TodoActionCreators, dispatch)
console.log(this.boundActionCreators)
}

componentDidMount() {
// 由 react-redux 注入的 dispatch:
let { dispatch } = this.props

// 注意:这样是行不通的:
// TodoActionCreators.addTodo('Use Redux')

// 你只是调用了创建 action 的方法。
// 你必须要同时 dispatch action。

// 这样做是可行的:
let action = TodoActionCreators.addTodo('Use Redux')
dispatch(action)
}

render() {
// 由 react-redux 注入的 todos:
let { todos } = this.props

return <TodoList todos={todos} {...this.boundActionCreators} />
}
}

export default connect((state) => ({ todos: state.todos }))(TodoListContainer)

上面 addTodo 函数就是一个 Action Creator

4-5、dispatch

dispatch 是 View 发出 Action 的唯一方法。

import { createStore } from 'redux'
const store = createStore(fn)

store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
})

上面代码中,store.dispatch 接受一个 Action 对象作为参数,将它发送出去。

结合 Action Creator,这段代码可以改写如下。

store.dispatch(addTodo('Learn Redux'))

4-6、Reducer

Store 收到 Action 后,必须返回一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State

const reducer = function (state, action) {
// ...
return new_state
}

整个应用的初始状态,可以作为 State 的默认值。举个例子:

const defaultState = 0
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'ADD':
return state + action.payload
default:
return state
}
}

const state = reducer(1, {
type: 'ADD',
payload: 2
})

上面 reducer 函数收到名为 ADDAction 后,就返回一个新的 State,作为加法的计算结果。

实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch 方法会触发 Reducer 的自动执行。为此 Store 需要知道 Reducer 函数,做法就是在生成 Store 时将 Reducer 传入 createStore 方法:

import { createStore } from 'redux'
const store = createStore(reducer)

上面 createStore 接受 Reducer 作为参数,生成一个新的 Store。以后每当 store.dispatch 发来一个新的 Action 就会自动调用 Reducer,得到新的 State

注意:由于 Reducer 是纯函数,可以保证相同的 State 必定得到相同的 View。但也因为这一点,Reducer 函数中不能改变 State,必须返回一个全新的对象:

// State 是一个对象
function reducer(state, action) {
return Object.assign({}, state, { thingToChange })
// 或
return { ...state, ...newState }
}

// State 是一个数组
function reducer(state, action) {
return [...state, newItem]
}

最好把 State 对象设为只读,要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候与某个 View 对应的 State 总是一个不变的对象。

4-7、subscribe

Store 允许使用 subscribe 方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。

import { createStore } from 'redux'
const store = createStore(reducer)

store.subscribe(listener)

只要把 View 的更新函数放入 listener,就可以实现 View 的自动渲染。

store.subscribe(listener) 方法返回一个函数,调用这个函数就可以解除监听。

let unsubscribe = store.subscribe(
() => console.log(store.getState())
)

unsubscribe()

二、Redux 的使用

1、安装

# npm
npm install redux

# yarn
yarn add redux

2、基本用法

下面用 Redux 实现一个简单的计数器:

src/App.js
import React, { useState } from 'react'
import store from './store/index'

const App = () => {
const curCount = store.getState().count
const [count, setCount] = useState(curCount)

const handleAddCount = () => {
store.dispatch({
type: 'ADD_COUNT'
})
setCount(store.getState().count)
}

const handleSubCount = () => {
store.dispatch({
type: 'SUB_COUNT'
})
setCount(store.getState().count)
}

const handleMultiCount = () => {
store.dispatch({
type: 'MULTI_COUNT',
payload: store.getState().count
})
setCount(store.getState().count)
}

return (
<div>
<button onClick={handleAddCount}>+1</button>
<button onClick={handleSubCount}>-1</button>
<button onClick={handleMultiCount}>× last</button>
<span>{count}</span>
</div>
)
}

export default App

运行效果:

3、store 结构优化

3-1、分离出 Reducer

将 reducer 单独分离出来,方便管理:

src/store/index.js
import { createStore } from 'redux'
import reducer from './reducer'

const store = createStore(reducer)
export default store

3-2、分离出 action 处理逻辑

将 Reducer 中的判断及逻辑处理分离,方便管理:

src/store/reducer.js
const defaultState = {
count: 0
}

const handleAddCount = (state, action) => {
const newState = JSON.parse(JSON.stringify(state)) // 深拷贝
newState.count += 1
return newState
}

const handleSubCount = (state, action) => {
const newState = JSON.parse(JSON.stringify(state)) // 深拷贝
newState.count -= 1
return newState
}

const handleMultiCount = (state, action) => {
const newState = JSON.parse(JSON.stringify(state)) // 深拷贝
newState.count *= action.payload
return newState
}

export default function reducer(state = defaultState, action) {
switch (action.type) {
case 'ADD_COUNT':
return handleAddCount(state, action)
case 'SUB_COUNT':
return handleSubCount(state, action)
case 'MULTI_COUNT':
return handleMultiCount(state, action)
default:
return state
}
}

可以将上面 reducer 提取出来的处理逻辑单独放在一个 actions 文件中,方便管理:

src/store/reducer.js
import * as actions from './actions'

const defaultState = {
count: 0
}

export default function reducer(state = defaultState, action) {
switch (action.type) {
case 'ADD_COUNT':
return actions.handleAddCount(state, action)
case 'SUB_COUNT':
return actions.handleSubCount(state, action)
case 'MULTI_COUNT':
return actions.handleMultiCount(state, action)
default:
return state
}
}

3-3、分离出 actionTypes

action 拥有一个不变的 type 以便 reducer 能够识别它们,这个 action type 建议定义为 string 常量。例如:

const ADD_TODO = 'ADD_TODO'

这么做有以下好处:

  • 所有的 action type 汇总在同一位置,可以避免命名冲突,也便于寻找;
  • 当 import 的 action type 常量拼写错误,dispatch 这个 action 时会报错,可以快速定位问题。

可以将前面优化步骤的 action type 分离出来,写在一个文件中:

src/store/reducer.js
import * as actions from './actions'
import * as actionTypes from './actionTypes'

const defaultState = {
count: 0
}

export default function reducer(state = defaultState, action) {
switch (action.type) {
case actionTypes.ADD_COUNT:
return actions.handleAddCount(state, action)
case actionTypes.SUB_COUNT:
return actions.handleSubCount(state, action)
case actionTypes.MULTI_COUNT:
return actions.handleMultiCount(state, action)
default:
return state
}
}

3-4、使用 combineReducers 拆分 reducers

随着项目变得越来越复杂,可以将 reducer 函数拆分成多个函数来独立管理 state 的一部分。

combineReducers 可以把多个 reducer 函数合并成一个最终的 reducer 函数。合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。

combineReducers() 返回的 state 对象,会将传入的 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。举个例子:

rootReducer = combineReducers({
potato: potatoReducer,
tomato: tomatoReducer
})
// rootReducer 将返回如下的 state 对象
{
potato: {
// ... potatoes, 和由 potatoReducer 管理的 state 对象 ...
},
tomato: {
// ... tomatoes, 和由 tomatoReducer 管理的 state 对象,比如说 sauce 属性 ...
}
}

上面通过为传入对象的 reducer 命名不同的 key 来控制返回 state key 的命名。例如:

// 可以调用
combineReducers({ todos: myTodosReducer, counter: myCounterReducer })
// 将 state 结构变为
{ todos, counter }

// 通常的做法是命名 reducer,然后 state 再去分割那些信息,这样就可以使用 ES6 的简写方法:
combineReducers({ counter, todos })
// 这与
combineReducers({ counter: counter, todos: todos })
// 是等价的

接下来对上面 src/store/reducer.js 中 counter 拆分出来:

src/store/reducers/index.js
import { combineReducers } from 'redux'
import counter from './counter'

export default combineReducers({
counter,
// ... 其它拆分的 reducers 函数
})

拆分完,记得更新原 reducer 的引入以及引用的 reducer state 的值:

src/App.js
import React, { useState } from 'react'
import store from './store/index'

const App = () => {
const curCount = store.getState().count
const curCount = store.getState().counter.count
const [count, setCount] = useState(curCount)

const handleAddCount = () => {
store.dispatch({
type: 'ADD_COUNT'
})
setCount(store.getState().count)
setCount(store.getState().counter.count)
}

const handleSubCount = () => {
store.dispatch({
type: 'SUB_COUNT'
})
setCount(store.getState().count)
setCount(store.getState().counter.count)
}

const handleMultiCount = () => {
store.dispatch({
type: 'MULTI_COUNT',
payload: store.getState().count
payload: store.getState().counter.count
})
setCount(store.getState().count)
setCount(store.getState().counter.count)
}

return (
<div>
<button onClick={handleAddCount}>+1</button>
<button onClick={handleSubCount}>-1</button>
<button onClick={handleMultiCount}>× last</button>
<span>{count}</span>
</div>
)
}

export default App

3-5、全局注入 store

上面是直接在组件中 import store,也可以直接在最外层容器组件中初始化 store,然后将 state 上的属性作为 props 层层传递下去。但更好的方法是使用 react-redux 提供的 Provider 全局注入。

首先安装 react-redux 库:

# npm
npm install react-redux

# yarn
yarn add react-redux

注入方式如下:

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App'
import store from './store'
import { Provider } from 'react-redux'

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

注意:如果使用 TS 的话,可能会出现以下错误:

需要做以下处理

src/store/index.ts
import { createStore } from 'redux'
import reducers from './reducers/index'

const store = createStore(reducers)
export default store

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

然后新建 hooks 文件

src/store/hooks.ts
import type { TypedUseSelectorHook} from 'react-redux';
import { useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './index'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

在组件引入新声明的 hooks

src/App.tsx
import React from 'react'
import './index.scss'
import { useSelector, useDispatch } from 'react-redux'
import { useAppSelector, useAppDispatch } from './store/hooks'

const App = () => {
const count = useSelector((state) => state.counter.count)
const count = useAppSelector((state) => state.counter.count)
const dispatch = useDispatch()
const dispatch = useAppDispatch()

const handleAddCount = () => {
dispatch({ type: 'ADD_COUNT' })
}

const handleSubCount = () => {
dispatch({ type: 'SUB_COUNT' })
}

const handleMultiCount = () => {
dispatch({ type: 'MULTI_COUNT', payload: count })
}

return (
<div>
<button onClick={handleAddCount}>+1</button>
<button onClick={handleSubCount}>-1</button>
<button onClick={handleMultiCount}>× last</button>
<span>{count}</span>
</div>
)
}

export default App

react-redux 还可以使用 useStore 的 Hook 来引入 store,然后进行原始的 store 操作。

点击查看 Redux 结构优化完整代码

三、Rudux 生态

1、Redux Toolkit

Redux Toolkit 是 Redux 官方提供的编写 Redux 逻辑的方法。它简化了大多数 Redux 任务,防止了常见错误,并使编写 Redux 应用程序变得更加容易。

安装:

# npm
npm install @reduxjs/toolkit

# yarn
yarn add @reduxjs/toolkit

1-1、configureStore

configureStore 用于包装 createStore。可以组合切片 reducers、添加 Redux 中间件,集成并默认开启 redux-thunk、Redux DevTools 扩展。

src/store/index.js
import { createStore } from 'redux'
import { configureStore } from '@reduxjs/toolkit'
import reducers from './reducers/index'

const store = createStore(reducers)
const store = configureStore({
reducer: reducers
})
export default store

1-2、createAction()

createAction 用于创建一个 action。可以直接传入 action type 字符串来替代 type 常量,返回 action 函数。使用如下:

src/store/reducers/counter.js
import * as actions from '../actions'
import * as actionTypes from '../actionTypes'
import { createAction } from '@reduxjs/toolkit'

export const addCount = createAction('ADD_COUNT')
export const subCount = createAction('SUB_COUNT')
export const multiCount = createAction('MULTI_COUNT')

const defaultState = {
count: 0
}

export default function counter(state = defaultState, action) {
switch (action.type) {
case actionTypes.ADD_COUNT:
case addCount.type:
return actions.handleAddCount(state, action)
case actionTypes.SUB_COUNT:
case subCount.type:
return actions.handleSubCount(state, action)
case actionTypes.MULTI_COUNT:
case multiCount.type:
return actions.handleMultiCount(state, action)
default:
return state
}
}

1-3、createReducer()

createReducer 用于创建一个 reducer。将 action type 映射到 case reducer 函数中,不用再写 switch-case,而且集成的 immer 可以编写更简单的 immutable 更新,例如 state.todos [3] .completed = true

src/store/reducers/counter.js
import * as actions from '../actions'
import { createAction } from '@reduxjs/toolkit'
import { createAction, createReducer } from '@reduxjs/toolkit'

export const addCount = createAction('ADD_COUNT')
export const subCount = createAction('SUB_COUNT')
export const multiCount = createAction('MULTI_COUNT')

const defaultState = {
count: 0
}

export default function counter(state = defaultState, action) {
switch (action.type) {
case addCount.type:
return actions.handleAddCount(state, action)
case subCount.type:
return actions.handleSubCount(state, action)
case multiCount.type:
return actions.handleMultiCount(state, action)
default:
return state
}
}

const counter = createReducer(defaultState, {
[addCount]: (state, action) => actions.handleAddCount(state, action),
[subCount]: (state, action) => actions.handleSubCount(state, action),
[multiCount]: (state, action) => actions.handleMultiCount(state, action)
})

export default counter

1-4、createSlice()

createSlice 用于创建一个 slice。接受一个 reducer 函数的对象、分片名称和初始状态值,并自动生成具有相应 action creators 和 action 类型的分片 reducer;

因此上面的 createAction 可以进一步优化掉

src/store/reducers/counter.js
import * as actions from '../actions'
import { createAction, createReducer } from '@reduxjs/toolkit'
import { createSlice } from '@reduxjs/toolkit'

const defaultState = {
count: 0
}

export const addCount = createAction('ADD_COUNT')
export const subCount = createAction('SUB_COUNT')
export const multiCount = createAction('MULTI_COUNT')

const counter = createReducer(defaultState, {
[addCount]: (state, action) => actions.handleAddCount(state, action),
[subCount]: (state, action) => actions.handleSubCount(state, action),
[multiCount]: (state, action) => actions.handleMultiCount(state, action)
})

const counter = createSlice({
name: 'counter',
initialState: defaultState,
reducers: {
addCount: (state, action) => actions.handleAddCount(state, action),
subCount: (state, action) => actions.handleSubCount(state, action),
multiCount: (state, action) => actions.handleMultiCount(state, action)
}
})

export default counter

注意 createSlice 创建出的 slice 的 reducer 属性值才是 reducer

src/store/index.js
import { combineReducers } from 'redux'
import counter from './counter'

export default combineReducers({
counter
counter: counter.reducer
})

然后在组件中引入 slice 的 actions 属性值提取供 dispatch 使用的新 Action

src/App.jsx
import React from 'react'
import './index.scss'
import { useSelector, useDispatch } from 'react-redux'
import { addCount, subCount, multiCount } from './store/reducers/counter'
import counter from './store/reducers/counter'

const App = () => {
const count = useSelector((state) => state.counter.count)
const dispatch = useDispatch()

const handleAddCount = () => {
dispatch(addCount())
dispatch(counter.actions.addCount())
}

const handleSubCount = () => {
dispatch(subCount())
dispatch(counter.actions.subCount())
}

const handleMultiCount = () => {
dispatch(multiCount(count))
dispatch(counter.actions.multiCount(count))
}

return (
<div>
<button onClick={handleAddCount}>+1</button>
<button onClick={handleSubCount}>-1</button>
<button onClick={handleMultiCount}>× last</button>
<span>{count}</span>
</div>
)
}

export default App

1-5、createAsyncThunk

createAsyncThunk 创建一个 thunk。用于处理异步 Action,接受 action type 和 payload,返回 promise,并生成一个发起基于该 promise 的 pending/fulfilled/rejected action 类型的 thunk;

创建 thunk 如下

src/store/reducers/counter.js
import * as actions from '../actions'
import { createSlice } from '@reduxjs/toolkit'
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'

const defaultState = {
count: 0
}

export const fetchTemperature = createAsyncThunk(
'counter/fetchTemperature',
async (city) => {
const res = await axios.get(
`http://wthrcdn.etouch.cn/weather_mini?city=${city}`
)
return res.data
}
)

const counter = createSlice({
name: 'counter',
initialState: defaultState,
reducers: {
addCount: (state, action) => actions.handleAddCount(state, action),
subCount: (state, action) => actions.handleSubCount(state, action),
multiCount: (state, action) => actions.handleMultiCount(state, action)
},
extraReducers: {
[fetchTemperature.fulfilled]: (state, action) => {
console.log(action)
}
}
})

export default counter

注意上面创建的 thunk 是放在 extraReducers 下。使用 thunk 如下

src/App.jsx
import React from 'react'
import './index.scss'
import { useSelector, useDispatch } from 'react-redux'
import counter from './store/reducers/counter'
import counter, { fetchTemperature } from './store/reducers/counter'

const App = () => {
const count = useSelector((state) => state.counter.count)
const dispatch = useDispatch()

const handleAddCount = () => {
dispatch(counter.actions.addCount())
}

const handleSubCount = () => {
dispatch(counter.actions.subCount())
}

const handleMultiCount = () => {
dispatch(counter.actions.multiCount(count))
}

const handleFetchTemperature = () => {
dispatch(fetchTemperature('广州'))
}

return (
<div>
<button onClick={handleAddCount}>+1</button>
<button onClick={handleSubCount}>-1</button>
<button onClick={handleMultiCount}>× last</button>
<button onClick={handleFetchTemperature}>获取广州温度</button>
<span>{count}</span>
</div>
)
}

export default App

点击“获取广州温度”按钮输出结果:

1-6、createEntityAdapter

createEntityAdapter 用于生成一组可重用的 reducers 和 selectors,来管理规范化 store 数据。换句话说,就是利用所创建 Adapter 的 API 来操作 state。由 createEntityAdapter 方法生成的 entity state 结构如下:

{
// 每个对象唯一的 id(string | number)
// 可以在 createEntityAdapter 的 selectId 中设置对象的某个属性为该 id(索引值)
ids: [],
// 映射的实体对象
entities: {}
}

创建 Adapter 如下

src/store/reducers/counter.js
import * as actions from '../actions'
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import { createSlice, createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit'
import axios from 'axios'

const defaultState = {
count: 0,
temperature: 0
}

export const fetchTemperature = createAsyncThunk(
'counter/fetchTemperature',
async (city) => {
const res = await axios.get(
`http://wthrcdn.etouch.cn/weather_mini?city=${city}`
)
return res.data
}
)

const playersAdapter = createEntityAdapter({
// 将数组对象中每个对象的 id 属性存放在 Adapter 的 IDs 中
selectId: (player) => player.id,
// 将 IDs 中对应的每个对象以 id 为指定规则进行排序
sortComparer: (a, b) => a - b
})

const counter = createSlice({
name: 'counter',
initialState: defaultState,
initialState: playersAdapter.getInitialState(defaultState),
reducers: {
addCount: (state, action) => actions.handleAddCount(state, action),
subCount: (state, action) => actions.handleSubCount(state, action),
multiCount: (state, action) => actions.handleMultiCount(state, action),
playerAdd: playersAdapter.addOne
},
extraReducers: {
[fetchTemperature.fulfilled]: (state, action) => {
actions.handleGetTemperature(state, action)
}
}
})

export default counter
addOne API 使用
src/App.jsx
<div className="player-container">
<button
onClick={() =>
dispatch(
counter.actions.playerAdd({ id: '006', name: 'UZI' })
)
}
>
playerAdd006
</button>
<button
onClick={() =>
dispatch(
counter.actions.playerAdd({ id: '001', name: 'clearlove' })
)
}
>
playerAdd001
</button>
</div>

点击效果如下:

点击查看更多 Adapter API

1-7、createSelector 组件

来自 Reselect 库,被重新导出,用于 state 缓存,防止不必要的计算。

举个例子:

const testState = {
a: 1,
b: 2,
c: 3
}

const addValue1 = createSelector(
(state) => state.a,
(state) => state.b,
(state) => state.c,
(value1, value2, value3) => value1 + value2 + value3
)

const addValue2 = createSelector(
[(state) => state.a, (state) => state.b, (state) => state.c],
(value1, value2, value3) => value1 + value2 + value3
)

console.log(addValue1(testState)) // 6
console.log(addValue2(testState)) // 6

上面在 createSelector 的最后一个参数中进行前面参数结果的缓存处理。

点击查看 Redux Toolkit 写法完整代码

2、rematch

rematch 是对 redux 的二次封装,简化了 redux 的使用,极大的提高了开发体验(没有多余的 action typesaction creatorsswitch 语句或 thunks

rematch 的优点:

  • 省略了 action types

    不必再多次写字符串,转而使用 model/method 代替;

  • 省略了 action creators

    可以直接调用方法,不必再生产 action type,转而使用 dispatch.model.method 代替;

  • 省略了 switch 语句

    调用 model.method 方法,不必判断 action type;

  • 集中书写状态,同步和异步方法

    在一个 model 中使用 state、reducers 和 effects 来写状态,同步和异步方法

rematch 的安装:

# npm
npm install @rematch/core
# yarn
yarn add @rematch/core

2-1、init × models

init 使用 models 属性初始化 store,需要配合以下格式的 modal:

const count = {
state: 0,
reducers: {
upBy: (state, payload) => state + payload
},
effects: {
async loadData(payload, rootState) {
const response = await fetch('xx/data')
const data = await response.json()
console.log(data)
}
}
}

init({
models: {
count
}
})

原 redux 写法改造如下

src/store/index.js
import { createStore, combineReducers } from 'redux'
import { init } from '@rematch/core'
import counter from './reducers/counter'

const store = createStore(
combineReducers({
counter
})
)
const store = init({
models: {
counter
}
})

export default store

2-2、init × redux

init 初始化 store 的过程中,可以通过 redux 这个属性来兼容老项目中的 redux 配置。

使用 init 替代 createStore 创建 store

src/store/index.js
import { createStore, combineReducers } from 'redux'
import { init } from '@rematch/core'
import counter from './reducers/counter'

const store = createStore(
combineReducers({
counter
})
)
const store = init({
redux: {
reducers: {
counter
}
}
})

export default store

点击查看 rematch 写法完整代码

3、reduxSaga

Redux 拥抱函数式编程,在处理异步时并不是很友好,但社区中有许多优秀的开源库可以直接使用,例如 Redux-Saga