your programing

Redux에서 비동기 흐름을 위해 미들웨어가 필요한 이유는 무엇입니까?

lovepro 2020. 10. 3. 11:26
반응형

Redux에서 비동기 흐름을 위해 미들웨어가 필요한 이유는 무엇입니까?


문서에 따르면 "미들웨어없이 Redux 스토어는 동기식 데이터 흐름 만 지원합니다" . 나는 이것이 왜 그런지 이해하지 못한다. 컨테이너 구성 요소가 비동기 API를 호출 한 다음 dispatch작업을 호출 할 수없는 이유는 무엇 입니까?

예를 들어 간단한 UI (필드와 버튼)를 상상해보십시오. 사용자가 버튼을 누르면 필드가 원격 서버의 데이터로 채워집니다.

필드와 버튼

import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';

const ActionTypes = {
    STARTED_UPDATING: 'STARTED_UPDATING',
    UPDATED: 'UPDATED'
};

class AsyncApi {
    static getFieldValue() {
        const promise = new Promise((resolve) => {
            setTimeout(() => {
                resolve(Math.floor(Math.random() * 100));
            }, 1000);
        });
        return promise;
    }
}

class App extends React.Component {
    render() {
        return (
            <div>
                <input value={this.props.field}/>
                <button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
                {this.props.isWaiting && <div>Waiting...</div>}
            </div>
        );
    }
}
App.propTypes = {
    dispatch: React.PropTypes.func,
    field: React.PropTypes.any,
    isWaiting: React.PropTypes.bool
};

const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
    switch (action.type) {
        case ActionTypes.STARTED_UPDATING:
            return { ...state, isWaiting: true };
        case ActionTypes.UPDATED:
            return { ...state, isWaiting: false, field: action.payload };
        default:
            return state;
    }
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
    (state) => {
        return { ...state };
    },
    (dispatch) => {
        return {
            update: () => {
                dispatch({
                    type: ActionTypes.STARTED_UPDATING
                });
                AsyncApi.getFieldValue()
                    .then(result => dispatch({
                        type: ActionTypes.UPDATED,
                        payload: result
                    }));
            }
        };
    })(App);
export default class extends React.Component {
    render() {
        return <Provider store={store}><ConnectedApp/></Provider>;
    }
}

내 보낸 컴포넌트가 렌더링 될 때 버튼을 클릭하면 입력이 올바르게 업데이트됩니다.

호출 에서 update함수를 확인하십시오 connect. 앱에 업데이트 중임을 알리는 작업을 전달한 다음 비동기 호출을 수행합니다. 호출이 완료되면 제공된 값이 다른 작업의 페이로드로 전달됩니다.

이 접근 방식의 문제점은 무엇입니까? 문서에서 알 수 있듯이 Redux Thunk 또는 Redux Promise를 사용하는 이유는 무엇입니까?

편집 : 나는 단서를 찾기 위해 Redux 저장소를 검색했으며 과거에는 Action Creator가 순수한 기능이어야한다는 것을 알았습니다. 예를 들어 다음 은 비동기 데이터 흐름에 대한 더 나은 설명을 제공하려는 사용자입니다.

액션 생성자 자체는 여전히 순수 함수이지만 반환하는 썽크 함수는 그럴 필요가 없으며 비동기 호출을 수행 할 수 있습니다.

액션 제작자는 더 이상 순수 할 필요가 없습니다. 따라서 과거에는 썽크 / 약속 미들웨어가 확실히 필요했지만 더 이상 그렇지 않은 것 같습니까?


이 접근 방식의 문제점은 무엇입니까? 문서에서 알 수 있듯이 Redux Thunk 또는 Redux Promise를 사용하는 이유는 무엇입니까?

이 접근 방식에는 잘못된 것이 없습니다. 동일한 작업을 수행하는 다른 구성 요소가 있기 때문에 대규모 응용 프로그램에서는 불편합니다. 일부 작업을 디 바운스하거나 작업 생성자에 가까운 ID 자동 증가와 같은 일부 로컬 상태를 유지해야 할 수 있습니다. 액션 제작자를 별도의 기능으로 추출하는 유지 관리 관점.

자세한 내용은 "시간 초과가있는 Redux 작업을 전달하는 방법"에 대한 내 대답을 읽을 수 있습니다 .

돌아 오는 썽크 또는 돌아 오는 약속 같은 미들웨어는 당신에게 썽크 또는 약속을 파견은 "구문 설탕"을 제공하지만, 당신은하지 않습니다 필요가 그것을 사용할 수 있습니다.

따라서 미들웨어가 없으면 액션 제작자가

// action creator
function loadData(dispatch, userId) { // needs to dispatch, so it is first argument
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
}

그러나 Thunk Middleware를 사용하면 다음과 같이 작성할 수 있습니다.

// action creator
function loadData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do
}

