react-saga原理探索

redux-saga原理探索

这篇文章是为了取探索saga的异步工作流和effect的处理,探究他的思路和工作原理。

redux的applyMiddleware

从头开始撸

saga是一个redux的中间件,而redux提供了一个applyMiddleware(...middlewares)函数,

middleware它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点,可以接收store的dispatch和getState方法,我们来做一个简单的测试去理解redux的中间件。

  • 第一步,logger 可以说是redux中间件的范式,也就是说redux的中间件都需要按照此类方式实现。
function logger({ getState }:any) {
  return (next:Dispatch) => (action:any) => {
    console.log('will dispatch', action)

    // 调用 middleware 链中下一个 middleware 的 dispatch。
    let returnValue = next(action)

    console.log('state after dispatch', getState())

    // 一般会是 action 本身,除非
    // 后面的 middleware 修改了它。
    return returnValue
  }
}
创建我们的redux仓库,用于在react-redux的provider上承载store
let store = createStore(
  todos,// reducers
  [ 'Use Redux' ],//state
  applyMiddleware(logger)// 应用中间件
)
  • 第二步,applyMiddleware的实现,整体看起来很简单,执行他只是返回了一个函数,接收createStore的构造器。这里并没有执行任何东西,只是得到了一个函数而已。
export default function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
    // 返回函数。
  return (createStore: StoreCreator) => <S, A extends AnyAction>(
    reducer: Reducer<S, A>,
    ...args: any[]
  ) => {
      // 这里就没有enhancer了,也就不会进去enhancer的处理逻辑。就会创建出reducer
    const store = createStore(reducer, ...args)
    let dispatch: Dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI: MiddlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    // 遍历applyMiddleware的参数,为每个middleware装入getState和dispatch
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    //compose是用于组合我们的reducer,会从最后一个执行到第一个,顺序执行,
    // 本例来说 这个dispatch就是我们定义的logger返回的函数,函数执行得到的就是dispatch即 action=>{}这样的函数,其实就是dispatch,在他的前后多了两个console而已。当我们dispatch的时候,我们就会走到这里来,按中间件从后往前的顺序去
   // (next:Dispatch) => (action:any) => {
    //console.log('will dispatch', action)

    // 调用 middleware 链中下一个 middleware 的 dispatch。
    //let returnValue = next(action)

    //console.log('state after dispatch', getState())

    // 一般会是 action 本身,除非
    // 后面的 middleware 修改了它。
    
    return returnValue
  }
    dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

export default function compose(...funcs: Function[]) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return <T>(arg: T) => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}
  • 第三步,程序程序回到我们的第一步,在执行createStore,他接收三个参数。reducers,state,enhancer中间件函数。这里得到的是第二步返回的函数**(createStore: StoreCreator)**,在案例里面是由中间件的。他会执行第二步返回的函数。我们回到第二步看。
export default function createStore<
  S,
  A extends Action,
  Ext = {},
  StateExt = never
>(
  reducer: Reducer<S, A>,
  preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function.'
    )
  }
	// 判断state是否函数,是函数就会将这个赋值给enhancer,认为是中间件,也就是说我们初始化可以不给默认的state状态值
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
    preloadedState = undefined
  }
// 判断enhancer是否存在,存在确保是function
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
// 执行enhancer,执行第二步返回的函数
    return enhancer(createStore)(
      reducer,
      preloadedState as PreloadedState<S>
    ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }
总结

执行applyMiddleware得到一个接收createStore的函数,然后执行createSore检测是否由enhancer中间件函数,有就会调用applyMiddleware得到函数,然后compose组合多个中间件,此时就形成了由右向左的一层一层的dispatch方法,也就是说我们store.dispatch调用的是我们最右边的中间件,然后这个中间件调用next指向下一个中间件调用,最后才会分发到原始的dispatch方法。

redux-saga

redux-saga是用利用generator函数的特性来控制,先看下generator函数会返回什么,

function* gen(){
    yield 'hello';
    yield 'test';
    return true;
}
let runs=gen();
//{next: ƒ, throw: ƒ, return: ƒ, Symbol(Symbol.iterator): ƒ}
console.log(runs);
//{value: 'hello', done: false}
console.log(runs.next())
//{value: 'test', done: false}
console.log(runs.next())
//{value: true, done: true}
console.log(runs.next())

