[javascript] 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 Creators가 순수한 기능이어야한다는 것을 알았습니다. 예를 들어 다음 은 비동기 데이터 흐름에 대한 더 나은 설명을 제공하려는 사용자입니다.

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

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



답변

이 접근 방식에 어떤 문제가 있습니까? 설명서에서 제안하는 것처럼 왜 Redux Thunk 또는 Redux Promise를 사용하고 싶습니까?

이 방법에는 아무런 문제가 없습니다. 동일한 작업을 수행하는 다른 구성 요소가 있거나 일부 작업을 디 바운싱하거나 자동 증분 ID와 같은 로컬 상태를 작업 작성자와 가깝게 유지하기 때문에 대규모 응용 프로그램에서는 불편합니다. 유지 보수 관점은 조치 작성자를 별도의 기능으로 추출합니다.

보다 자세한 연습은 “시간 초과로 Redux 작업을 발송하는 방법”에 대한 나의 답변을 읽을 수 있습니다 .

Redux Thunk 또는 Redux Promise와 같은 미들웨어는 썽크 또는 약속을 발송하기위한 “구문 설탕”을 제공하지만 이를 사용할 필요는 없습니다 .

따라서 미들웨어가 없으면 액션 제작자가 다음과 같이 보일 수 있습니다.

// 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짧은 구문 등으로 그러한 액션 생성자를 바인딩 하는 데 사용할 수 있습니다 . 구성 요소를 변경하지 않고 반면, 이전의 명시 적 접근 방식을 사용하면 구성 요소 는 특정 호출이 비동기 임을 정확히 알고 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 입니다. Redux Saga 를 사용하면 수행되는 작업을 수행하고 작업을 출력하기 전에 요청을 변환하거나 수행하는 장기 실행 데몬 ( “sagas”)을 정의 할 수 있습니다. 이것은 로직을 액션 제작자에서 사 가스로 옮깁니다. 그것을 확인하고 나중에 가장 적합한 것을 선택하십시오.

나는 Redux 저장소에서 단서를 찾아서 과거에는 Action Creators가 순수한 기능이어야한다는 것을 알았습니다.

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


답변

당신은하지 않습니다.

그러나 … 당신은 redux-saga를 사용해야합니다 🙂

Dan Abramov의 대답은 옳습니다. redux-thunk그러나 나는 아주 비슷하지만 더 강력한 redux-saga 에 대해 조금 더 이야기 할 것 입니다.

명령형 VS 선언

  • DOM : jQuery는 필수적입니다 ./ 반응은 선언적입니다.
  • Monads : IO는 필수입니다 / 무료는 선언적입니다
  • 환원 효과 : redux-thunk명령 적 / redux-saga선언적

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

모의를 사용하는 경우 기능 프로그래밍을 수행하지 않는 것입니다.

부작용의 렌즈를 통해 볼 때 모의는 코드가 불완전하다는 플래그이며 함수형 프로그래머의 눈에는 문제가 있음을 증명합니다. 빙산이 손상되지 않았는지 확인하기 위해 라이브러리를 다운로드하는 대신 주변을 항해해야합니다. 하드 코어 TDD / 자바 남자가 Clojure에서 조롱하는 방법을 물었습니다. 대답은 보통 그렇지 않습니다. 우리는 일반적으로 코드를 리팩토링하는 데 필요한 신호로 본다.

출처

사가 redux-saga )는 선언적이며 Free 모나드 또는 React 구성 요소와 마찬가지로 모의 실험없이 훨씬 쉽게 테스트 할 수 있습니다.

기사를 참조하십시오 :

현대 FP에서는 프로그램을 작성해서는 안됩니다. 프로그램에 대한 설명을 작성해야합니다. 그러면 프로그램을 설명하고 변환하고 해석 할 수 있습니다.

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

혼란 : 액션 / 이벤트 / 명령 …

CQRS / EventSourcing 및 Flux / Redux와 같은 일부 백엔드 개념이 어떻게 관련 될 수 있는지에 대한 프론트 엔드 세계에는 많은 혼란이 있습니다. Flux에서는 종종 명령 코드 ( LOAD_USER)와 이벤트 ( USER_LOADED). 이벤트 소싱과 마찬가지로 이벤트 만 전달해야한다고 생각합니다.

실제로 사가 사용

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

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및 components dispatch) 를 유지하는 것이 여전히 유용합니다 . 앞으로 액션 검증 (어설 션 / 플로우 / 유형)을 추가하는 데 도움이 될 수 있습니다.

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