따라서 큰 차이는 없습니다. 후자의 접근 방식에 대해 내가 좋아하는 한 가지는 구성 요소가 액션 생성자가 비동기인지 신경 쓰지 않는다는 것입니다. dispatch일반적으로 호출 mapDispatchToProps하고 짧은 구문 등으로 이러한 액션 생성자를 바인딩 하는 사용할 수도 있습니다 . 구성 요소는 액션 생성자가 구현되는 방법을 모르며 다른 비동기 접근 방식 (Redux Thunk, Redux Promise, Redux Saga)간에 전환 할 수 있습니다. ) 구성 요소를 변경하지 않고. 반면에 이전의 명시 적 접근 방식을 사용하면 구성 요소 가 특정 호출이 비동기 적이며 일부 규칙 (예 : 동기화 매개 변수)에 의해 전달되어야 함을 정확히 알고 dispatch있습니다.

또한이 코드가 어떻게 변경되는지 생각해보십시오. 두 번째 데이터로드 기능을 하나의 액션 생성기로 결합하고 싶다고 가정 해 보겠습니다.

첫 번째 접근 방식에서는 우리가 어떤 액션 크리에이터라고 부르는지 염두에 두어야합니다.

// action creators
function loadSomeData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(dispatch, userId) {
  return Promise.all(
    loadSomeData(dispatch, userId), // pass dispatch first: it's async
    loadOtherData(dispatch, userId) // pass dispatch first: it's async
  );
}


// component
componentWillMount() {
  loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first
}

Redux Thunk 액션 제작자는 dispatch다른 액션 제작자의 결과를 얻을 수 있으며 동기식인지 비동기식인지 생각조차 할 수 없습니다.

// action creators
function loadSomeData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(userId) {
  return dispatch => Promise.all(
    dispatch(loadSomeData(userId)), // just dispatch normally!
    dispatch(loadOtherData(userId)) // just dispatch normally!
  );
}


// component
componentWillMount() {
  this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally!
}

이 접근 방식을 사용하면 나중에 액션 생성자가 현재 Redux 상태를 살펴 보도록하려는 경우 getState호출 코드를 전혀 수정하지 않고 썽크에 전달 된 두 번째 인수를 사용할 수 있습니다 .

function loadSomeData(userId) {
  // Thanks to Redux Thunk I can use getState() here without changing callers
  return (dispatch, getState) => {
    if (getState().data[userId].isLoaded) {
      return Promise.resolve();
    }

    fetch(`http://data.com/${userId}`)
      .then(res => res.json())
      .then(
        data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
        err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
      );
  }
}

동기식으로 변경해야하는 경우 호출 코드를 변경하지 않고도이 작업을 수행 할 수 있습니다.

// I can change it to be a regular action creator without touching callers
function loadSomeData(userId) {
  return {
    type: 'LOAD_SOME_DATA_SUCCESS',
    data: localStorage.getItem('my-data')
  }
}

따라서 Redux Thunk 또는 Redux Promise와 같은 미들웨어를 사용할 때의 이점은 구성 요소가 작업 생성자가 구현되는 방식, Redux 상태에 관심이 있는지 여부, 동기 또는 비동기 여부, 다른 작업 생성자를 호출하는지 여부를 알지 못한다는 것입니다. . 단점은 약간의 간접적이지만 실제 응용 프로그램에서는 그만한 가치가 있다고 생각합니다.

마지막으로 Redux Thunk와 친구들은 Redux 앱의 비동기 요청에 대한 가능한 접근 방식 중 하나 일뿐입니다. 또 다른 흥미로운 접근 방식은 Redux Saga 입니다. 이 방법 을 사용하면 작업을 수행 할 때 작업을 수행하고 작업을 출력하기 전에 요청을 변환하거나 수행하는 장기 실행 데몬 ( "sagas")을 정의 할 수 있습니다. 이것은 액션 제작자의 논리를 무용담으로 이동시킵니다. 그것을 확인하고 나중에 가장 적합한 것을 선택하고 싶을 수도 있습니다.

단서를 찾기 위해 Redux 저장소를 검색 한 결과, 과거에는 Action Creator가 순수한 기능이어야한다는 것을 알았습니다.

이것은 올바르지 않습니다. 문서는 이것을 말했지만 문서가 잘못되었습니다.
액션 제작자는 순수한 기능이 필요하지 않았습니다.
이를 반영하기 위해 문서를 수정했습니다.


당신은하지 않습니다.

하지만 ... redux-saga를 사용해야합니다. :)

Dan Abramov의 대답은 redux-thunk맞지만 꽤 비슷하지만 더 강력한 redux-saga 에 대해 조금 더 이야기하겠습니다 .

명령형 VS 선언적

  • DOM : jQuery는 필수 / React는 선언적
  • 모나드 : IO는 필수 / Free는 선언적입니다.
  • Redux 효과 : redux-thunk필수 / redux-saga선언