generator函数执行返回的是一个iterator遍历器,有一个暂停和继续的效果,我们主动触发iterator的next()才会走向下一个yield。

简单案例-demo

我按照源码,理了几个简单的案例去实现一个简单redux-saga。

demo的文件总共有五个:

  • middleware.ts:用于构建redux的中间件介入redux
  • effects.ts:创建各种各样的副作用函数,如:take\fork\call\put
  • runSaga:saga的run相关配置
  • proc:分发effects,及next逻辑控制相关
  • effectRunnerMap:对应的effects运行方式。
sagaMiddleware中间件 — middleware.ts

根据redux中间件的标准范式创建

import { Dispatch,Action } from "redux";
import {runSaga} from './runSaga';
const sagaMiddlewareFactory=()=>{
    let boundRunSaga:any;
    const sagaMiddleware=({dispatch,getState}:any)=>{
        console.log('middleware init');
        // 给runsaga绑定上dispatch和getState方法。
        boundRunSaga=runSaga.bind(null,{
            // channel,
            dispatch,
            getState,
        })
        return (next:Dispatch)=>(action:Action)=>{
            console.log("middleware start");
            const result=next(action);
            console.log("middleware end");
            return result;
        }
    }
    sagaMiddleware.run=(...arg:any)=>{
        // 运行runsaga
        boundRunSaga(...arg);
    }
    return sagaMiddleware;
}
export default sagaMiddlewareFactory;
effects.ts

这里用于生成我们的副作用函数

// effect构造函数
const makeEffect = (type: string, payload: { pattern?: any; fn?: any; args?: any[]; action?: any }) => ({
    IO: true,
    type,
    payload
  })
  // 用于命令middleware在store上等待指定的action,发起与pattern匹配上之后。会将generator暂停
  export function take(pattern: any) {
    return makeEffect('TAKE', { pattern })
  }
  // 命令middleware以非阻塞的形式执行fn
  export function fork(fn: any) {
    return makeEffect('FORK', { fn })
  }
  // 命令middleware以参数args调用函数fn,返回promise的函数 
//{
//IO:true
//payload:{fn: ƒ, args: Array(0)}
//type:'CALL'}
//	调用之后就形成了这样一种格式的effects对象
}
  export function call(fn: any, ...args: any[]) {
    return makeEffect('CALL', { fn, args })
  }
  // 触发store.dispatch
  export function put(action: any) {
    return makeEffect('PUT', { action })
  }
  export function takeEvery(pattern:any, saga:any) {
    function* takeEveryHelper() {
      while (true) {
        yield take(pattern);
        yield fork(saga); 
      }
    }
  
    return fork(takeEveryHelper);
  }
runSaga.ts
import proc from './proc';
export function runSaga({channel,dispatch,getState}:any,saga:any,...arg:any){
    // 执行我们的generator函数。得到iterator遍历器
    const iterator=saga(...arg);
    const env={channel,dispatch,getState};
    // 具体分发逻辑。
    proc(env,iterator);
}
proc.ts
import effectRunnerMap  from './effectRunnerMap';
export default function proc(env:any,iterator:any){
    function next(arg:any,isErr:any){
        let result;
        // 这个因为我们调用的call,这里result={value:{IO:true,payload:{fn: ƒ, args: Array(0)},type:'CALL'},done:false}
        if(isErr){
            result=iterator.throw(arg);
        }else{
            
            result =iterator.next(arg);
        }
        if(!result.done){
            //所以进入查找对应runEffect,next函数传递下去,
            digestEffect(result.value,next);
        }
    }
    next();

    function runEffect(effect:any,currCb:any){
        if(effect && effect.IO){
        // CALL第一次进来到这个,
        // 查找对应effectRunner
        //参数env有store的getState和dispatch,payload是我们使用副作用时候的参数,currcb是当前的回调。
            const effectRunner=effectRunnerMap[effect.type];
            effectRunner(env,effect.payload,currCb);
        }else{
            currCb();
        }
    }
    function digestEffect(effect:any,cb:any){
        let effectSettled:any;
        //封装回调
        function currCb(res:any,isErr:any){
            if(effectSettled){
                return;
            }
            effectSettled=true;
            cb(res,isErr);
        }
        //开始runEffect
        runEffect(effect,currCb);
    }
}
runnerEffectsMap.ts
import proc from "./proc";