더 이상 rpc와 같은 호출을 트리거 할 필요가 없습니다 actions.loadUser(). UI는 HAPPENED를 전달하기 만하면됩니다. 우리는 사건을 발동 하고 (항상 시제로) 더 이상 행동하지 않습니다. 즉, 분리 된 “덕” 또는 바운드 컨텍스트를 만들 수 있으며 saga가 이러한 모듈 식 구성 요소 사이의 연결 지점 역할을 할 수 있습니다.

즉, 발생한 일과 결과로 발생하는 일 사이에 해당 변환 계층을 더 이상 포함 할 필요가 없으므로 뷰를보다 쉽게 ​​관리 할 수 ​​있습니다.

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

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

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

사가 흐름 로깅

디커플링

Sagas는 redux 썽크 만 교체하지 않습니다. 백엔드 / 분산 시스템 / 이벤트 소싱에서 나옵니다.

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

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

프론트 엔드 세계에서이를 단순화하려면 widget1과 widget2가 있다고 가정하십시오. widget1의 일부 단추를 클릭하면 widget2에 영향을줍니다. widget1은 위젯 2 개를 함께 결합하는 대신 (즉, widget1은 widget2를 대상으로하는 조치를 전달 함) 단추를 클릭 한 것만 전달합니다. 그런 다음 saga는이 단추 클릭을 청취하고 widget2가 인식하는 새 이벤트를 표시하여 widget2를 업데이트하십시오.

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

Redux 앱을 구성하는 방법에 대한 멋진 기사로, 디커플링 이유로 Redux-saga를 사용할 수 있습니다.

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

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

JSX 구성 요소가 알림을 표시하거나 숨길 시간을 결정하고 싶지 않습니다. 나는 단지 알림을 요청하는 능력을 부여하고, 사가 안에 복잡한 규칙을 남겨 둡니다. 이런 종류의 물건은 썽크 나 약속으로 구현하기가 매우 어렵습니다.

알림

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

왜 사가라고 불리는가?

사가라는 용어는 백엔드 세계에서 비롯됩니다. 나는 처음에 야사 인 (Redux-saga의 저자)을 긴 토론 에서 그 용어에 대해 소개했다 .

처음에는이 용어가 논문 과 함께 소개되었는데 , 사가 패턴은 분산 트랜잭션에서 최종 일관성을 처리하는 데 사용되었지만 백엔드 개발자는이를 “프로세스 관리자”도 다루도록 광범위하게 정의했습니다. 패턴 (어떻게도 원래의 사가 패턴은 프로세스 관리자의 특수한 형태입니다).

오늘날, “사가”라는 용어는 두 가지 다른 것을 설명 할 수 있으므로 혼동됩니다. redux-saga에서 사용되므로 분산 트랜잭션을 처리하는 방법이 아니라 앱의 작업을 조정하는 방법을 설명합니다. redux-saga라고도 할 수 있습니다 redux-process-manager.

또한보십시오:

대안

생성기를 사용하는 아이디어는 마음에 들지 않지만 saga 패턴 및 디커플링 특성에 관심이있는 경우 이름 을 사용 하여 정확히 동일한 패턴을 설명하지만 RxJS 를 사용하는 redux-observable 을 사용하여 epic동일한 결과를 얻을 수도 있습니다. 이미 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 데이터 흐름에서 가장 좋은 곳은 어디입니까? 어때요?

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

답변

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

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

이러한 문서는 Redux plus React가 아니라 Redux 용입니다. React 컴포넌트에 연결된 Redux 스토어는 사용자가하는 말을 정확하게 수행 할 수 있지만 미들웨어가없는 Plain Jane Redux 스토어는 dispatch일반 객체 를 제외하고 인수를 허용하지 않습니다 .

미들웨어가 없으면 여전히 할 수 있습니다.

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비동기 인지 알 수 없습니다 .

약속 , 관찰 가능 , 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 미들웨어를 사용하면 조치 대신 함수를 리턴하는 조치 작성자를 작성할 수 있습니다. 썽 크는 조치의 디스패치를 ​​지연 시키거나 특정 조건이 충족되는 경우에만 디스패치하는 데 사용될 수 있습니다. 내부 함수는 store 메소드 dispatch 및 getState를 매개 변수로받습니다.

보시다시피, 액션 대신 함수를 반환합니다. 함수이므로 언제든지 호출하고 기다릴 수 있습니다.

도대체 뭐야? 그것이 Wikipedia에 소개 된 방법입니다.

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

이 용어는 “생각하는”의 파생적 파생어에서 유래했습니다.

썽 크는 식을 래핑하여 평가를 지연시키는 함수입니다.

//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'));

이것을 시도하십시오. 일하고 있습니다