IO 모나드 또는 약속과 같은 썽크가 손에 있으면 실행 한 후에 수행 할 작업을 쉽게 알 수 없습니다. 썽크를 테스트하는 유일한 방법은 그것을 실행하고 디스패처를 조롱하는 것입니다 (또는 더 많은 것들과 상호 작용하는 경우 전체 외부 세계 ...).

mock을 사용하는 경우 함수형 프로그래밍을 수행하지 않습니다.

부작용의 렌즈를 통해 볼 수있는 모의는 코드가 불순하다는 플래그이며, 기능적 프로그래머의 눈에는 무언가 잘못되었다는 증거입니다. 빙산이 손상되지 않았는지 확인하는 데 도움이되는 라이브러리를 다운로드하는 대신 그 주변을 항해해야합니다. 하드 코어 TDD / Java 녀석이 Clojure에서 어떻게 조롱하는지 물었습니다. 대답은 일반적으로 그렇지 않다는 것입니다. 일반적으로 코드를 리팩토링하는 데 필요한 신호로 봅니다.

출처

sagas (에서 구현 됨 redux-saga)는 선언적이며 Free 모나드 또는 React 구성 요소처럼 모의없이 테스트하기가 훨씬 쉽습니다.

기사를 참조하십시오 :

현대 FP에서 우리는 프로그램을 작성해서는 안됩니다. 프로그램에 대한 설명을 작성해야합니다. 그런 다음 마음대로 검사하고 변형하고 해석 할 수 있습니다.

(실제로 Redux-saga는 하이브리드와 같습니다. 흐름은 필수적이지만 효과는 선언적입니다)

혼란 : 작업 / 이벤트 / 명령 ...

CQRS / EventSourcing 및 Flux / Redux와 같은 일부 백엔드 개념이 관련 될 수있는 방법에 대해 프런트 엔드 세계에서 많은 혼란이 있습니다. 대부분 Flux에서는 명령형 코드 ( LOAD_USER)와 이벤트 ( USER_LOADED). 이벤트 소싱처럼 이벤트 만 발송해야한다고 생각합니다.

실제로 sagas 사용

사용자 프로필에 대한 링크가있는 앱을 상상해보십시오. 두 미들웨어로 이것을 처리하는 관용적 방법은 다음과 같습니다.

redux-thunk

<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>

function loadUserProfile(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
      err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
    );
}

redux-saga

<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>


function* loadUserProfileOnNameClick() {
  yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}

function* fetchUser(action) {
  try {
    const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
    yield put({ type: 'USER_PROFILE_LOADED', userProfile })
  } 
  catch(err) {
    yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
  }
}

이 사가는 다음과 같이 번역됩니다.

사용자 이름이 클릭 될 때마다 사용자 프로필을 가져온 다음로드 된 프로필과 함께 이벤트를 전달합니다.

보시다시피 redux-saga.

takeLatest마지막으로 클릭 한 사용자 이름의 데이터 만 가져 오는 데 관심이 있음을 나타내는 허가 의 사용 (사용자가 많은 사용자 이름을 매우 빠르게 클릭하는 경우 동시성 문제 처리). 이런 종류의 물건은 썽크에게는 어렵습니다. takeEvery이 동작을 원하지 않으면 사용할 수 있습니다 .

액션 크리에이터를 순수하게 유지합니다. actionCreators (sagas put및 구성 요소에 있음 dispatch) 를 유지하는 것이 여전히 유용합니다 . 향후 작업 유효성 검사 (어설 션 / 흐름 / 유형 스크립트)를 추가하는 데 도움이 될 수 있습니다.

효과가 선언적이므로 코드를 훨씬 더 테스트 할 수 있습니다.

더 이상 .NET과 같은 rpc와 유사한 호출을 트리거 할 필요가 없습니다 actions.loadUser(). UI는 HAS HAPPENED를 전달하기 만하면됩니다. 이벤트 (항상 과거형!) 만 발생 하고 더 이상 동작은 발생하지 않습니다. 즉, 분리 된 "더크" 또는 바인딩 된 컨텍스트를 생성 할 수 있으며 saga가 이러한 모듈 식 구성 요소 간의 연결 지점 역할을 할 수 있습니다.

즉, 발생한 일과 효과로 발생해야하는 일 사이에 번역 레이어를 더 이상 포함 할 필요가 없기 때문에 뷰를 관리하기가 더 쉽습니다.

예를 들어 무한 스크롤 뷰를 상상해보십시오. CONTAINER_SCROLLED로 이어질 수 NEXT_PAGE_LOADED있지만 다른 페이지를로드해야하는지 여부를 결정하는 것은 실제로 스크롤 가능한 컨테이너의 책임입니까? 그런 다음 그는 마지막 페이지가 성공적으로로드되었는지 여부 또는로드를 시도하는 페이지가 이미 있는지 또는로드 할 항목이 더 이상 남아 있는지 여부와 같은 더 복잡한 사항을 알고 있어야합니다. 나는 그렇게 생각하지 않는다 : 최대한의 재사용 성을 위해 스크롤 가능한 컨테이너는 단지 그것이 스크롤되었다고 설명해야한다. 페이지 로딩은 해당 스크롤의 "비즈니스 효과"입니다.

