react-saga 浅析

先上一张经典图为敬
image

redux-saga 是一个用于管理redux应用异步操作代替 redux-thunk 的中间件

  • 集中处理 redux 副作用问题。reducer负责处理action的更新,saga负责协调那些复杂或者异步的操作
  • 使用 generator 函数执行异步,generator不是线程
  • watch/worker(监听->执行) 的工作形式

redux-saga 启动的任务可以在任何时候通过手动来取消,也可以把任务和其他的Effects放到 race 方法里以自动取消

createSagaMiddleware(...sagas:[])

createSagaMiddleware的作用是创建一个redux中间件,并将sagas与Redux store建立链接

effect

只要是被 yield 的值,都可以被称为 effect。例如 yield 1 中,数字 1 就是 effect。
所有的 effect 都是通过 yield 语法传递给 effect-runner 的,effect-runner 处理该 effect 并决定什么时候恢复生成器。
redux-saga里所有effect返回的值,都是一个带类型的纯object对象。
generator的特点是执行到某一步时,可以把控制权交给外部代码,由外部代码拿到返回结果后,决定该怎么做。

put
作用和 redux 中的 dispatch 相同。

yield put({ type: 'CLICK_BTN' });

take
等待 redux dispatch 匹配某个 pattern 的 action 。

在这个例子中,先等待一个按钮点击的 action ,然后执行按钮点击的 saga:

while (true) {
  yield take('CLICK_BUTTON');
  yield fork(clickButtonSaga);
}

fork
当接收到 BEGIN_COUNT 的 action,则开始倒数,而接收到 STOP_COUNT 的 action, 则停止倒数。

function* count(number) {
  let currNum = number;

  while (currNum >= 0) {
    console.log(currNum--);
    yield delay(1000);
  }
}

function countSaga* () {
  while (true) {
    const { payload: number } = yield take(BEGIN_COUNT);
    const countTaskId = yield fork(count, number);

    yield take(STOP_TASK);
    yield cancel(countTaskId);
  }
}

call
有阻塞地调用 saga 或者返回 promise 的函数。

const project = yield call(fetch, { url: UrlMap.fetchProject });
const members = yield call(fetchMembers, project.id);

select
作用和 redux thunk 中的 getState 相同。

const id = yield select(state => state.id);

阻塞调用和无阻塞调用
其中 fork 是无阻塞型调用,call 是阻塞型调用。

channel

channel是对事件源的抽象,作用是先注册一个take方法,当put触发时,执行一次take方法,然后销毁take。

export function channel(buffer = buffers.expanding()) {
  let closed = false
  let takers = []

  function put(input) {
    if (closed) {
      return
    }
    if (takers.length === 0) {
      return buffer.put(input)
    }
    const cb = takers.shift()
    cb(input)
  }

  function take(cb) {
    if (closed && buffer.isEmpty()) {
      cb(END)
    } else if (!buffer.isEmpty()) {
      cb(buffer.take())
    } else {
      takers.push(cb)
      cb.cancel = () => {
        remove(takers, cb)
      }
    }
  }

  function flush(cb) {
    if (closed && buffer.isEmpty()) {
      cb(END)
      return
    }
    cb(buffer.flush())
  }

  function close() {
    if (closed) {
      return
    }

    closed = true

    const arr = takers
    takers = []

    for (let i = 0, len = arr.length; i < len; i++) {
      const taker = arr[i]
      taker(END)
    }
  }

  return {
    take,
    put,
    flush,
    close,
  }
}

当put触发时,如果channel里已经有注册了的taker,taker就会执行。

我们需要在put触发之前,先调用channel的take方法,注册实际要运行的方法。

const chan = channel();
btn.addEventListener('click', () => {
  const action =`action data${i++}`;
  chan.put(action);
}, false);

function* mainSaga() {
  const action = yield take();
  console.log(action);
}

这个take是saga里的一种effect类型。

task

task是generator方法的执行环境,所有saga的generator方法都跑在task里。
简易实现如下:

function task(iterator) {
  const iter = iterator();
  function next(args) {
    const result = iter.next(args);
    if (!result.done) {
      const effect = result.value;
      if (effect.type === 'take') {
        runEffect(result.value, next);
      }
    }
  }
  next();
}

数据流转

image

// saga.js
// 它是一个 generator function
// fn 中同样包含了业务数据请求代码逻辑
function* fetchData(action) {
  const { payload: { someValue } } = action;
  try {
    const result = yield call(myAjaxLib.post, "/someEndpoint", { data: someValue });
    yield put({ type: "REQUEST_SUCCEEDED", payload: response });
  } catch (error) {
    yield put({ type: "REQUEST_FAILED", error: error });
  }
}

// watcher saga
// 监听每一次 dispatch(action)
// 如果 action.type === 'REQUEST',那么执行 fetchData
export function* watchFetchData() {
  yield takeEvery('REQUEST', fetchData);
}


// component.js
// View 层 dispatch(action) 触发异步请求 
// 这里的 action 依然可以是一个 plain object
this.props.dispatch({
  type: 'REQUEST',
  payload: {
    someValue: { hello: 'saga' }
  }
});

对比 thunk

thunk 采用的是扩展action的方式:使得redux的store能dispatch的内容从普通对象扩展到函数

saga 采用的方案更接近于redux的全局思想
saga 采用发布订阅模式,需要一个经纪人Broker,用于监听组件发出的action,将监听到的action1转发给对应的订阅者,再由订阅者执行具体任务,任务执行完后,再发出另一个action2交由reducer修改state

posted @ 2022-01-19 17:30  远方的少年🐬  阅读(1057)  评论(0编辑  收藏  举报