Redux 是一款小巧的 JavaScript 状态容器,提供可预测化的状态管理。Redux 常见于 React 应用的数据状态管理,但是 Redux 不仅仅局限于 React,还支持其它 UI 库。

Redux 应用示例

在 Redux 中,应用的整体全局状态以对象树的方式存放于单个 store 中。唯一改变状态树(state tree)的方法是创建 action。action 是一个描述发生了什么的对象,并将其 dispatch 给 store。要指定状态树如何响应 action 来进行更新,可以编写纯 reducer 函数,这些函数根据旧 state 和 action 来计算新 state。

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
import { createStore } from 'redux'

/**
* 这是一个 reducer 函数:接受当前 state 值和描述“发生了什么”的 action 对象,它返回一个新的 state 值。
* reducer 函数签名是 : (state, action) => newState
*
* Redux state 应该只包含普通的 JS 对象、数组和基本类型。
* 根状态值通常是一个对象。 重要的是,不应该改变 state 对象,而是在 state 发生变化时返回一个新对象。
*
* 你可以在 reducer 中使用任何条件逻辑。 在这个例子中,我们使用了 switch 语句,但这不是必需的。
*
*/
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case 'counter/incremented':
return { value: state.value + 1 }
case 'counter/decremented':
return { value: state.value - 1 }
default:
return state
}
}

// 创建一个包含应用程序 state 的 Redux store。
// 它的 API 有 { subscribe, dispatch, getState }.
let store = createStore(counterReducer)

// 你可以使用 subscribe() 来更新 UI 以响应 state 的更改。
// 通常你会使用视图绑定库(例如 react-redux)而不是直接使用 subscribe()。
// 可能还有其他用例对 subscribe 也有帮助。

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

// 改变内部状态的唯一方法是 dispatch 一个 action。
// 这些 action 可以被序列化、记录或存储,然后再重放。
store.dispatch({ type: 'counter/incremented' })
// {value: 1}
store.dispatch({ type: 'counter/incremented' })
// {value: 2}
store.dispatch({ type: 'counter/decremented' })
// {value: 1}

Redux 的应用场景

  1. 同一个 state 需要在多个 Component 中共享,例如应用需要登录,登录后的用户信息可以存放于 store 中
  2. 需要操作一些全局性的常驻 Component,比如 Notifications,Tooltips 等
  3. 太多 props 需要在组件树中传递,但其中大部分只是为了透传给子组件
  4. 业务太复杂导致 Component 文件太大,可以考虑将业务逻辑拆出来放到 Reducer 中

不适用的场景:
使用 Redux 需要创建很多模版代码,会让 state 的更新变得非常繁琐,如果应用数据流向比较简单,可以不使用 Redux 。

Redux 数据流向

严格的单向数据流是 Redux 架构的设计核心。Redux 应用中数据的生命周期遵循下面 4 个步骤:

  1. View 中的某个操作调用 store.dispatch(action)
  2. Redux store 调用传入的 reducer 函数,入参为 当前的 state 和 action。
  3. 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
  4. Redux store 保存了根 reducer 返回的完整 state 树。

Redux 数据流向

redux_inner

带 middleware 的 Redux 数据流向

redux_middleware_inner

Action: Action 是把数据从应用传到 store 的有效载荷,本质上是 JavaScript 普通对象。action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。
Reducer: Reducer 指定了应用状态的变化如何响应 action 并发送到 store 的。它是一个纯函数,接收旧的 state 和action,返回新的 state 。
State: State 记录了应用的状态。在 Redux 应用中,所有的 state 都被保存在一个单一对象中,即 store 中。
Store: Redux 应用只有一个单一的 store,它维持应用的 state,提供 getState() 方法获取 state;提供 dispatch(action) 方法更新 state;通过 subscribe(listener) 注册监听器;通过 subscribe(listener) 返回的函数注销监听器。

Redux 和 Flux 的区别

