Redux之异步Action及操作|原理篇
30 秒速记
- 核心判断:状态管理的核心是把状态变化约束成可追踪的数据流,中间件在派发边界扩展异步、日志和副作用
- 原理主线:围绕 「一、创建同步Action」、「二、创建异步action的多种方式」、「三、redux异步流程」 建立输入、状态变化与输出之间的因果关系
- 文章范围:介绍了Redux中异步Action的原理、实现方式及实际操作方法,帮助开发者理解如何在React项目中高效管理异步数据流和状态更新。
- 边界与代价:集中式状态不是越多越好;可变性、订阅粒度和异步取消决定性能与可维护性
- 工程落地:选型要从状态归属、更新频率、调试需求和团队约束出发,而不是只比较 API 数量
Redux 的异步处理本质上是让中间件先接住非普通对象的 action,异步完成后再派发同步 action 更新状态。 redux-thunk 接收函数,可在请求前、成功和失败时分别 dispatch,控制力强,也支持乐观更新,但模板代码较多。redux-promise 用 Promise 简化写法,却会过滤初始动作,因此不适合需要立即反馈的乐观更新。实际选型要看业务是否需要过程状态、失败回滚和精细控制。
这篇文章不要按 API 清单来背。先用上面的 Mind Map 建立全局结构,再通过交互 DEMO 观察正常路径和边界路径如何改变状态;阅读正文时重点核对每一步的输入、负责执行的参与者、产生的中间状态以及最终可观察结果。遇到版本敏感结论,要把“历史实现”“当前行为”和“工程兼容策略”分开说明;遇到性能或架构取舍,则用实际指标、失败现象和验证手段支撑判断。
版本校准: 本文若分析 ReactDOM.render、旧生命周期或栈调和,应把它视为理解架构演进的历史路径。React 19 已移除 ReactDOM.render,当前客户端入口使用 createRoot;并发渲染也必须区分可中断的渲染阶段与同步提交阶段。迁移前对照 React 19 官方升级指南。
# 一、创建同步Action
Action是数据从应用传递到store/state的载体,也是开启一次完成数据流的开始
普通的action对象
const action = {
type:'ADD_TODO',
name:'poetries'
}
dispatch(action)
封装action creator
function actionCreator(data){
return {
type:'ADD_TODO',
data:data
}
}
dispatch(actionCreator('poetries'))
bindActionCreators合并
function a(name,id){
reurn {
type:'a',
name,
id
}
}
function b(name,id){
reurn {
type:'b',
name,
id
}
}
let actions = Redux.bindActionCreators({a,b},store.dispatch)
//调用
actions.a('poetries','id001')
actions.b('jing','id002')
action创建的标准
在Flux的架构中,一个Action要符合 FSA(Flux Standard Action) 规范,需要满足如下条件
- 是一个纯文本对象
- 只具备
type、payload、error和meta中的一个或者多个属性。type字段不可缺省,其它字段可缺省 - 若
Action报错,error字段不可缺省,切必须为true
payload是一个对象,用作Action携带数据的载体
标准action示例
- A basic Flux Standard Action:
{
type: 'ADD_TODO',
payload: {
text: 'Do something.'
}
}
- An FSA that represents an error, analogous to a rejected Promise
{
type: 'ADD_TODO',
payload: new Error(),
error: true
}
- 可以采用如下一个简单的方式检验一个
Action是否符合FSA标准
// every有一个匹配不到返回false
let isFSA = Object.keys(action).every((item)=>{
return ['payload','type','error','meta'].indexOf(item) > -1
})
# 二、创建异步action的多种方式
最简单的方式就是使用同步的方式来异步,将原来同步时一个
action拆分成多个异步的action的,在异步开始前、异步请求中、异步正常返回(异常)操作分别使用同步的操作,从而模拟出一个异步操作了。这样的方式是比较麻烦的,现在已经有redux-saga等插件来解决这些问题了
异步action的实现方式一:setTimeout
redux-thunk中间处理解析
function thunkAction(data) {
reutrn (dispatch)=>{
setTimeout(function(){
dispatch({
type:'ADD_TODO',
data
})
},3000)
}
}
异步action的实现方式二:promise实现异步action
redux-promise`中间处理这种`action@前端进阶之旅: 代码已经复制到剪贴板
function promiseAction(name){
return new Promise((resolve,reject) => {
setTimeout((param)=>{
resolve({
type:'ADD_TODO',
name
})
},3000)
}).then((param)=>{
dispatch(action("action2"))
return;
}).then((param)=>{
dispatch(action("action3"))
})
}
# 三、redux异步流程

- 首先发起一个action,然后通过中间件,这里为什么要用中间件呢,因为这样
dispatch的返回值才能是一个函数。 - 通过
store.dispatch,将状态的的改变传给store的小弟reducer,reducer根据action的改变,传递新的状态state。 - 最后将所有的改变告诉给它的大哥,
store。store保存着所有的数据,并将数据注入到组件的顶部,这样组件就可以获得它需要的数据了
# 四、Redux异步方案选型
redux-thunk
Redux本身只能处理同步的Action,但可以通过中间件来拦截处理其它类型的action,比如函数(Thunk),再用回调触发普通Action,从而实现异步处理
- 发送异步的
action其实是被中间件捕获的,函数类型的action就被middleware捕获。至于怎么定义异步的action要看你用哪个中间件,根据他们的实例来定义,这样才会正确解析action
Redux本身不处理异步行为,需要依赖中间件。结合redux-actions使用,Redux有两个推荐的异步中间件
redux-thunkredux-promise
redux-thunk的源码如下
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
源码可知,
action creator需要返回一个函数给redux-thunk进行调用,示例如下
export let addTodoWithThunk = (val) => async (dispatch, getState)=>{
//请求之前的一些处理
let value = await Promise.resolve(val + ' thunk');
dispatch({
type:CONSTANT.ADD_TO_DO_THUNK,
payload:{
value
}
});
};
- 而它使用起来最大的问题,就是重复的模板代码太多
//action types
const GET_DATA = 'GET_DATA',
GET_DATA_SUCCESS = 'GET_DATA_SUCCESS',
GET_DATA_FAILED = 'GET_DATA_FAILED';
//action creator
const getDataAction = (id) => (dispatch, getState) => {
dispatch({
type: GET_DATA,
payload: id
})
api.getData(id) //注:本文所有示例的api.getData都返回promise对象
.then(response => {
dispatch({
type: GET_DATA_SUCCESS,
payload: response
})
})
.catch(error => {
dispatch({
type: GET_DATA_FAILED,
payload: error
})
})
}
}
//reducer
const reducer = (oldState, action) => {
switch(action.type) {
case GET_DATA :
return oldState;
case GET_DATA_SUCCESS :
return successState;
case GET_DATA_FAILED :
return errorState;
}
}
这已经是最简单的场景了,请注意:我们甚至还没写一行业务逻辑,如果每个异步处理都像这样,重复且无意义的工作会变成明显的阻碍
- 另一方面,像
GET_DATA_SUCCESS、GET_DATA_FAILED这样的字符串声明也非常无趣且易错 上例中,GET_DATA这个action并不是多数场景需要的
redux-promise
由于
redux-thunk写起来实在是太麻烦了,社区当然会有其它轮子出现。redux-promise则是其中比较知名的
← Redux之源码分析浅析中间件 →
