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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM