Redux 是一款小巧的 JavaScript 状态容器,提供可预测化的状态管理。Redux 常见于 React 应用的数据状态管理,但是 Redux 不仅仅局限于 React,还支持其它 UI 库。
Redux 应用示例
在 Redux 中,应用的整体全局状态以对象树的方式存放于单个 store 中。唯一改变状态树(state tree)的方法是创建 action。action 是一个描述发生了什么的对象,并将其 dispatch 给 store。要指定状态树如何响应 action 来进行更新,可以编写纯 reducer 函数,这些函数根据旧 state 和 action 来计算新 state。
1 | import { createStore } from 'redux' |
Redux 的应用场景
- 同一个 state 需要在多个 Component 中共享,例如应用需要登录,登录后的用户信息可以存放于 store 中
- 需要操作一些全局性的常驻 Component,比如 Notifications,Tooltips 等
- 太多 props 需要在组件树中传递,但其中大部分只是为了透传给子组件
- 业务太复杂导致 Component 文件太大,可以考虑将业务逻辑拆出来放到 Reducer 中
不适用的场景:
使用 Redux 需要创建很多模版代码,会让 state 的更新变得非常繁琐,如果应用数据流向比较简单,可以不使用 Redux 。
Redux 数据流向
严格的单向数据流是 Redux 架构的设计核心。Redux 应用中数据的生命周期遵循下面 4 个步骤:
- View 中的某个操作调用
store.dispatch(action)
。 - Redux store 调用传入的 reducer 函数,入参为 当前的 state 和 action。
- 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
- Redux store 保存了根 reducer 返回的完整 state 树。
Redux 数据流向
带 middleware 的 Redux 数据流向
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
- 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 和可预测的数据变化反馈视图的改变。
- Redux 不仅仅局限于 React,还支持其它 UI 库;Vuex 和 Vue 深度绑定
- Vuex 定义了 state、getter、mutation、action 四个对象;Redux 定义了 store、reducer、action
- Vuex 事件触发方式 包括 commit 同步和 dispatch 异步;Redux 同步和异步都是用 dispatch
- Vuex 中 state 统一存放;Redux 中 store 依赖所有 reducer 的初始值
- Vuex 中有 getter 可以便捷的得到 state; React-redux 中 mapStateToProps 参数做了这个工作
- Vuex 中的 action 可以使用异步 ajax 请求;Redux 中的 action 仅支持发送数据对象,异步 ajax 需要使用 redux-thunk 或 redux-saga 等第 middleware
- Redux 中的是不可变数据,Vuex 中的数据是可变的。Redux 每次使用新的 store 替换旧的 store,Vuex 是直接修改 state
- Redux 在检测数据变化的时候,是要通过 diff 的方式进行比较的,而 Vuex 是通过 getter/setter 来比较的
Redux 和 Mobx 的区别
- Redux 的编程范式是函数式;Mobx 是面向对象的
- Redux 中的数据是不可变对象,每次更新返回一个新的数据;Mobx 中的数据从始至终都是同一份引用
- Mobx 没有全局的状态树,状态分散在各个独立的 store 中;Redux 中的 store 是全局唯一的
- Mobx 相对 Redux 来说会少些工程化模板
- 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)
- 创建 context 对象实例:
Redux
- Redux 是 JavaScript 应用的可预测状态容器
Redux 使用方法
- 声明 reducer 函数
1
2
3
4
5
6
7const reducer =(state, action) =>{
switch(action.type){
case 'INCREMENT': return state + 1;
case 'DECREMENT': return state - 1;
default: return state;
}
} - 创建 store 对象
1
2import { createStore } from 'redux';
const store = createStore(reducer); - 使用 state dispatch
1
2
3
4
5
6
7
8
9
10
11
12
13
14const 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'})}
/>
)
- 声明 reducer 函数
目的
- ReactContext 解决跨组件层级传递 props 的效率问题
- Redux 是 JavaScript 应用的可预测状态容器,拥有完整的状态管理功能
- 更新机制
- ReactContext:Context 的 value 更新,它内部所有消费组件都会重新渲染,并且不受制于 shouldComponentUpdate 函数,需要手动优化
- 避免使用对象字面量作为 value
- 拆分 Context
- 记忆化
- 使用 createContext 的第二参数手动优化
- Redux
- 只有当前组件所消费的状态值改变,当前组件才会被更新
- ReactContext:Context 的 value 更新,它内部所有消费组件都会重新渲染,并且不受制于 shouldComponentUpdate 函数,需要手动优化
- 调试
- ReactContext 支持 ReactDevTools 调试
- Redux 支持 Redux DevTools 调试
- 可以方便地跟踪应用的状态何时、何处、为什么及如何改变
- Redux 架构允许开发者记录更改,使用“时间旅行调试”
- 中间件
- ReactContext 不支持中间件
- Redux 支持 applyMiddleware 将所有中间件组成一个数据,依次执行,最后执行
store.dispatch
,依靠中间件扩展 Redux 功能,如简化异步操作等
React 访问 ReduxStore 的方法
- connect:适合 React 组件访问 ReduxStore
1
2
3
4
5import 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
4import { createStore }from 'redux'
import reducer from './reducer'
conststore = createStore(reducer)
export defaultstore - 引入 sotre 通过 getState 获取状态
1
2
3
4
5import store from'./store'
export function get(){
const state = store.getState()
const { value } = state
}
- 创建 store 并导出
- 使用 redux-thunk 的第二参数 getState
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import { 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
18import { 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
12const 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
13import { 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
11import { 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
16import { 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
1 | import { createStore, applyMiddleware } from 'redux' |
如何判断项目需要引入 Redux
并非所有应用程序都需要 Redux,是否引入 Redux 由以下决定
- 正在构建的应用程序类型
- 需要解决的问题类型
- 哪些工具可以更好地解决问题
Redux 可以共享和管理状态
- 通过可预测的行为来帮助回答:状态何时、何处、为什么及如何改变
- 增加概念、代码和限制,增加学习成本和项目复杂度
平衡利弊,在以下情况引入 Redux 最有用
- 应用程序许多地方都需要状态
- 应用程序的状态经常更新
- 更新状态的逻辑比较复杂
- 项目较大,需要比较多的人协作
- 想查看状态何时、何处、为什么及如何改变