// 简单判断是不是promise
function isPromise(obj: Promise<any>) {
  return obj && typeof obj.then === 'function';
}

function runTakeEffect(env: { channel: any; }, { channel = env.channel, pattern }: any, cb: any) {
  const matcher = (input: { type: any; }) => input.type === pattern;

  channel.take(cb, matcher);
}

function runForkEffect(env: any, { fn }: any, cb: () => void) {
  const taskIterator = fn();    

  proc(env, taskIterator);      

  cb();      
}

function runPutEffect(env: { dispatch: (arg0: any) => any; }, { action }: any, cb: (arg0: any) => void) {
  const result = env.dispatch(action);    

  cb(result);
}

function runCallEffect(env: any, { fn, args }: any, cb: (arg0: any, arg1: boolean | undefined) => void) {
    // 调用fetchUserInfoAPI,得到的是promise,回到了proc去看currCb
  const result = fn.apply(null, args);

  if (isPromise(result)) {
    return result
      .then((data: any) => cb(data,false))
      .catch((error: any) => cb(error, true));
  }
  cb(result,false);
}

const effectRunnerMap = {
  'TAKE': runTakeEffect,
  'FORK': runForkEffect,
  'PUT': runPutEffect,
  'CALL': runCallEffect
};

export default effectRunnerMap;
引入中间件及saga案例
saga案例
import { call, put, takeEvery,} from '../saga-demo/effects';
import { fetchUserInfoAPI } from './api';
// import { call, put, takeEvery,} from 'redux-saga/effects';

// export function* fetchUserInfo() {
//   yield put({ type: "FETCH_USER_FAILED", payload: 'message' });
  
// }

export function* rootSaga() {
  const user = yield call(fetchUserInfoAPI);
  yield put({ type: "FETCH_USER_SUCCEEDED", payload: user });

}

// fetchUserInfoAPI
export function fetchUserInfoAPI() {
    return new Promise((resolve) => {
      const mockData = {
        id: '123',
        name: '小明',
        age: 18
      };
  
      resolve(mockData)
    });
  }
中间件
import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'
import {rootSaga,fetchUserInfo} from './sagas'
import createSagaMiddleware from '../saga-demo/middleware';
// import createSagaMiddleware from 'redux-saga';
let sagaMiddleware=createSagaMiddleware();
let store = createStore(
  todos,
  [ 'Use Redux' ],
  applyMiddleware(sagaMiddleware)
)
// 
sagaMiddleware.run(rootSaga);