Flux

  • Flux 是用于构建用户界面的应用程序架构,通过单向数据流补充 React 可组合的视图组件
  • Flux 更像模式而非框架,没有任何硬依赖
  • Flux 架构的应用包含 4 部分
    • Action
      • 通过 Action creators 创建
      • 每个 Action 拥有 type 或类似属性
      • 传递给 Dispatcher
    • Dispatcher
      • 分发 Actions 给所有注册 Store 的回调函数
    • Store
      • 接受 Action 更新数据后,触发 change 事件,通知 View
      • 可以由多个 Store
    • View 视图组件,即 Controller-View
      • change 事件,从 Store 取回数据,将数据传递给子组件或更新组件状态
      • 响应用户输入,生成新的 Action

Redux

  • Redux 是 JavaScript 应用的可预测状态容器
  • Redux 对不同框架都有完整实现,Facebook 官方推荐使用代替 Flux
  • Redux 架构与 Flux 基本一致,但做了简化
    • State 只读,更改 State 的方式是返回新的对象,即引入 Reducer 纯函数
    • Action 与 Dispatcher ,只需返回包含 type 和 payload 属性的对象
    • Store
      • store 唯一
      • createStore 基于 Reducer 纯函数创建
      • store.dispatch() 调用 Action
    • View
      • 通过 store.getState() 获取最新状态
      • 通过 store.subscribe() 订阅状态更新
        • store.subscribe() 返回函数可取消订阅

综上,Redux 与 Flux 都基于单向数据流,架构相似,但 Redux 默认应用只有唯一 Store,精简掉 Dispatcher,引入 Reducer 纯函数,通过返回新对象,而不是更改对象,更新状态。

对比 Flux 的官方实现,Redux 的 API 更简洁,并且提供了如 combineReducers 等工具函数及 React-Toolkit 工具集,以及对状态的撤销、重做和持久化等更复杂的功能。提供如 React-Redux 等简化 Redux 与其他第三方库的连接。

Redux 和 Vuex 的区别

Vuex 和 Redux 的本质思想是一致的,均是将数据从视图中抽离的方案,通过单一的数据源 store 和可预测的数据变化反馈视图的改变。

  1. Redux 不仅仅局限于 React,还支持其它 UI 库;Vuex 和 Vue 深度绑定
  2. Vuex 定义了 state、getter、mutation、action 四个对象;Redux 定义了 store、reducer、action
  3. Vuex 事件触发方式 包括 commit 同步和 dispatch 异步;Redux 同步和异步都是用 dispatch
  4. Vuex 中 state 统一存放;Redux 中 store 依赖所有 reducer 的初始值
  5. Vuex 中有 getter 可以便捷的得到 state; React-redux 中 mapStateToProps 参数做了这个工作
  6. Vuex 中的 action 可以使用异步 ajax 请求;Redux 中的 action 仅支持发送数据对象,异步 ajax 需要使用 redux-thunk 或 redux-saga 等第 middleware
  7. Redux 中的是不可变数据,Vuex 中的数据是可变的。Redux 每次使用新的 store 替换旧的 store,Vuex 是直接修改 state
  8. Redux 在检测数据变化的时候,是要通过 diff 的方式进行比较的,而 Vuex 是通过 getter/setter 来比较的

Redux 和 Mobx 的区别

  1. Redux 的编程范式是函数式;Mobx 是面向对象的
  2. Redux 中的数据是不可变对象,每次更新返回一个新的数据;Mobx 中的数据从始至终都是同一份引用
  3. Mobx 没有全局的状态树,状态分散在各个独立的 store 中;Redux 中的 store 是全局唯一的
  4. Mobx 相对 Redux 来说会少些工程化模板
  5. Mobx 中使用 async/await 来处理异步逻辑;Redux 需要使用第三方 middleware

Redux 的核心原则

Redux 设计和使用遵循三个基本原则:

  • 单一数据源,Store 唯一
    整个应用程序的状态 State 存储在单一对象树 Object tree 中
    Object tree 只存在唯一的 Store 中
    单一对象树让跟踪状态的时间变化,调试和检查应用程度都更加容易
  • 状态是只读的
    Redux 假设开发者永远不会更改数据,而是在 Reducer 中返回新对象来更新状态
    更改状态的唯一方法是发出一个动作 Action,Action 是对已发生事情的抽象描述的对象
    数据变更,如用户输入和网络请求都不能直接更改状态
  • Reducer 是纯函数,用来归并状态 State
    接受原状态和 Action,返回新状态 reducer(state, action) => new State
    纯函数,无副作用,输出和输入一一对应,与执行上下文、时间、调用次数无关。不应在函数内请求 API,操作 DOM,使用 Date.now() 等时间耦合方法或随机值