일부는 생성기가 본질적으로 지역 변수를 사용하여 redux 저장소 외부의 상태를 숨길 수 있다고 주장 할 수 있지만 타이머 등을 시작하여 썽크 내부에서 복잡한 것을 조정하기 시작하면 어쨌든 동일한 문제가 발생합니다. 그리고 select이제 Redux 스토어에서 일부 상태를 가져올 수 있는 효과가 있습니다.

Sagas는 시간 여행을 할 수 있으며 현재 작업중인 복잡한 흐름 로깅 및 개발 도구를 사용할 수도 있습니다. 다음은 이미 구현 된 간단한 비동기 흐름 로깅입니다.

saga 흐름 로깅

디커플링

Sagas는 redux thunk를 대체 할뿐만 아니라 백엔드 / 분산 시스템 / 이벤트 소싱에서 제공됩니다.

sagas가 redux 썽크를 더 나은 테스트 가능성으로 대체하기 위해 여기에 있다는 것은 매우 일반적인 오해입니다. 실제로 이것은 redux-saga의 구현 세부 사항입니다. 선언적 효과를 사용하는 것이 테스트 가능성을 위해 썽크보다 낫지 만 saga 패턴은 명령형 또는 선언적 코드 위에 구현할 수 있습니다.

우선, saga는 장기 실행 트랜잭션 (최종 일관성)과 서로 다른 경계 컨텍스트 (도메인 기반 설계 전문 용어)에서 트랜잭션을 조정할 수있는 소프트웨어입니다.

프론트 엔드 세계에서 이것을 단순화하기 위해 widget1과 widget2가 있다고 가정하십시오. widget1의 일부 버튼을 클릭하면 widget2에 영향을 미칠 것입니다. 두 위젯을 함께 결합하는 대신 (예 : widget1이 widget2를 대상으로하는 작업을 전달 함) widget1은 해당 버튼이 클릭되었음을 전달합니다. 그런 다음 saga는이 버튼 클릭을 수신 한 다음 widget2가 인식하는 새 이벤트를 표시하여 widget2를 업데이트합니다.

이렇게하면 단순한 앱에 필요하지 않은 간접 수준이 추가되지만 복잡한 애플리케이션을보다 쉽게 ​​확장 할 수 있습니다. 이제 widget1 및 widget2를 서로 다른 npm 저장소에 게시 할 수 있으므로 글로벌 작업 레지스트리를 공유하지 않고도 서로에 대해 알 필요가 없습니다. 2 개의 위젯은 이제 개별적으로 존재할 수있는 제한된 컨텍스트입니다. 서로 일관성을 유지할 필요는 없으며 다른 앱에서도 재사용 할 수 있습니다. saga는 비즈니스에 의미있는 방식으로 조정하는 두 위젯 간의 연결 지점입니다.

디커플링 이유로 Redux-saga를 사용할 수있는 Redux 앱을 구성하는 방법에 대한 몇 가지 멋진 기사 :

구체적인 사용 사례 : 알림 시스템

내 구성 요소가 인앱 알림 표시를 트리거 할 수 있기를 원합니다. 그러나 내 구성 요소가 자체 비즈니스 규칙 (동시에 최대 3 개의 알림 표시, 알림 대기열, 4 초 표시 시간 등)이있는 알림 시스템과 밀접하게 결합되는 것을 원하지 않습니다.

내 JSX 구성 요소가 알림 표시 / 숨기기시기를 결정하는 것을 원하지 않습니다. 알림을 요청하고 복잡한 규칙은 무용담 안에 남겨 두는 기능 만 제공합니다. 이런 종류의 물건은 썽크 또는 약속으로 구현하기가 매우 어렵습니다.

알림

여기 에 saga로 어떻게 할 수 있는지 설명 했습니다.

사가라고하는 이유는?

saga라는 용어는 백엔드 세계에서 나왔습니다. 나는 처음에 Yassine (Redux-saga의 저자)을 긴 토론 에서 그 용어에 소개했습니다 .

처음에이 용어는 문서 와 함께 도입되었으며 , saga 패턴은 분산 트랜잭션의 최종 일관성을 처리하는 데 사용되어야했지만 백엔드 개발자에 의해 더 넓은 정의로 확장되어 이제 "프로세스 관리자"도 포함됩니다. 패턴 (어쨌든 원래 saga 패턴은 프로세스 관리자의 특수한 형태입니다).

오늘날 "사가"라는 용어는 두 가지 다른 것을 설명 할 수 있기 때문에 혼란 스럽습니다. redux-saga에서 사용되기 때문에 분산 트랜잭션을 처리하는 방법이 아니라 앱에서 작업을 조정하는 방법을 설명합니다. redux-saga또한 호출 될 수 있습니다 redux-process-manager.

또한보십시오:

대안

제너레이터 사용 아이디어가 마음에 들지 않지만 saga 패턴과 그 디커플링 속성에 관심이 있다면 이름 사용 하여 정확히 동일한 패턴을 설명하는 redux-observable 을 사용 epic하지만 RxJS 를 사용하여 동일한 결과를 얻을 수도 있습니다. 이미 Rx에 익숙하다면 집에있는 것처럼 느낄 것입니다.

const loadUserProfileOnNameClickEpic = action$ =>
  action$.ofType('USER_NAME_CLICKED')
    .switchMap(action =>
      Observable.ajax(`http://data.com/${action.payload.userId}`)
        .map(userProfile => ({
          type: 'USER_PROFILE_LOADED',
          userProfile
        }))
        .catch(err => Observable.of({
          type: 'USER_PROFILE_LOAD_FAILED',
          err
        }))
    );

redux-saga 유용한 리소스

2017 조언

  • Redux-saga를 사용하기 위해 남용하지 마십시오. 테스트 가능한 API 호출만으로는 가치가 없습니다.
  • 대부분의 간단한 경우 프로젝트에서 썽크를 제거하지 마십시오.
  • yield put(someActionThunk)의미 있다면 망설이지 말고 썽크를 파견하십시오 .

Redux-saga (또는 Redux-observable)를 사용하는 것이 두렵지 만 디커플링 패턴 만 필요한 경우 redux-dispatch-subscribe를 확인하십시오 . 이것은 디스패치를 ​​수신하고 리스너에서 새로운 디스패치를 ​​트리거 할 수 있도록 허용합니다.

const unsubscribe = store.addDispatchListener(action => {
  if (action.type === 'ping') {
    store.dispatch({ type: 'pong' });
  }
});

짧은 대답 : 나에게 비 동시성 문제에 대한 완전히 합리적인 접근처럼 보입니다. 몇 가지주의 사항이 있습니다.

방금 직장에서 시작한 새 프로젝트를 작업 할 때도 비슷한 생각이 들었습니다. 저는 저장소를 업데이트하고 React 구성 요소 트리의 내장에서 벗어나는 방식으로 구성 요소를 다시 렌더링하는 바닐라 Redux의 우아한 시스템의 열렬한 팬이었습니다. dispatch비 동시성을 처리하기 위해 우아한 메커니즘에 연결하는 것이 나에게는 이상하게 보였습니다 .

결국 우리 프로젝트에서 고려한 라이브러리에있는 것과 매우 유사한 접근 방식을 사용하게되었습니다. 우리는 react-redux-controller 라고 불렀습니다 .

나는 몇 가지 이유로 위의 정확한 접근 방식을 사용하지 않았습니다.

  1. 당신이 작성한 방식대로, 그 디스패치 함수는 상점에 액세스 할 수 없습니다. UI 구성 요소가 디스패치 함수에 필요한 모든 정보를 전달하면이 문제를 해결할 수 있습니다. 그러나 이것은 이러한 UI 구성 요소를 불필요하게 디스패치 로직에 연결한다고 주장합니다. 그리고 더 문제가되는 것은 디스 패칭 함수가 비동기 연속에서 업데이트 된 상태에 액세스 할 수있는 확실한 방법이 없다는 것입니다.
  2. 디스패치 함수는 dispatch어휘 범위를 통해 자신에 액세스 할 수 있습니다. 이는 해당 connect문이 손에서 벗어나면 리팩토링 옵션을 제한하며 한 가지 update방법 만으로는 매우 다루기 어려워 보입니다 . 따라서 이러한 디스패처 함수를 별도의 모듈로 분할 할 경우 구성 할 수있는 시스템이 필요합니다.

종합 dispatch하면 이벤트의 매개 변수와 함께 디스패치 함수에 스토어를 주입 할 수 있도록 일부 시스템을 구성해야합니다 . 이 종속성 주입에 대한 세 가지 합리적인 접근 방식을 알고 있습니다.

  • redux-thunk 는이를 썽크 에 전달하여 기능적인 방식으로 수행합니다 (돔 정의에 따라 정확히 썽크가 아님). 나는 다른 dispatch미들웨어 접근 방식으로 작업하지 않았지만 기본적으로 동일하다고 가정합니다.
  • react-redux-controller는 코 루틴으로이를 수행합니다. 보너스로 connect, 정규화 된 원시 저장소로 직접 작업하지 않고에 대한 첫 번째 인수로 전달했을 수있는 함수 인 "선택자"에 대한 액세스 권한도 제공합니다 .
  • this다양한 가능한 메커니즘을 통해 컨텍스트 에 삽입하여 객체 지향 방식으로 수행 할 수도 있습니다 .

최신 정보

이 수수께끼의 일부가 react-redux 의 한계라는 생각이 듭니다 . connect상태 스냅 샷 가져 오지만 디스패치하지 않는 첫 번째 인수 입니다. 두 번째 인수는 전달되지만 상태가 아닙니다. 연속 / 콜백시 업데이트 된 상태를 볼 수 있기 때문에 두 인수 모두 현재 상태에 대해 닫히는 썽크를 얻지 못합니다.


Abramov의 목표 (이상적으로는 모든 사람의 경우 )는 가장 적절한 위치에 복잡성 (및 비동기 호출)캡슐화하는 것 입니다.

표준 Redux 데이터 흐름에서이를 수행하는 가장 좋은 장소는 어디입니까? 어때 :

  • 감속기 ? 절대 안돼. 부작용이없는 순수 함수 여야합니다. 상점 업데이트는 심각하고 복잡한 업무입니다. 그것을 오염시키지 마십시오.
  • Dumb View 구성 요소? 확실히 아니요. 프레젠테이션과 사용자 상호 작용이라는 한 가지 우려 사항이 있으며 가능한 한 간단해야합니다.
  • 컨테이너 구성 요소? 가능하지만 차선책입니다. 컨테이너가 뷰 관련 복잡성을 캡슐화하고 상점과 상호 작용하는 장소라는 점에서 의미가 있지만 다음과 같습니다.
    • 컨테이너는 멍청한 구성 요소보다 더 복잡해야하지만 뷰와 상태 / 스토어 간의 바인딩을 제공하는 것은 여전히 ​​단일 책임입니다. 비동기 논리는 그것과는 완전히 별개의 문제입니다.
    • 컨테이너에 배치하면 단일 뷰 / 라우트에 대해 비동기 논리를 단일 컨텍스트로 잠 그게됩니다. 나쁜 생각. 이상적으로는 모두 재사용 가능하고 완전히 분리됩니다.
  • S 오메 다른 서비스 모듈은? 나쁜 생각 : 스토어에 대한 접근 권한을 주입해야하는데, 이는 유지 보수 가능성 / 테스트 가능성의 악몽입니다. Redux를 사용하고 제공된 API / 모델을 사용하여 상점에 액세스하는 것이 좋습니다.
  • 이를 해석하는 액션과 미들웨어? 왜 안돼?! 우선, 우리가 남긴 유일한 주요 옵션입니다. :-) 더 논리적으로, 액션 시스템은 어디에서나 사용할 수있는 분리 된 실행 로직입니다. 상점에 대한 액세스 권한이 있으며 더 많은 작업을 전달할 수 있습니다. 애플리케이션 주변의 제어 및 데이터 흐름을 구성하는 단일 책임이 있으며 대부분의 비동기가 여기에 적합합니다.
    • 액션 크리에이터는 어떻습니까? 작업 자체와 미들웨어 대신 비 동기화를 수행하지 않는 이유는 무엇입니까?
      • 가장 중요한 것은 제작자가 미들웨어처럼 스토어에 액세스 할 수 없다는 것입니다. 즉, 새로운 조건부 작업을 전달할 수없고, 비동기를 구성하기 위해 저장소에서 읽을 수 없습니다.
      • 따라서 복잡하게 필요한 곳에 복잡성을 유지하고 나머지는 모두 단순하게 유지하십시오. 그런 다음 제작자는 테스트하기 쉬운 단순하고 비교적 순수한 함수가 될 수 있습니다.

처음에 묻는 질문에 답하려면 :

컨테이너 구성 요소가 비동기 API를 호출 한 다음 작업을 전달할 수없는 이유는 무엇입니까?

해당 문서는 Redux와 React가 아닌 Redux 용이라는 것을 명심하십시오. React 구성 요소에 연결된 Redux 저장소는 사용자가 말하는대로 정확하게 수행 할 수 있지만 미들웨어가없는 Plain Jane Redux 저장소는 dispatch일반 ol '객체 제외하고 인수를 허용하지 않습니다 .

미들웨어 없이는 물론 할 수 있습니다.

const store = createStore(reducer);
MyAPI.doThing().then(resp => store.dispatch(...));

하지만 그것은 비동기 래핑 된 유사한 사건 의 주위에 돌아 오는 것이 아니라 처리 돌아 오는이. 따라서 미들웨어는에 직접 전달할 수있는 내용을 수정하여 비 동시성을 허용합니다 dispatch.


즉, 귀하의 제안의 정신은 타당하다고 생각합니다. Redux + React 애플리케이션에서 비 동시성을 처리 할 수있는 다른 방법이 있습니다.

미들웨어 사용의 한 가지 이점은 정확히 어떻게 연결되는지에 대해 걱정하지 않고 평소처럼 액션 생성자를 계속 사용할 수 있다는 것입니다. 예를 들어를 사용 redux-thunk하면 작성한 코드는 다음과 유사합니다.

