一、概览
首先,对于redux来说,redux-saga就是一个中间件。
其次,redux-saga主要用于处理action中涉及到的一些副作用,来确保reducer始终都是一个纯函数。
常规操作redux中的state,流程是这样的:
UI(点击按钮) → dispatch(action)→ reducer(根据action操作state)→ store(合并reducer更新的state)→ UI更新
toLoginIn: (username, password)=>{
dispatch({
type: 'TO_LOGIN_IN',
username,
password
})
}
我们需要根据action发送一个请求,dispatch依旧是正常封装action,那么发送请求的操作就需要交给reducer来实现,可是这就违背了reducer应该是一个纯函数的设计初衷。
我们可以将带有副作用的操作先交给saga来完成,然后再由saga发出一个action,加入了saga,流程就变成了这样:
UI(点击按钮) → dispatch(action)→ take(拦截action)→ call(发送请求)→ put(根据结果转发对应的action2)→ reducer(根据action2操作state)→ store(合并reducer更新的state)→ UI更新
while(true){
const action = yield take('TO_LOGIN_IN'); // 在发出type为TO_LOGIN_IN的action时,截获该action
const res = yield call(fetchSmart, '/login', {
method: 'POST',
body: JSON.stringify({
username: action.username,
password: action.password
})
if(res){
put({type: 'to_login_in'})
}
})
}
可以看到,put的作用就很像是dispatch。
发送请求的部分最好还是使用fork,它不会像call那样阻塞线程,影响后续代码的执行。
我们来看一下“点击按钮登录 → 请求验证 → 请求资源 → 退出登录” 的完整代码
function * getList(){
try {
yield delay(3000);
const res = yield call(fetchSmart,'/list',{
method:'POST',
body:JSON.stringify({})
});
yield put({type:'update_list',list:res.data.activityList});
} catch(error) {
yield put({type:'update_list_error', error});
}
}
function * watchIsLogin(){
while(true){
//监听登入事件
const action1=yield take('TO_LOGIN_IN');
const res=yield call(fetchSmart,'/login',{
method:'POST',
body:JSON.stringify({
username:action1.username,
password:action1.password
})
});
//根据返回的状态码判断登陆是否成功
if(res.status===10000){
yield put({type:'to_login_in'});
//登陆成功后获取首页的活动列表
yield call(getList);
}
//监听登出事件
const action2=yield take('TO_LOGIN_OUT');
yield put({type:'to_login_out'});
}
}
可以看到,我们使用yield call(getList)获取的资源,假如网络较慢,线程就一直会处于阻塞状态,这一过程就可能花费很长时间,而等待的这段时间。我们无法做任何操作,比如点击退出登录按钮,直观感受就是点击没有任何反应,直到数据被响应回来。
二、常用 Effect (完善中)
Effect 指 Reduct Saga 中各种 Api(take、call、put、select、fork、takeEveny、takeLatest...)
1. take 与 takeEvery 的区别
take
用于暂停 Generator 直到匹配指定的 action 被发起。当匹配到对应的 action 时,Generator 将继续执行。
import { take, put } from 'redux-saga/effects';
function* mySaga() {
yield take('SOME_ACTION');
yield put({ type: 'OTHER_ACTION' });
}
上述代码中,take('SOME_ACTION') 表示 Generator 会在捕获到 'SOME_ACTION' 的 dispatch 时停止执行,然后继续执行后续的代码。
takeEvery
在每次匹配到指定的 action 时启动一个新的 saga。
import { takeEvery, put } from 'redux-saga/effects';
function* mySaga(action) {
yield put({ type: 'OTHER_ACTION', payload: action.payload });
}
function* watchEvery() {
yield takeEvery('SOME_ACTION', mySaga);
}
takeEvery 会监听每次 'SOME_ACTION' 被 dispatch,然后调用 mySaga,传递相应的 action。
总结:take 是等待单个 action 的 Effect,而 takeEvery 是在每次匹配到指定 action 时启动一个新的 saga。
2. takeEvery、takeLatest、takeLeading 的区别
takeLeading
在每次 Action 被触发时立即执行一次 Saga。如果在 Saga 期间再次触发相同的 Action,Saga 将被阻塞,直到当前的 Saga 完成才会再次执行。
takeEvery
- 多次执行: takeEvery 会在每次匹配到指定 action 时都启动一个新的 saga,即使前一个执行还未完成。
- 并发执行: 如果多个相同的 action 被 dispatch,多个对应的 saga 可能会同时执行。
- 适用场景: 适用于需要处理每次匹配到的 action,不考虑执行时机的场景。
import { takeEvery, put } from 'redux-saga/effects';
function* mySaga(action) {
yield put({ type: 'OTHER_ACTION', payload: action.payload });
}
function* watchEvery() {
yield takeEvery('SOME_ACTION', mySaga);
}
takeLatest
- 最新执行: takeLatest 在每次匹配到指定 action 时都会取消之前正在执行的 saga,只执行最新的。
- 防抖效果: 适用于处理频繁发起的 action,确保只有最新的 action 会被处理,可以实现一种防抖效果。
- 适用场景: 适用于需要关注最新 action 并避免并发执行的场景。
import { takeLatest, put } from 'redux-saga/effects';
function* mySaga(action) {
yield put({ type: 'OTHER_ACTION', payload: action.payload });
}
function* watchLatest() {
yield takeLatest('SOME_ACTION', mySaga);
}
总的来说,takeEvery 适用于并发执行的场景,而 takeLatest 适用于确保只处理最新 action 的场景,避免并发执行的问题。选择使用哪个取决于具体的业务需求。
而fork是非阻塞的,将call替换为fork就可以解决这一问题,其效果更像是web worker。