[口頭での反応]冗長ミドルウェアによる非同期タスクの管理3


目次
18.1作業環境の準備
18.2の間件とは何ですか.
18.3ミドルウェアによる非同期タスクの処理
18.3.2 redux-saga
redux-thunkの原理は,関数形式の動作をdispatchすることにより,中間部品に商店のdispatchとgetStateをパラメータとしてこの関数に入れることで用いられる.したがって、実装されたthunk関数の内部では、所望のAPI要求を発行したり、他の動作を送信したり、現在の状態をクエリーしたりすることができる.
今回やるredux-sagaはもっと厄介な状況に適しています.例として、次のようなことが挙げられます.
  • 既存要求の処理をキャンセルする必要がある場合(不要な重複要求を避ける)
  • .
  • は、ある動作が発生すると別の動作をトリガするか、API要求のようなサポートされていないコードを実行するときに
  • を呼び出す.
  • Webスロット、
  • API要求が失敗した場合、
  • を再要求する必要がある
  • 18.3.2.1サードパーティ関数について
    redux-sagaはES 6の構文を使用し、サードパーティ関数と呼ばれます.この構文の核心機能は、関数を記述するときに、関数をある区間に停止し、必要に応じて返すことができます.
  • function weirdFunction() {
      return 1;
      return 2;
      return 3;
    1つの関数から複数を返すことはできないため、コードが呼び出されるたびに1だけが返されます.ただし、jennizer関数を使用すると、関数から値を順次返すことができます.また,関数の流れを途中で止めてから続行することも可能である.
    function* genereatorFunction(){
      console.log('안녕');
      yield 1;
      console.log('제너레이터 함수');
      yield 2;
      console.log('function*');
      yield 3;
      return 4;
    }
    
    const generator = generatorFunction();
    ジェネレータ関数を作成するときにfunction*キーワードを使用します.jner layer関数を呼び出すと返されるオブジェクトをjner layerと呼びます.
    次に、次のコードの行を順番に入力し、結果を表示します.
    generator.next();
    // 안녕하세요
    // {value: 1, done: false}
    
    generator.next();
    // 제너레이터 함수
    // {value: 2, done: false}
    
    generator.next();
    // function*
    // {value: 3, done: false}
    
    generator.next();
    // {value: 4, done: true}
    
    generator.next();
    // {value: defined, done: true}
    発電機が初めて作成された場合、関数の流れは静止しています.next()が呼び出されると、次の最終品目に呼び出され、関数が再び停止します.jnerator関数を使用すると、関数を途中で停止したり、複数の値を順次返したりすることができます.next関数にパラメータを追加した後、jenerator関数の降伏値を使用して値をクエリーすることもできます.
    function* sumGenerator(){
      console.log('sumGenerator가 만들어졌다.');
      let a = yield;
      let b = yield;
      yield a + b;
    }
    
    const sum = sumGenerator();
    sum.next();
    // sumGenerator가 만들어졌다.
    // {value: undefined, done: false}
    
    sum.next(1);
    // {value: undefined, done: false}
    
    sum.next(2)
    // {value: 3, done: false}
    
    sum.next()
    // {value: undefined, done: true}
    redux-sagaはjnerator関数構文に基づいて非同期タスクを管理します.redux-sagaは、配布された動作を監視し、必要に応じて必要な操作を個別に実行できるミッドレンジソフトウェアです.
    function* watchGenerator()[
      console.log('모니터링 중...');
      let prevAction = null;
      while (true) {
        const action = yield;
        console.log('이전 액션: ', prevAction);
        prevAction = action;
        if (action.type === 'HELLO'){
          console.log('안녕하세요!');
        }
      }
    }
    
    const watch = watchGenerator();
    
    watch.next();
    // 모니터링 중..
    // {value: undefined, done: false}
    
    watch.next({type:"TEST"});
    // 이전 액션: null
    // {value: undefined, done: false}
    
    watch.next({type:"HELLO"});
    // 이전 액션: {type: "TEST"}
    // 안녕하세요!
    // {value: undefined, done:false}
  • 18.3.2.2非同期カウンタの作成yarn add redux-saga
  • INCREAMENT_ASYNCDECREMENT_ASYNCの動作タイプを発表します.このアクションのアクション生成関数を作成し、サードパーティ関数を作成します.このサードパーティ関数をsagaと呼ぶ.increaseAsyncdecreaseAsyncincreaseSagadecreaseSagaどこから来たのかわからない
    ->//modules/index.jsからfunction* rootSaga()で使用することができます.
    // modules/counter.js
    import {createAction, handleActions} from 'redux-actions';
    import {delay, put, takeEvery, takeLatest} from 'redux-saga/effects';
    
    const INCREASE = 'counter/INCREASE';
    const DECREASE = 'counter/DECREASE';
    const INCREASE_ASYNC = 'counter/INCREASE_ASYNC';
    const DECREASE_ASYNC = 'counter/DECREASE_ASYNC';
    
    export const increase = createAction(INCREASE);
    export const decrease = createAction(DECREASE);
    // 마우스 클릭 이벤트가 payload 안에 들어가지 않도록
    // () => undefined를 두 번째 파라미터로 넣어준다.
    
    export const increaseAsync = createAction(INCREASE_ASYNC, () => undefined);
    export const decreaseAsync = createAction(DECREASE_ASYNC, () => undefined);
    
    function* increaseSaga(){
      yield delay(1000); // 1초를 기다린다.
      yield put(increase()); // 특정 액션을 디스패치한다.
    }
    
    function* decreaseSaga(){
      yield delay(1000);
      yield put(decrease()); // 특정 액션을 디스패치한다.
    }
    
    export function* counterSaga(){
      // takeEvery는 들어오는 모든 액션에 대해 특정 작업을 처리해준다.
      yield takeEvery(INCREASE_ASYNC, increaseSaga);
      // takeLatest는 기존에 진행 중이던 작업이 있다면 취소 처리하고
      // 가장 마지막으로 실행된 작업만 수행한다.
      yield takeLatest(DECREASE_ASYNC, decreaseSaga);
    }
    
    const initialState = 0; // 상태는 꼭 객체일 필요 없다. 숫자도 작동한다.
    const counter = hanleActions(
      {
        [INCREASE]: state => state + 1,
        [DECREASE]: state => state - 1
      },
      initialState
    );
    export default counter;
    ルトリー・ドゥーサーを創造したように、ルトリー・ドゥーサを創造しなければならない.後日、他の李杜書にも私歌を作って登録するからだ.
    // modules/index.js
    import {combineReducers} from 'redux';
    import {all} from 'redux-saga/effects';
    import counter, {counterSaga} from './counter';
    import sample from './sample';
    import loading from './loading';
    
    const rootReducer = combineReducers({
      counter,
      sample,
      loading
    });
    
    export function* rootSaga(){
      // all 함수는 여러 사가를 합쳐주는 역할을 한다.
      yield all([counterSaga()]);
    }
    
    export default rootReducer;
    次にredux-sagaミドルウェアをショップに適用します.
    // index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import {createStore, applyMiddleware} from 'redux';
    import { Provider } from 'react-redux';
    import './index.css';
    import App from './App';
    import rootReducer, {rooSaga} from './moduels';
    import {createLogger} from 'redux-logger';
    import ReduxThunk from 'redux-thunk';
    import createSagaMiddleware from 'redux-saga';
    
    const logger = createLogger();
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(
      rootReducer,
      applyMiddleware(logger, ReduxThunk, sagaMiddleware)
    );
    sagaMiddleware.run(rootSaga);
    
    ReactDOM.render(
      <Provider store={store}>
      	<App/>
      </Provider>,
      document.getElementById('root')
    );
    ショップでミドルウェアが適用されている場合は、アプリケーションコンポーネントにComputerContainerコンポーネントをレンダリングして、正常に動作していることを確認します.インバースワイヤモジュールは変更されていますが、コンテナ構成部品には変更する必要はありません.これは,動作生成関数(INCREASE_ASYNC,DECREASE_ASYNC)を従来のThunk関数と同じ名称で作成したためである.
    // App.js
    import React from 'react';
    import CounterContainer from './containers/CounterContainer';
    
    const App = () => {
      return (
        <div>
        	<CounterContainer />
        </div>
      );
    };
    export default App;
    Reduce開発者ツールを適用して、どの動作がdispatchされているかを簡単に決定します.まずRidex開発者ツールライブラリをインストールします.$ yarn add redux-devtools-extensionこのライブラリのComponentWithDevTools関数をRidexミドルウェアと一緒に使用する場合は、applyMiddleware部分を包むだけです.
    // index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import {createStore, applyMiddleware} from 'redux';
    import { Provider } from 'react-redux';
    import './index.css';
    import App from './App';
    import rootReducer, {rooSaga} from './moduels';
    import {createLogger} from 'redux-logger';
    import ReduxThunk from 'redux-thunk';
    import createSagaMiddleware from 'redux-saga';
    import {composeWithDevTools} from 'redux-devtools-extension';
    
    const logger = createLogger();
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(
      rootReducer,
      composeWithDevTools(applyMiddleware(logger, ReduxThunk, sagaMiddleware))
    );
    sagaMiddleware.run(rootSaga);
    
    ReactDOM.render(
      <Provider store={store}>
      	<App/>
      </Provider>,
      document.getElementById('root')
    );
    そうすると、Sagaのクリック数を増やすとカウンターが増えます.degreaseSagaはtakeLatestで、一度だけDispatchされます.複数の動作が重なると、既存の動作は無視され、最後の動作のみが処理されるからです.
    以上、SagaをCounterに適用しました.
    これからは、APIリクエストにSagaを適用してみます.

  • 18.3.2.3 API要求状態の管理
    ここで、redux-sagaリクエストAPIを使用します.以前thunkが管理していた動作生成関数を取り除き,プライベート処理を用いる.sampleモジュールの変更:
  • delay設定時間後に解析されたPromiseオブジェクトを返します.
    ex)delay(1000):1秒待ち.
  • call関数の最初のパラメータは関数で、残りのパラメータは関数を入れる引数です.
    ex)call(delay, 1000):delay(1000)もcall関数で書くことができます.callは所定の関数を実行し、putはパラメータとして店舗に送信する動作に差がある.
  • put特定のアクションを割り当てる
    ex)put({type: 'INCREMENT'}):INSREMENT action割り当て.
  • all allを使用してパラメータにjenerator関数を配列形式で入れると、jenerator関数が並列に同時に実行され、すべて解析されるのを待つ.Promise.allと同様
    ex) yield all([testSaga1(), testSaga2()])
  • // modules/sample.js
    import {createAction, handleActions} from 'redux-actions';
    import {call, put, takeLatest} from 'redux-saga/effects';
    import * as api from '../lib/api';
    import {startLoading, finishLoading} from './loading';
    
    // 액션 타입을 선언한다.
    const GET_POST = 'sample/GET_POST';
    const GET_POST_SUCCESS = 'sample/GET_POST_SUCCESS';
    const GET_POST_FAILURE = 'sample/GET_POST_FAILURE';
    
    const GET_USERS = 'sample/GET_USERS';
    const GET_USERS_SUCCESS = 'sample/GET_USERS_SUCCESS';
    const GET_USERS_FAILURE = 'sample/GET_USERS_FAILURE';
    
    export const getPost = createAction(GET_POST, id=>id);
    export const getUsers = createAction(GET_USERS);
    
    function* getPostSaga(action){
      yield put(startLoading(GET_POST)); // 로딩 시작
      // 파라미터로 action을 받아오면 액션의 정보를 조회할 수 있다
      try {
        // call을 사용하면 Proise를 반환하는 함수를 호ㅜ출하고, 기다릴 수 있다.
        // 첫 번째 파라미터는 함수, 나머지 파라미터는 해당 함수에 넣을 인수이다.
        const post = yield call(api.getPost, action.payload); // apit.getPost(action.payload)를 의미
        yield put({
          type: GET_POST_SUCCESS,
          payload: post.data
        });
      } catch(e){
        // try/catch 문을 사용하여 에러도 잡을 수 있다.
        yield put({
          type: GET_POST_FAILURE,
          payload: e,
          error: true
        });
      }
      yield put(finishLoading(GET_POST)); // 로딩 완료
    }
    
    function* getUsersSaga() {
      yield put(startLoading(GET_USERS));
      try {
        const users = yield call(api.getUsers);
        yield put({
          type: GET_USERS_SUCCESS,
          payload: users.data,
        });
      } catch (e) {
        yield put({
          type: GET_USERS_FAILURE,
          payload: e,
          error: true,
        });
      }
      yield put(finishLoading(GET_USERS));
    }
    
    export function* sampleSaga() {
      yield takeLatest(GET_POST, getPostSaga);
      yield takeLatest(GET_USERS, getUsersSaga);
    }
    
    // 초기 상태를 선언한다.
    // 요청의 로딩 중 상태는 loading이라는 객체에서 관리한다.
    
    const initialState = {
      post: null,
      users: null,
    };
    
    const sample = handleActions(
      {
        [GET_POST_SUCCESS]: (state, action) => ({
          ...state,
          post: action.payload,
        }),
        [GET_USERS_SUCCESS]: (state, action) => ({
          ...state,
          users: action.payload,
        }),
      },
      initialState
    );
    
    export default sample;
    ここでGET POST動作の場合、API要求時にどのようなIDで問い合わせるかを指定する必要がある.redux-sagaを使用する場合は,idのように要求に必要な値を動作のペイロードとする必要がある.たとえば、現在の場合、dispatchは次のように動作します.
    {
      type: 'sample/GET_POST',
      payload: 1
    }
    では、この動作を処理するための歌詞を記述する際には、APIを呼び出す関数の引数としてペイロード値を用いなければならない.
    APIを呼び出す必要がある場合、呼び出し関数は社内で直接呼び出されません.call関数の場合、最初の引数は呼び出したい関数で、後ろの引数はその関数に入れたい引数です.現在getPostSagaにとってidを表すaction有効荷重は因数です.
    // modules/index.js
    import { combineReducers } from "redux";
    import { all } from "redux-saga/effects";
    import counter, { counterSaga } from "./counter";
    import sample, { sampleSaga } from "./sample";
    import loading from "./loading";
    
    const rootReducer = combineReducers([counter, sample, loading]);
    
    export function* rootSaga() {
      // all 함수는 여러 사가를 합쳐주는 역할을 한다
      yield all([counterSaga(), sampleSaga()]);
    }
    export default rootReducer;
    歌詞を登録したら、AppコンポーネントでSampleContainerをレンダリングします.
    import React from "react";
    import SampleContainer from "./containers/SampleContainer";
    
    const App = () => {
      return (
        <div>
          <SampleContainer />
        </div>
      );
    };
    export default App;
  • 18.3.2.4再包装
    thunk関数にcreaterequestThunkという関数を作成するように、createrequestSagaという関数を作成します.
  • // lib/createRequestSaga.js
    import { call, put } from "redux-saga/effects";
    import { startLoading, finishLoading } from "../modules/loading";
    
    export default function createRequestSaga(type, request) {
      const SUCCESS = `${type}_SUCCESS`;
      const FAILURE = `${type}_FAILURE`;
    
      return function* (action) {
        yield put(startLoading(type)); // 로딩 시작
        try {
          const response = yield call(request, action.payload);
          yield put({
            type: SUCCESS,
            payload: response.data,
          });
        } catch (e) {
          yield put({
            type: FAILURE,
            payload: e,
            error: true,
          });
        }
        yield put(finishLoading(type));
      };
    }
    次の短いコードを使用して、既存のプライベートコードを実装できます.getPostSagagetUsersSagaの部分はlib/createrequestSagaで簡単に表現してください
    // modules/sample.js
    import { createAction, handleActions } from "redux-actions";
    import { call, put, takeLatest } from "redux-saga/effects";
    import * as api from "../lib/api";
    import createRequestSaga from "../lib/createRequestSaga";
    import { startLoading, finishLoading } from "./loading";
    
    (...)
    
    const getPostSaga = createRequestSaga('GET_POST', api.getPost)
    const getUsersSaga = createRequestSaga('GET_USERS', api.getUsers)
    
    
    export function* sampleSaga() {
      yield takeLatest(GET_POST, getPostSaga);
      yield takeLatest(GET_USERS, getUsersSaga);
    }
    (...)
    export default sample;
  • 18.3.2.5これらの有用な機能を理解する
    -社内で現在のステータスを問い合わせる方法
  • import {createAction, handleActions} from 'redux-actions';
    import {delay, put, takeEvery, takeLatest, select} from 'redux-saga/effects';
    
    (...)
    
    function* increaseSaga(){
        yield delay(1000); // 1초를 기다린다
        yield put(increase()); // 특정 액션을 디스패치한다.
        const number = yield select(state => state.counter); // state는 스토어 상태를 의미한다.
        console.log(`현재 값은 ${number}이다`);
    }
    - 사가가 실행되는 주기를 제한하는 방법
    takeEvery 대신 throttle이란 함수를 사용하면 사가가 n초에 단 한 번만 호출도도록 설정할 수 있다. 예를 들어 counterSaga를 다음과 같이 수정하면 increaseSaga는 3초에 단 한 번만 호출된다.
    import { createAction, handleActions } from "redux-actions";
    
    import {
        delay,
        put,
        takeEvery,
        takeLatest,
        select,
        throttle,
    } from 'redux-saga/effects';
    
    (...)
    
    export function* counterSaga() {
        // 첫번 째 파라미터: n초 * 1000
        yield throttle(3000, INCREASE_ASYNC, increaseSaga);
        // takeLatest는 기존에 진행 중이던 작업이 있다며 취소 처리하고
    }