React Context 和 Redux 的区别

ReactContext

  • React Context API 是为了解决跨组件层级传递 props 的效率问题
  • 试验性的 Context API 存在问题
    • 提供数据源的父组件和接收数据的子组件间的某个组件的 shouldComponentUpdate 返回 false 跳过更新,子组件也会被动跳过更新
  • ContextAPI 正式在 React16.3 引入,使用方法
    • 创建 context 对象实例: const MyContext = React.createContext(defaultValue)
      订阅 context 的变化: 使用 <MyContext.Provider value={/* 某个值 */}> 组件去声明想要传递的数据源
    • 消费数据
      ​高阶组件写法 <MyContext.Consumer>{vaule=> /* 基于 context 值进行渲染 */ }</Consumer>
      Hook写法 const value = useContext(MyContext)

Redux

  • Redux 是 JavaScript 应用的可预测状态容器
  • Redux 使用方法

    • 声明 reducer 函数
      1
      2
      3
      4
      5
      6
      7
      const reducer =(state, action) =>{
      switch(action.type){
      case 'INCREMENT': return state + 1;
      case 'DECREMENT': return state - 1;
      default: return state;
      }
      }
    • 创建 store 对象
      1
      2
      import { createStore } from 'redux';
      const store = createStore(reducer);
    • 使用 state dispatch
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      const MyComponent = ({ value, onIncrement, onDecrement })=> (
      <>
      {value}
      <button onClick={onIncrement}>+</button>
      <button onClick={onDecrement}>-</button>
      </>
      );
      const App = ()=> (
      <MyComponent
      value={store.getState()}
      onIncrement={() => store.dispatch({type: 'INCREMENT'})}
      onDecrement={()=> store.dispatch({type: 'DECREMENT'})}
      />
      )
  • 目的

    • ReactContext 解决跨组件层级传递 props 的效率问题
    • Redux 是 JavaScript 应用的可预测状态容器,拥有完整的状态管理功能
  • 更新机制
    • ReactContext:Context 的 value 更新,它内部所有消费组件都会重新渲染,并且不受制于 shouldComponentUpdate 函数,需要手动优化
      • 避免使用对象字面量作为 value
      • 拆分 Context
      • 记忆化
      • 使用 createContext 的第二参数手动优化
    • Redux
      • 只有当前组件所消费的状态值改变,当前组件才会被更新
  • 调试
    • ReactContext 支持 ReactDevTools 调试
    • Redux 支持 Redux DevTools 调试
      • 可以方便地跟踪应用的状态何时、何处、为什么及如何改变
      • Redux 架构允许开发者记录更改,使用“时间旅行调试”
  • 中间件
    • ReactContext 不支持中间件
    • Redux 支持 applyMiddleware 将所有中间件组成一个数据,依次执行,最后执行 store.dispatch,依靠中间件扩展 Redux 功能,如简化异步操作等

React 访问 ReduxStore 的方法

  • connect:适合 React 组件访问 ReduxStore
    1
    2
    3
    4
    5
    import React from 'react'
    import { connect } from 'react-redux'
    const MyComponent = ({value}) => <>{value}</>
    const mapStateToProps= ({value}) => ({value})
    export connect(mapStateToProps)(MyComponent)
  • 导出 store:适合非服务端渲染
    • 创建 store 并导出
      1
      2
      3
      4
      import { createStore }from 'redux'
      import reducer from './reducer'
      conststore = createStore(reducer)
      export defaultstore
    • 引入 sotre 通过 getState 获取状态
      1
      2
      3
      4
      5
      import store from'./store'
      export function get(){
      const state = store.getState()
      const { value } = state
      }
  • 使用 redux-thunk 的第二参数 getState
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import { createStore, applyMiddleware } from 'redux'
    import thunk from 'redux-thunk'
    import reducer from'./reducer'
    const store = createStore(reducer, applyMiddleware(thunk))
    const get = () => (dispatch, getState) => {
    dispatch({type: 'getStart'})
    const { token } = getState()
    fetch('/user/info', {
    method: 'GET',
    header: {
    Authorization: `Bearer ${token}`
    }
    }).then(res=>res.json()).then(json => {
    dispatch({type: 'getSuccess', payload: {user: json}})
    })
    }
    store.dispatch(get())
  • 手写中间件 middleware,截获 action 的 payload,或者直接输出 getState 方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import { createStore, applyMiddleware } from 'redux'
    import reducer from './reducer'
    const payload = null
    const myMiddleware = store => next => action => {
    if (typeof action === 'function') {
    return action(store)
    } else {
    payload = action.payload
    }
    return next(action)
    }
    const store = createStore(reducer, applyMiddleware(myMiddleware))
    store.dispatch(store => store.getState())// 直接调用 store 的方法
    store.dispatch({
    type: 'start',
    payload : 'payload'
    })
    console.log(payload) // payload

Redux 中异步请求数据时发送多 Action 方法

异步请求数据等异步操作通常要发出三种 Action

  • 操作发起时 Action,以 start 为例
  • 操作成功时 Action,以 success 为例
  • 操作失败时 Action,以 failure 为例

发送多 Action 方法:

  • mapDispatchToprops
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const mapDispatchToprops = dispatch => {
    return {
    asyncAction() {
    dispatch({ type: 'start' })
    fetch('/api').then(res => res.json).then(data => {
    dispatch({ type: 'success', payload: { data } })
    }).catch(error=> {
    dispatch({ type: 'failure', payload: { error } })
    }))
    }
    }
    }
  • redux-thunk,使 dispatch 支持传入函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import { createStore, applyMiddleware } from 'redux'
    import thunk from 'redux-thunk'
    import reducer from './reducer'
    const store = createStore(reducer, applyMiddleware(thunk))
    const asyncAction = dispatch => {
    dispatch({ type: 'start' })
    fetch('/api').then(res => res.json).then(data => {
    dispatch({ type: 'success', payload: { data } })
    }).catch(error => {
    dispatch({ type: 'failure', payload: { error } })
    }))
    }
    store.dispatch(asyncAction())
  • redux-promise,使 dispatch 支持传入 Promise

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import { createStore, applyMiddleware } from 'redux'
    import promiseMiddleware from 'redux-promise'
    import reducer from './reducer'
    const stroe = createStore(reducer, applyMiddleware(promiseMiddleware))
    const asyncAction = dispatch => fetch('/api').then(res =>res.json).then(data => {
    dispatch({ type: 'success', payload: { data } })
    }).catch(error => {
    dispatch({ type: 'failure', payload: { error } })
    }))
    store.dispatch({ type: 'start' })
    store.dispatch(asyncAction())
  • redux-saga,采用 Generator 生成器函数支持异步多 Action

    sagas.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
    function* asyncAction() {
    try {
    const data= yield call('/api')
    yield put({ type: 'success', payload: { data } })
    } catch(error){
    yield put({ type: 'failure', payload: { error } })
    }
    }
    function* mySaga(){ // 支持并发
    yield takeEvery('start', asyncAction)
    }
    function* mySage() { //不支持并发,前个处理中的相同 type 的 Action 会被取消
    yield takeLatest('start', asyncAction)
    }
    export default mySage
main.js
1
2
3
4
5
6
7
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'react-saga'
import reducer from './reducer'
import mySaga from './sagas'
const sagaMiddleware= createSagaMiddleware()
conststore = createStore(reducer, applyMiddleware(sagaMiddleware))
sagaMiddleware.run(mySaga)

如何判断项目需要引入 Redux

并非所有应用程序都需要 Redux,是否引入 Redux 由以下决定

  • 正在构建的应用程序类型
  • 需要解决的问题类型
  • 哪些工具可以更好地解决问题

Redux 可以共享和管理状态

  • 通过可预测的行为来帮助回答:状态何时、何处、为什么及如何改变
  • 增加概念、代码和限制,增加学习成本和项目复杂度

平衡利弊,在以下情况引入 Redux 最有用

  • 应用程序许多地方都需要状态
  • 应用程序的状态经常更新
  • 更新状态的逻辑比较复杂
  • 项目较大,需要比较多的人协作
  • 想查看状态何时、何处、为什么及如何改变