export default store;
分析
  1. 第一步,在我们的案例里,saga里面有rootsaga,用了call和put副作用函数,在saga案例,执行调用就调用了effects对应的副作用函数,call得到的就是{IO:true,payload:{fn: ƒ, args: Array(0)},type:‘CALL’},put得到的是{IO:true,payload:{},type:‘PUT’}这样类似的结构。这是整个结构最开始执行的地方。

  2. 第二步,在redux集成sagamiddleware的地方,我们引入了rootsaga,并run起来了。run的是runsaga这个文件,这个文件主要是执行我们的rootsaga的generator函数,得到一个遍历器。进入(proc)查找运行对应副作用函数的文件(runEffect),这个函数进来就会执行一次在runsaga处得到iterator.next(),iterator.next()的结果是{value:{IO:true,payload:{fn: ƒ, args: Array(0)},type:‘CALL’},done:false},具体可以去查看proc处的内容,proc执行完毕。

  3. 第三步,到达具体runnerEffectsMap文件,我们第一个是call,去对应文件看下。调用异步函数,根据成功失败调用proc的currCb函数,给成功或者错误的结果。就回到了next函数,调用,就走到了put的逻辑,再次从第一步开始。

    这几步,走下来,已经完成了我们正常利用saga去调用函数,得到结果放入redux数据,最后用于渲染页面,他的异步逻辑,和redux一点关系都没有。

    参考文章:https://juejin.cn/post/6885223002703822855#heading-2

    我的案例地址:https://github.com/lqy0101/fe-code-analysis/tree/main/example/react

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: React-sagaReact-thunk都是用于处理异步操作的中间件。 React-thunk是Redux官方推荐的中间件之一,它允许我们在Redux中编写异步操作,使得我们可以在Redux中处理异步操作,而不需要在组件中处理异步操作。Thunk是一个函数,它接收dispatch和getState作为参数,并返回一个函数,这个函数接收dispatch作为参数,并在异步操作完成后调用dispatch。 React-saga是另一个处理异步操作的中间件,它使用了ES6的Generator函数来处理异步操作。Saga使用了一种称为Effect的概念,它是一个简单的JavaScript对象,用于描述异步操作。Saga使用了一种称为yield的语法,它允许我们在Generator函数中暂停异步操作,并在异步操作完成后继续执行。 总的来说,React-thunk和React-saga都是用于处理异步操作的中间件,它们的实现方式不同,但都可以让我们在Redux中处理异步操作。选择哪种中间件取决于个人的喜好和项目的需求。 ### 回答2: React-SagaReact-Thunk都是React应用中用于处理异步操作的中间件。它们的主要目的是在Redux应用中,帮助我们管理异步操作。这两个中间件都可以让React应用更加的灵活、健壮和易于维护。 React-Saga的核心理念是利用生成器函数来处理异步操作,Saga通过使用生成器来让异步操作变得像同步操作一样,其中每个异步操作都会被转化为一个迭代器函数,这些函数可以被Saga调用和暂停。 Saga主要有以下几个特点: 1. Saga可以使异步操作更加同步和简单,让异步调用变得更容易。Saga使用了轻量级、高效的生成器函数,从而有效地减少了异步调用中的代码复杂度。 2. Saga可以很好地管理和协调多个异步操作。Saga可以在任意阶段暂停异步操作,等待其他异步操作完成之后再继续执行。 3. Saga可以捕获和控制异步操作的错误、超时和状态。当出现问题时,Saga可以修复错误或者更改异步操作的状态,保证应用程序的稳定性和可靠性。 React-Thunk的核心概念是利用闭包函数来处理异步操作,Thunk将异步操作转化为一个闭包函数,然后通过回调函数将其传递到Redux的异步流中。 Thunk的主要特点有以下几个: 1. Thunk可以轻松处理异步操作,没有复杂的代码逻辑或者概念。 2. Thunk主要使用了闭包函数来捕捉当前异步操作的上下文,使得处理异步操作更加的简单、方便和自然。 3. Thunk可以轻松控制异步操作的状态、结果和错误处理,保证应用程序的稳定性和可靠性。 总之,React-SagaReact-Thunk都是帮助我们管理和处理应用程序的异步操作的中间件。它们都有自己独特的实现方式和特点。我们可以根据自己的项目需求和开发团队的技能水平来选择适合我们的中间件。 ### 回答3: React-sagaReact-thunk 都是针对 React 应用中异步操作的中间件。它们两个都可以用来控制异步流程,使得我们可以更好的管理 React 应用程序中异步操作的数据和状态。 相较于 react-thunk, react-saga 是一个更加强大的中间件,它基于 generator 函数的概念,可以用来控制非常复杂的异步流程,使得我们可以在操作时更加精细地掌控多个异步操作的执行顺序和状态。 如果说 react-thunk 的核心概念是将异步操作封装进一个函数里,而在需要时调用这个函数即可,那么 redux-saga 的核心概念则是分离出一个独立的 Generator 函数来代表所有的异步业务逻辑。 redux-saga 可以让你从另一个角度处理异步流程,使你能够同步处理异步操作,不同的 Saga 可以用一种集中且易于理解的方式组合起来,组成它们自己的执行序列。 总而言之,React-sagaReact-thunk 都是 React 应用程序开发中非常实用的工具,对于管理异步操作和数据状态非常有帮助。但是针对不同的开发需求,我们需要选择相应的中间件,来实现我们最好的业务逻辑。所以我们在使用的时候需要根据实际情况选择适合的中间件进行操作,以达到最好的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值