function updateThing() {
  return dispatch => {
    dispatch({
      type: ActionTypes.STARTED_UPDATING
    });
    AsyncApi.getFieldValue()
      .then(result => dispatch({
        type: ActionTypes.UPDATED,
        payload: result
      }));
  }
}

const ConnectedApp = connect(
  (state) => { ...state },
  { update: updateThing }
)(App);

원본과 크게 다르지 않고 약간 섞여서 비동기식 (또는 connect그럴 updateThing필요가 있음)을 알지 못합니다 .

promises , observables , sagas 또는 미친 커스텀고도로 선언적인 액션 제작자 를 지원하고 싶다면 Redux는 전달하는 항목 dispatch(액션 제작자로부터 반환 하는 항목)을 변경하여 수행 할 수 있습니다 . React 구성 요소 (또는 connect호출)를 다룰 필요가 없습니다.


자, 먼저 미들웨어가 어떻게 작동하는지 살펴 보겠습니다. 질문에 답할 수 있습니다. 이것은 Redux 의 소스 코드 인 pplyMiddleWare 함수입니다.

function applyMiddleware() {
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
      var store = createStore(reducer, preloadedState, enhancer);
      var _dispatch = store.dispatch;
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      _dispatch = compose.apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

이 부분을보고, 디스패치어떻게 함수되는지보세요 .

  ...
  getState: store.getState,
  dispatch: function dispatch(action) {
  return _dispatch(action);
}
  • 각 미들웨어에는 dispatchgetState함수가 명명 된 인수로 제공됩니다.

좋아, 이것이 Redux 에서 가장 많이 사용되는 미들웨어 중 하나 인 Redux-thunk 가 자신을 소개하는 방법입니다.

Redux Thunk 미들웨어를 사용하면 작업 대신 함수를 반환하는 작업 생성자를 작성할 수 있습니다. 썽 크는 작업 전달을 지연하거나 특정 조건이 충족되는 경우에만 전달하는 데 사용할 수 있습니다. 내부 함수는 매개 변수로 저장 메소드 dispatch 및 getState를 수신합니다.

보시다시피 액션 대신 함수를 반환합니다. 즉, 함수이기 때문에 언제든지 기다렸다가 호출 할 수 있습니다.

그래서 도대체 무엇입니까? 이것이 Wikipedia에 소개 된 방법입니다.

컴퓨터 프로그래밍에서 썽 크는 다른 서브 루틴에 추가 계산을 삽입하는 데 사용되는 서브 루틴입니다. Thunk는 주로 필요할 때까지 계산을 지연하거나 다른 서브 루틴의 시작 또는 끝에 작업을 삽입하는 데 사용됩니다. 그들은 컴파일러 코드 생성 및 모듈 식 프로그래밍에 대한 다양한 다른 응용 프로그램을 가지고 있습니다.

이 용어는 "생각"의 조속한 파생어에서 유래되었습니다.

썽 크는 평가를 지연시키기 위해 표현식을 래핑하는 함수입니다.

//calculation of 1 + 2 is immediate 
//x === 3 
let x = 1 + 2;

//calculation of 1 + 2 is delayed 
//foo can be called later to perform the calculation 
//foo is a thunk! 
let foo = () => 1 + 2;

개념이 얼마나 쉬운 지, 비동기 작업을 관리하는 데 어떻게 도움이되는지 알아보세요.

그것은 당신이 그것 없이도 살 수있는 것입니다. 그러나 프로그래밍에는 항상 일을하는 더 좋고 깔끔하고 적절한 방법이 있다는 것을 기억하십시오 ...

미들웨어 Redux 적용


Redux-saga를 사용하는 것은 React-redux 구현에서 최고의 미들웨어입니다.

예 : store.js

  import createSagaMiddleware from 'redux-saga';
  import { createStore, applyMiddleware } from 'redux';
  import allReducer from '../reducer/allReducer';
  import rootSaga from '../saga';

  const sagaMiddleware = createSagaMiddleware();
  const store = createStore(
     allReducer,
     applyMiddleware(sagaMiddleware)
   )

   sagaMiddleware.run(rootSaga);

 export default store;

그리고 saga.js

import {takeLatest,delay} from 'redux-saga';
import {call, put, take, select} from 'redux-saga/effects';
import { push } from 'react-router-redux';
import data from './data.json';

export function* updateLesson(){
   try{
       yield put({type:'INITIAL_DATA',payload:data}) // initial data from json
       yield* takeLatest('UPDATE_DETAIL',updateDetail) // listen to your action.js 
   }
   catch(e){
      console.log("error",e)
     }
  }

export function* updateDetail(action) {
  try{
       //To write store update details
   }  
    catch(e){
       console.log("error",e)
    } 
 }

export default function* rootSaga(){
    yield [
        updateLesson()
       ]
    }

그리고 action.js

 export default function updateFruit(props,fruit) {
    return (
       {
         type:"UPDATE_DETAIL",
         payload:fruit,
         props:props
       }
     )
  }

그리고 reducer.js

import {combineReducers} from 'redux';

const fetchInitialData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
 const updateDetailsData = (state=[],action) => {
    switch(action.type){
      case "INITIAL_DATA":
          return ({type:action.type, payload:action.payload});
          break;
      }
     return state;
  }
const allReducers =combineReducers({
   data:fetchInitialData,
   updateDetailsData
 })
export default allReducers; 

그리고 main.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/components/App.jsx';
import {Provider} from 'react-redux';
import store from './app/store';
import createRoutes from './app/routes';

const initialState = {};
const store = configureStore(initialState, browserHistory);

ReactDOM.render(
       <Provider store={store}>
          <App />  /*is your Component*/
       </Provider>, 
document.getElementById('app'));

시도 해봐 ..


동기 액션 생성자가 있고 비동기 액션 생성자가 있습니다.

동기식 액션 생성자는 호출 할 때 해당 객체에 연결된 모든 관련 데이터가 포함 된 Action 객체를 즉시 반환하고 리듀서에서 처리 할 준비가 된 것입니다.

비동기식 액션 생성자는 결국 액션을 전달할 준비가되기까지 약간의 시간이 필요합니다.

정의에 따라 네트워크 요청을하는 액션 생성자가있을 때마다 항상 비동기 액션 생성자로 자격이 부여됩니다.

Redux 애플리케이션 내부에 비동기 액션 생성자를 갖고 싶다면 이러한 비동기 액션 생성자를 처리 할 수있는 미들웨어를 설치해야합니다.

비동기 작업에 사용자 지정 미들웨어를 사용한다는 오류 메시지에서이를 확인할 수 있습니다.

그렇다면 미들웨어는 무엇이며 Redux의 비동기 흐름에 왜 필요합니까?

redux-thunk와 같은 redux 미들웨어의 맥락에서 미들웨어는 Redux가 즉시 처리 할 수없는 비동기 작업 생성자를 처리하는 데 도움이됩니다.

Redux 사이클에 통합 된 미들웨어로, 우리는 여전히 액션 생성자를 호출하고 있습니다. 이것은 전달 될 액션을 반환 할 것입니다. 그러나 이제 액션을 모든 리듀서로 직접 보내는 것이 아니라 액션을 전달할 때 애플리케이션 내부의 모든 미들웨어를 통해 조치가 전송 될 것입니다.

단일 Redux 앱 내에서 원하는만큼 미들웨어를 가질 수 있습니다. 대부분의 경우 우리가 작업하는 프로젝트에서 하나 또는 두 개의 미들웨어가 Redux 스토어에 연결됩니다.

미들웨어는 우리가 전달하는 모든 단일 작업과 함께 호출되는 일반 JavaScript 함수입니다. 그 기능 내에서 미들웨어는 어떤 리듀서로든 액션이 전달되는 것을 막을 수있는 기회를 가지고 있습니다. 액션을 수정하거나 어떤 방식 으로든 액션을 엉망으로 만들 수 있습니다. 예를 들어 콘솔이 기록하는 미들웨어를 만들 수 있습니다. 보는 즐거움을 위해 보내는 모든 작업.

프로젝트에 종속성으로 설치할 수있는 수많은 오픈 소스 미들웨어가 있습니다.

오픈 소스 미들웨어를 사용하거나 종속성으로 설치하는 데만 국한되지 않습니다. 사용자 지정 미들웨어를 작성하고 Redux 스토어 내에서 사용할 수 있습니다.

미들웨어의 가장 인기있는 용도 중 하나 (그리고 답을 얻는 방법)는 비동기 액션 생성자를 다루는 것입니다. 아마도 가장 인기있는 미들웨어는 아마도 redux-thunk이며 비동기 액션 생성자를 다루는 데 도움이 될 것입니다.

비동기 작업 생성자를 처리하는 데 도움이되는 다른 유형의 미들웨어가 많이 있습니다.


질문에 답하려면 :

컨테이너 구성 요소가 비동기 API를 호출 한 다음 작업을 전달할 수없는 이유는 무엇입니까?

나는 적어도 두 가지 이유로 말할 것입니다.

첫 번째 이유는 우려 사항의 분리입니다. action creator를 호출하고 api데이터를 다시 가져 오는 것은 작업이 아닙니다 . action creator function, the action type및 a에 두 개의 인수를 전달해야 합니다 payload.

두 번째 이유 redux store는는 필수 작업 유형과 선택적으로 a가있는 일반 객체를 기다리고 있기 때문 입니다 payload(하지만 여기서는 페이로드도 전달해야합니다).

액션 생성자는 아래와 같은 일반 객체 여야합니다.

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

그리고하는 일 Redux-Thunk midlewaredispache당신의 결과 api call적절한에 action.

참고 URL : https://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux

반응형