[javascript] 시간 초과로 Redux 액션을 전달하는 방법은 무엇입니까?

내 응용 프로그램의 알림 상태를 업데이트하는 작업이 있습니다. 일반적으로이 알림은 오류 또는 일종의 정보입니다. 그런 다음 알림 상태를 초기 상태로 되돌릴 5 초 후에 다른 작업을 전달해야하므로 알림이 없습니다. 이것의 주된 이유는 5 초 후에 알림이 자동으로 사라지는 기능을 제공하기 위해서입니다.

setTimeout다른 작업 을 사용 하고 반환 하는 것에 대해 운이 없었으며 이것이 온라인에서 수행되는 방법을 찾을 수 없습니다. 따라서 어떤 조언도 환영합니다.



답변

도서관이 모든 것을하는 방법을 처방해야한다고 생각하는 함정에 빠지지 마십시오 . JavaScript에서 시간 초과가있는 작업을 수행하려면을 사용해야 setTimeout합니다. Redux 작업이 다른 이유는 없습니다.

Redux 비동기적인 것들을 다루는 몇 가지 대안을 제공하지만 너무 많은 코드를 반복한다는 것을 깨달을 때만 사용해야합니다. 이 문제가 없으면 언어가 제공하는 것을 사용하여 가장 간단한 해결책을 찾으십시오.

비동기 코드 인라인 작성

이것은 가장 간단한 방법입니다. 그리고 Redux에는 특별한 것이 없습니다.

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

마찬가지로 연결된 구성 요소 내부에서 :

this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  this.props.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

유일한 차이점은 연결된 컴포넌트에서 일반적으로 상점 자체에 액세스 할 수 없지만 dispatch()특정 조치 작성자가 소품으로 주입 된다는 것 입니다. 그러나 이것은 우리에게 아무런 영향을 미치지 않습니다.

다른 컴포넌트에서 동일한 조치를 디스패치 할 때 오타를 원하지 않는 경우 조치 오브젝트를 인라인으로 디스패치하는 대신 조치 작성자를 추출 할 수 있습니다.

// actions.js
export function showNotification(text) {
  return { type: 'SHOW_NOTIFICATION', text }
}
export function hideNotification() {
  return { type: 'HIDE_NOTIFICATION' }
}

// component.js
import { showNotification, hideNotification } from '../actions'

this.props.dispatch(showNotification('You just logged in.'))
setTimeout(() => {
  this.props.dispatch(hideNotification())
}, 5000)

또는 이전에 다음을 바인딩 한 경우 connect():

this.props.showNotification('You just logged in.')
setTimeout(() => {
  this.props.hideNotification()
}, 5000)

지금까지 미들웨어 또는 기타 고급 개념을 사용하지 않았습니다.

비동기 액션 생성기 추출

위의 방법은 간단한 경우에는 잘 작동하지만 몇 가지 문제가 있음을 알 수 있습니다.

  • 알림을 표시하려는 모든 위치에서이 논리를 복제해야합니다.
  • 알림에는 ID가 없으므로 두 개의 알림을 충분히 빨리 표시하면 경쟁 조건이 발생합니다. 첫 번째 시간 초과가 끝나면을 전달 HIDE_NOTIFICATION하여 시간 초과 후보다 빨리 두 번째 알림을 숨 깁니다.

이러한 문제점을 해결하려면 시간 종료 로직을 ​​중앙 집중화하고 해당 두 조치를 전달하는 함수를 추출해야합니다. 다음과 같이 보일 수 있습니다 :

// actions.js
function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
  // Assigning IDs to notifications lets reducer ignore HIDE_NOTIFICATION
  // for the notification that is not currently visible.
  // Alternatively, we could store the timeout ID and call
  // clearTimeout(), but we’d still want to do it in a single place.
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

이제 컴포넌트는 showNotificationWithTimeout이 로직을 복제하거나 다른 알림으로 경쟁 조건을 갖지 않고도 사용할 수 있습니다 .

// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')

// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')    

왜 첫 번째 주장으로 showNotificationWithTimeout()받아들 dispatch입니까? 상점에 조치를 전달해야하기 때문입니다. 일반적으로 컴포넌트는 액세스 할 수 dispatch있지만 외부 함수가 디스패치를 ​​제어하기를 원하므로 디스패치에 대한 제어를 제공해야합니다.

일부 모듈에서 단일 저장소를 내 보낸 경우 가져 와서 dispatch직접 가져올 수 있습니다 .

// store.js
export default createStore(reducer)

// actions.js
import store from './store'

// ...

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  const id = nextNotificationId++
  store.dispatch(showNotification(id, text))

  setTimeout(() => {
    store.dispatch(hideNotification(id))
  }, 5000)
}

// component.js
showNotificationWithTimeout('You just logged in.')

// otherComponent.js
showNotificationWithTimeout('You just logged out.')    

이 방법은 더 단순 해 보이지만 이 방법은 권장하지 않습니다 . 우리가 싫어하는 주된 이유는 store를 singleton으로 강제 하기 때문 입니다. 따라서 서버 렌더링 을 구현하기가 매우 어렵습니다 . 서버에서 각 요청마다 고유 한 저장소를 가지도록하여 다른 사용자가 서로 다른 사전로드 된 데이터를 얻도록합니다.

단일 저장소도 테스트를 어렵게 만듭니다. 조치 작성자가 특정 모듈에서 내 보낸 특정 실제 상점을 참조하므로 조치 작성자를 테스트 할 때 더 이상 상점을 조롱 할 수 없습니다. 외부에서 상태를 재설정 할 수도 없습니다.

따라서 기술적으로 모듈에서 단일 저장소를 내보낼 수는 있지만 권장하지는 않습니다. 앱에서 서버 렌더링을 추가하지 않을 것이 아니라면이 작업을 수행하지 마십시오.

이전 버전으로 돌아 가기 :

// actions.js

// ...

let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')

// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')    

이것은 로직 복제 문제를 해결하고 경쟁 조건에서 우리를 저장합니다.

썽크 미들웨어

간단한 앱의 경우 접근 방식으로 충분합니다. 행복하다면 미들웨어에 대해 걱정하지 마십시오.

그러나 더 큰 앱에서는 주변에 특정 불편이있을 수 있습니다.

예를 들어, 우리가 통과해야하는 것은 불행한 것 같습니다 dispatch. 위와 같은 방식으로 Redux 작업을 비동기식으로 전달하는 모든 구성 요소 는 소품 으로 받아 들여 더 이상 전달할 수 있기 때문에 컨테이너와 프레젠테이션 구성 요소분리 하기가 더 까다로워 dispatch집니다. 실제로 액션 제작자가 아니기 connect()때문에 액션 제작자를 더 이상 바인딩 할 수 없습니다 showNotificationWithTimeout(). Redux 작업을 반환하지 않습니다.

또한 어떤 함수가 동기 작업 작성자와 같은 showNotification()비동기 도우미 인지 기억하는 것은 어색 할 수 있습니다 showNotificationWithTimeout(). 서로 다르게 사용해야하며 서로 실수하지 않도록주의해야합니다.

이것은 도우미 기능에 이러한 패턴을 제공하는 방법을 찾고, dispatchRedux가 이러한 비동기식 액션 제작자를 완전히 다른 기능이 아닌 일반 액션 제작자의 특별한 경우로 “볼 수 있도록 하는 동기를 부여했습니다 .

여전히 우리와 함께 있고 앱에서 문제로 인식되면 Redux Thunk 미들웨어 를 사용하는 것이 좋습니다 .

요점에서 Redux Thunk는 Redux에게 실제로 작동하는 특수한 종류의 작업을 인식하도록 지시합니다.

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

const store = createStore(
  reducer,
  applyMiddleware(thunk)
)

// It still recognizes plain object actions
store.dispatch({ type: 'INCREMENT' })

// But with thunk middleware, it also recognizes functions
store.dispatch(function (dispatch) {
  // ... which themselves may dispatch many times
  dispatch({ type: 'INCREMENT' })
  dispatch({ type: 'INCREMENT' })
  dispatch({ type: 'INCREMENT' })

  setTimeout(() => {
    // ... even asynchronously!
    dispatch({ type: 'DECREMENT' })
  }, 1000)
})

이 미들웨어가 활성화 된 상태에서 함수를 디스패치하면 Redux Thunk 미들웨어가이를 dispatch인수로 제공합니다 . 또한 그러한 동작을 “삼키기”때문에 감속기가 이상한 함수 인수를받는 것에 대해 걱정하지 마십시오. 감속기는 직접 방출되거나 방금 설명한 기능에 의해 방출되는 일반 물체 작동 만받습니다.

이것은 매우 유용하게 보이지 않습니까? 이 특별한 상황에는 없습니다. 그러나 showNotificationWithTimeout()일반적인 Redux 액션 제작자로 선언 할 수 있습니다 .

// actions.js
function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch) {
    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

함수가 이전 섹션에서 작성한 함수와 거의 동일한 점에 유의하십시오. 그러나 dispatch첫 번째 주장으로 받아 들여지지 않습니다 . 대신 첫 번째 인수로 받아들이는 함수를 반환 합니다 dispatch.

컴포넌트에서 어떻게 사용합니까? 확실히, 우리는 이것을 쓸 수 있습니다 :

// component.js
showNotificationWithTimeout('You just logged in.')(this.props.dispatch)

우리는 단지 원하는 내부 기능을 얻을 수있는 비동기 작업 창조자를 호출 dispatch한 다음 우리가 통과 dispatch.

그러나 이것은 원래 버전보다 훨씬 어색합니다! 우리는 왜 그런 식으로 갔습니까?

내가 전에 한 말 때문에 Redux Thunk 미들웨어가 사용 가능한 경우 조치 오브젝트 대신 함수를 디스패치하려고 시도 할 때마다 미들웨어는 dispatch메소드 자체를 첫 번째 인수로 사용하여 해당 함수를 호출합니다 .

그래서 우리는 대신 이것을 할 수 있습니다 :

// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))

마지막으로, 비동기 액션 (실제로 일련의 액션)을 디스패치하는 것은 단일 액션을 컴포넌트에 동 기적으로 디스패치하는 것과 다르지 않습니다. 구성 요소가 동 기적으로 또는 비동기 적으로 발생하는지 여부를 신경 쓰지 않아야하기 때문에 좋습니다. 우리는 그것을 추상화했습니다.

우리가 “가르쳐”이후 돌아 오는이 (우리가 그들에게 전화 등의 “특별한”액션 제작자를 인식하는 것을 알 수 썽크 우리는 일반 액션 제작자을 사용 곳 액션 제작자), 우리는 지금 어떤 장소에서 사용할 수 있습니다. 예를 들어 다음과 connect()같이 사용할 수 있습니다 .

// actions.js

function showNotification(id, text) {
  return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
  return { type: 'HIDE_NOTIFICATION', id }
}

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch) {
    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

// component.js

import { connect } from 'react-redux'

// ...

this.props.showNotificationWithTimeout('You just logged in.')

// ...

export default connect(
  mapStateToProps,
  { showNotificationWithTimeout }
)(MyComponent)

뭉크의 읽기 상태

일반적으로 감속기는 다음 상태를 결정하기위한 비즈니스 로직을 포함합니다. 그러나 감속기는 작업이 발송 된 후에 만 ​​시작됩니다. 썽크 액션 생성자에 부작용 (예 : API 호출)이 있고 어떤 조건에서이를 방지하려면 어떻게해야합니까?

썽크 미들웨어를 사용하지 않고 구성 요소 내부 에서이 검사를 수행하면됩니다.

// component.js
if (this.props.areNotificationsEnabled) {
  showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}

그러나 액션 제작자를 추출하는 요점은 여러 구성 요소에서이 반복적 인 논리를 중앙 집중화하는 것이 었습니다. 다행히 Redux Thunk는 Redux 저장소의 현재 상태 를 읽을 수있는 방법을 제공합니다 . 뿐만 아니라 썽크 액션 생성자에서 반환하는 함수에 대한 두 번째 인수로 dispatch도 전달 getState됩니다. 이를 통해 썽 크는 상점의 현재 상태를 읽을 수 있습니다.

let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
  return function (dispatch, getState) {
    // Unlike in a regular action creator, we can exit early in a thunk
    // Redux doesn’t care about its return value (or lack of it)
    if (!getState().areNotificationsEnabled) {
      return
    }

    const id = nextNotificationId++
    dispatch(showNotification(id, text))

    setTimeout(() => {
      dispatch(hideNotification(id))
    }, 5000)
  }
}

이 패턴을 남용하지 마십시오. 사용 가능한 캐시 된 데이터가있을 때 API 호출을 제거하는 데는 좋지만 비즈니스 로직을 구축하기에는 아주 좋은 기초가 아닙니다. getState()조건부로 다른 조치를 디스패치 하는 데만 사용 하는 경우 비즈니스 로직을 리듀서에 대신 사용하십시오.

다음 단계

썽크의 작동 방식에 대한 기본적인 이해가 있으므로 이를 사용하는 Redux 비동기 예제 를 확인하십시오 .

썽크가 약속을 반환하는 많은 예를 찾을 수 있습니다. 이것은 필수는 아니지만 매우 편리합니다. Redux는 썽크에서 반환하는 것을 신경 쓰지 않지만에서 반환 값을 제공합니다 dispatch(). 그렇기 때문에 썽크에서 약속을 반환하고을 호출하여 완료 될 때까지 기다릴 수 있습니다 dispatch(someThunkReturningPromise()).then(...).

복잡한 썽크 액션 제작자를 여러 개의 작은 썽크 액션 제작자로 나눌 수도 있습니다. dispatch재귀 패턴을 적용 할 수 있도록 썽크에 의해 제공 방법은, 그 자체 썽크 받아 들일 수 있습니다. 다시 말하지만, 비동기 제어 흐름을 구현할 수 있기 때문에 Promises와 가장 잘 작동합니다.

일부 앱의 경우 비동기 제어 흐름 요구 사항이 너무 복잡하여 썽 크로 표현할 수없는 상황에 처할 수 있습니다. 예를 들어, 실패한 요청을 재 시도하거나 토큰을 사용한 재 인증 흐름 또는 단계별 온 보딩은 너무 자세하고 오류가 발생하기 쉽습니다. 이 경우 Redux Saga 또는 Redux Loop 와 같은 고급 비동기 제어 흐름 솔루션을 살펴볼 수 있습니다 . 그것들을 평가하고, 귀하의 필요와 관련된 예를 비교하고, 가장 좋아하는 것을 선택하십시오.

마지막으로 꼭 필요한 것이 없다면 썽크를 포함하여 아무 것도 사용하지 마십시오. 요구 사항에 따라 솔루션은 다음과 같이 단순 해 보일 수 있습니다.

store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
  store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)

왜 이런 짓을하는지 모른다면 땀을 흘리지 마십시오.


답변

Redux-Saga 사용

Dan Abramov가 말했듯이, 비동기 코드에 대한 고급 제어를 원한다면 redux-saga를 살펴보십시오 .

이 답변은 간단한 예입니다. redux-saga가 애플리케이션에 유용한 이유에 대한 자세한 설명을 원하면 이 다른 답변을 확인하십시오 .

일반적인 아이디어는 Redux-saga가 ES6 생성기 인터프리터를 제공하여 동기 코드처럼 보이는 비동기 코드를 쉽게 작성할 수 있도록하는 것입니다 (이러한 이유로 Redux-saga에서 무한 while 루프가 자주 발생합니다). 어떻게 든 Redux-saga는 Javascript 내부에서 직접 자체 언어를 구축하고 있습니다. Redux-saga는 처음에는 배우기가 어려울 수 있습니다. 생성기에 대한 기본적인 이해가 필요하지만 Redux-saga에서 제공하는 언어도 이해하기 때문입니다.

여기 redux-saga 위에 구축 한 알림 시스템을 여기에 설명하려고합니다. 이 예제는 현재 프로덕션에서 실행됩니다.

고급 알림 시스템 사양

  • 알림을 표시하도록 요청할 수 있습니다
  • 숨기기 알림을 요청할 수 있습니다
  • 알림은 4 초 이상 표시되지 않아야합니다.
  • 여러 알림을 동시에 표시 할 수 있습니다
  • 동시에 3 개 이하의 알림을 표시 할 수 없습니다
  • 3 개의 표시된 알림이 이미있는 동안 알림이 요청 된 경우 알림을 대기열에 넣거나 연기합니다.

결과

내 프로덕션 앱 Stample.co의 스크린 샷

토스트

암호

여기에 알림 toast이름을 지정했지만 이름을 자세히 지정합니다.

function* toastSaga() {

    // Some config constants
    const MaxToasts = 3;
    const ToastDisplayTime = 4000;


    // Local generator state: you can put this state in Redux store
    // if it's really important to you, in my case it's not really
    let pendingToasts = []; // A queue of toasts waiting to be displayed
    let activeToasts = []; // Toasts currently displayed


    // Trigger the display of a toast for 4 seconds
    function* displayToast(toast) {
        if ( activeToasts.length >= MaxToasts ) {
            throw new Error("can't display more than " + MaxToasts + " at the same time");
        }
        activeToasts = [...activeToasts,toast]; // Add to active toasts
        yield put(events.toastDisplayed(toast)); // Display the toast (put means dispatch)
        yield call(delay,ToastDisplayTime); // Wait 4 seconds
        yield put(events.toastHidden(toast)); // Hide the toast
        activeToasts = _.without(activeToasts,toast); // Remove from active toasts
    }

    // Everytime we receive a toast display request, we put that request in the queue
    function* toastRequestsWatcher() {
        while ( true ) {
            // Take means the saga will block until TOAST_DISPLAY_REQUESTED action is dispatched
            const event = yield take(Names.TOAST_DISPLAY_REQUESTED);
            const newToast = event.data.toastData;
            pendingToasts = [...pendingToasts,newToast];
        }
    }


    // We try to read the queued toasts periodically and display a toast if it's a good time to do so...
    function* toastScheduler() {
        while ( true ) {
            const canDisplayToast = activeToasts.length < MaxToasts && pendingToasts.length > 0;
            if ( canDisplayToast ) {
                // We display the first pending toast of the queue
                const [firstToast,...remainingToasts] = pendingToasts;
                pendingToasts = remainingToasts;
                // Fork means we are creating a subprocess that will handle the display of a single toast
                yield fork(displayToast,firstToast);
                // Add little delay so that 2 concurrent toast requests aren't display at the same time
                yield call(delay,300);
            }
            else {
                yield call(delay,50);
            }
        }
    }

    // This toast saga is a composition of 2 smaller "sub-sagas" (we could also have used fork/spawn effects here, the difference is quite subtile: it depends if you want toastSaga to block)
    yield [
        call(toastRequestsWatcher),
        call(toastScheduler)
    ]
}

그리고 감속기 :

const reducer = (state = [],event) => {
    switch (event.name) {
        case Names.TOAST_DISPLAYED:
            return [...state,event.data.toastData];
        case Names.TOAST_HIDDEN:
            return _.without(state,event.data.toastData);
        default:
            return state;
    }
};

용법

당신은 단순히 파견 할 수 있습니다 TOAST_DISPLAY_REQUESTED 이벤트 . 4 개의 요청을 발송하면 3 개의 알림 만 표시되며 첫 번째 알림이 사라지면 네 번째 알림이 조금 나중에 나타납니다.

TOAST_DISPLAY_REQUESTEDJSX에서 발송하는 것은 특별히 권장하지 않습니다 . 기존 앱 이벤트를 수신하는 다른 사가를 추가 한 다음TOAST_DISPLAY_REQUESTED 하는 알림을 추가 한 다음 알림을 트리거하는 구성 요소를 알림 시스템에 밀접하게 연결하지 않아도됩니다.

결론

내 코드는 완벽하지는 않지만 몇 달 동안 0 개의 버그로 프로덕션 환경에서 실행됩니다. Redux-saga와 제너레이터는 처음에는 약간 어렵지만 일단 이해하면 이러한 종류의 시스템을 쉽게 구축 할 수 있습니다.

다음과 같이보다 복잡한 규칙을 구현하는 것은 매우 쉽습니다.

  • 너무 많은 알림이 “대기 중”인 경우 각 알림에 대해 표시 시간을 줄이면 대기열 크기가 더 빨리 줄어들 수 있습니다.
  • 창 크기 변경을 감지하고 이에 따라 표시되는 최대 알림 수를 변경합니다 (예 : 데스크톱 = 3, 전화 세로 = 2, 전화 가로 = 1).

솔직히 이런 종류의 것들을 썽크와 올바르게 구현하는 것이 행운입니다.

redux-saga와 매우 유사한 redux-observable 을 사용 하여 정확히 같은 종류의 작업을 수행 할 수 있습니다 . 거의 동일하며 발전기와 RxJS 사이의 맛의 문제입니다.


답변

샘플 프로젝트가있는 저장소

현재 네 가지 샘플 프로젝트가 있습니다.

  1. 비동기 코드 인라인 작성
  2. 비동기 액션 생성기 추출
  3. Redux Thunk 사용
  4. Redux Saga 사용

허용되는 답변은 훌륭합니다.

그러나 빠진 것이 있습니다.

  1. 실행할 수있는 샘플 프로젝트가없고 일부 코드 스 니펫 만 있습니다.
  2. 다음과 같은 다른 대안에 대한 샘플 코드는 없습니다.
    1. 레독 사가

그래서 나는 누락 된 것들을 추가하기 위해 Hello Async 저장소를 만들었습니다 .

  1. 실행 가능한 프로젝트. 수정없이 다운로드하여 실행할 수 있습니다.
  2. 더 많은 대안을위한 샘플 코드를 제공하십시오.

레독 사가

허용 된 답변은 이미 비동기 코드 인라인, 비동기 동작 생성기 및 Redux 썽크에 대한 샘플 코드 스 니펫을 제공합니다. 완벽을 기하기 위해 Redux Saga에 대한 코드 스 니펫을 제공합니다.

// actions.js

export const showNotification = (id, text) => {
  return { type: 'SHOW_NOTIFICATION', id, text }
}

export const hideNotification = (id) => {
  return { type: 'HIDE_NOTIFICATION', id }
}

export const showNotificationWithTimeout = (text) => {
  return { type: 'SHOW_NOTIFICATION_WITH_TIMEOUT', text }
}

행동은 간단하고 순수합니다.

// component.js

import { connect } from 'react-redux'

// ...

this.props.showNotificationWithTimeout('You just logged in.')

// ...

export default connect(
  mapStateToProps,
  { showNotificationWithTimeout }
)(MyComponent)

컴포넌트에는 특별한 것이 없습니다.

// sagas.js

import { takeEvery, delay } from 'redux-saga'
import { put } from 'redux-saga/effects'
import { showNotification, hideNotification } from './actions'

// Worker saga
let nextNotificationId = 0
function* showNotificationWithTimeout (action) {
  const id = nextNotificationId++
  yield put(showNotification(id, action.text))
  yield delay(5000)
  yield put(hideNotification(id))
}

// Watcher saga, will invoke worker saga above upon action 'SHOW_NOTIFICATION_WITH_TIMEOUT'
function* notificationSaga () {
  yield takeEvery('SHOW_NOTIFICATION_WITH_TIMEOUT', showNotificationWithTimeout)
}

export default notificationSaga

Sagas는 ES6 발전기를 기반으로합니다

// index.js

import createSagaMiddleware from 'redux-saga'
import saga from './sagas'

const sagaMiddleware = createSagaMiddleware()

const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

sagaMiddleware.run(saga)

Redux Thunk와 비교

찬성

  • 콜백 지옥으로 끝나지 않습니다.
  • 비동기식 흐름을 쉽게 테스트 할 수 있습니다.
  • 당신의 행동은 순수합니다.

단점

  • 비교적 새로운 ES6 Generator에 의존합니다.

위의 코드 스 니펫이 모든 질문에 대답하지 않으면 실행 가능한 프로젝트를 참조하십시오 .


답변

redux-thunk 로이 작업을 수행 할 수 있습니다 . redux 문서 에는 setTimeout과 같은 비동기 작업에 대한 가이드 가 있습니다 .


답변

SAM 패턴도 살펴 보는 것이 좋습니다 .

SAM 패턴은 모델이 업데이트되면 “5 초 후에 알림이 자동으로 사라짐”과 같은 (자동) 작업이 트리거되는 “다음 작업 조건 자”를 포함하도록 권장합니다 (SAM 모델 ~ 감속기 상태 + 저장).

이 패턴은 모델의 “제어 상태”가 다음 조치 술어에 의해 조치가 사용 가능하고 /하거나 자동으로 실행되는 것을 “제어”하므로 시퀀싱 조치 및 모델 돌연변이를 한 번에 하나씩 옹호합니다. 조치를 처리하기 전에 시스템 상태를 예측할 수 없으므로 일반적으로 다음 예상 조치가 허용 / 가능한지 여부를 예측할 수 없습니다.

예를 들어 코드는

export function showNotificationWithTimeout(dispatch, text) {
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

hideNotification 작업을 전달할 수 있다는 사실은 “showNotication : true”값을 성공적으로 수락하는 모델에 따라 다르므로 SAM에서는 허용되지 않습니다. 모델의 다른 부분이 모델을 수락하지 못하게 할 수 있으므로 hideNotification 조치를 트리거 할 이유가 없습니다.

상점 업데이트 후 모델의 새로운 제어 상태를 알 수있는 경우 적절한 다음 조치 술어를 구현하는 것이 좋습니다. 그것은 당신이 찾고있는 행동을 구현하는 가장 안전한 방법입니다.

원하는 경우 Gitter에서 우리와 함께 할 수 있습니다. 여기에 SAM 시작 안내서도 있습니다 .


답변

다양한 인기있는 접근 방식 (액션 제작자, 썽크, 사가, 에픽, 효과, 사용자 정의 미들웨어)을 시도한 후에도 여전히 개선의 여지가 있다고 생각 하여이 블로그 기사에서 여행을 문서화 했습니다. React / Redux 애플리케이션?

여기서 논의한 것처럼, 나는 다양한 접근법을 대조하고 비교하려고 노력했다. 결국 새 도서관을 소개하게되었습니다 epics, sagas, custom 미들웨어로부터 영감을 redux-logic .

비동기 IO를 수행하는 방법을 제공 할뿐만 아니라 유효성 검증, 검증, 권한 부여를위한 조치를 인터셉트 할 수 있습니다.

일부 공통 기능은 단순히 수신 거부, 제한, 취소 및 최신 요청 (takeLatest)의 응답 만 사용하여 선언 할 수 있습니다. redux-logic은 코드를 감싸서이 기능을 제공합니다.

따라서 핵심 비즈니스 로직을 원하는대로 구현할 수 있습니다. 원치 않는 한 관측 가능 장치 나 생성기를 사용할 필요가 없습니다. 함수 및 콜백, 약속, 비동기 함수 (비동기 / 대기) 등을 사용하십시오.

간단한 5s 알림을 수행하는 코드는 다음과 같습니다.

const notificationHide = createLogic({
  // the action type that will trigger this logic
  type: 'NOTIFICATION_DISPLAY',

  // your business logic can be applied in several
  // execution hooks: validate, transform, process
  // We are defining our code in the process hook below
  // so it runs after the action hit reducers, hide 5s later
  process({ getState, action }, dispatch) {
    setTimeout(() => {
      dispatch({ type: 'NOTIFICATION_CLEAR' });
    }, 5000);
  }
});
    

내 저장소에 고급 알림 예제가 있는데, Sebastian Lorber가 N 항목으로 디스플레이를 제한하고 대기중인 항목을 통해 회전 할 수있는 위치에 대해 설명 한 것과 비슷합니다. redux-logic 알림 예제

다양한 redux-logic jsfiddle 라이브 예제와 전체 예제가 있습니다. 있습니다. 나는 문서와 예제를 계속 연구하고 있습니다.

귀하의 의견을 듣고 싶습니다.


답변

나는이 질문이 조금 오래되었다는 것을 이해하지만 redux-observable 일명을 사용하는 다른 솔루션을 소개 할 것 입니다. 서사시.

공식 문서 인용 :

redux-observable은 무엇입니까?

Redux 용 RxJS 5 기반 미들웨어. 비동기 작업을 작성 및 취소하여 부작용 등을 만듭니다.

에픽은 redux-observable의 핵심 프리미티브입니다.

액션 스트림을 취해 액션 스트림을 돌려주는 함수입니다. 행동, 행동.

간단히 말해서 Stream을 통해 작업을 수신 한 다음 새 작업 스트림을 반환하는 함수를 만들 수 있습니다 (시간 초과, 지연, 간격 및 요청과 같은 일반적인 부작용 사용).

코드를 게시 한 다음 조금 더 설명하겠습니다

store.js

import {createStore, applyMiddleware} from 'redux'
import {createEpicMiddleware} from 'redux-observable'
import {Observable} from 'rxjs'
const NEW_NOTIFICATION = 'NEW_NOTIFICATION'
const QUIT_NOTIFICATION = 'QUIT_NOTIFICATION'
const NOTIFICATION_TIMEOUT = 2000

const initialState = ''
const rootReducer = (state = initialState, action) => {
  const {type, message} = action
  console.log(type)
  switch(type) {
    case NEW_NOTIFICATION:
      return message
    break
    case QUIT_NOTIFICATION:
      return initialState
    break
  }

  return state
}

const rootEpic = (action$) => {
  const incoming = action$.ofType(NEW_NOTIFICATION)
  const outgoing = incoming.switchMap((action) => {
    return Observable.of(quitNotification())
      .delay(NOTIFICATION_TIMEOUT)
      //.takeUntil(action$.ofType(NEW_NOTIFICATION))
  });

  return outgoing;
}

export function newNotification(message) {
  return ({type: NEW_NOTIFICATION, message})
}
export function quitNotification(message) {
  return ({type: QUIT_NOTIFICATION, message});
}

export const configureStore = () => createStore(
  rootReducer,
  applyMiddleware(createEpicMiddleware(rootEpic))
)

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {configureStore} from './store.js'
import {Provider} from 'react-redux'

const store = configureStore()

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

App.js

import React, { Component } from 'react';
import {connect} from 'react-redux'
import {newNotification} from './store.js'

class App extends Component {

  render() {
    return (
      <div className="App">
        {this.props.notificationExistance ? (<p>{this.props.notificationMessage}</p>) : ''}
        <button onClick={this.props.onNotificationRequest}>Click!</button>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    notificationExistance : state.length > 0,
    notificationMessage : state
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onNotificationRequest: () => dispatch(newNotification(new Date().toDateString()))
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

이 문제를 해결하기위한 핵심 코드는 파이처럼 쉽게 알 수 있습니다. 다른 답변과 다르게 나타나는 유일한 기능은 rootEpic 함수입니다.

포인트 1. sagas와 마찬가지로 액션 스트림을 수신하고 액션 스트림을 반환하는 최상위 기능을 얻기 위해 epics를 결합해야 미들웨어 팩토리 createEpicMiddleware 와 함께 사용할 수 있습니다 . 우리의 경우 rootEpic 만 필요하므로 아무것도 결합하지 않아도 사실을 아는 것이 좋습니다.

포인트 2. 우리의 rootEpic 부작용 논리를 처리하는 은 약 5 줄의 코드 만 사용합니다. 거의 선언적이라는 사실을 포함!

포인트 3. 라인 별 루트

const rootEpic = (action$) => {
  // sets the incoming constant as a stream 
  // of actions with  type NEW_NOTIFICATION
  const incoming = action$.ofType(NEW_NOTIFICATION)
  // Merges the "incoming" stream with the stream resulting for each call
  // This functionality is similar to flatMap (or Promise.all in some way)
  // It creates a new stream with the values of incoming and 
  // the resulting values of the stream generated by the function passed
  // but it stops the merge when incoming gets a new value SO!,
  // in result: no quitNotification action is set in the resulting stream
  // in case there is a new alert
  const outgoing = incoming.switchMap((action) => {
    // creates of observable with the value passed 
    // (a stream with only one node)
    return Observable.of(quitNotification())
      // it waits before sending the nodes 
      // from the Observable.of(...) statement
      .delay(NOTIFICATION_TIMEOUT)
  });
  // we return the resulting stream
  return outgoing;
}

도움이 되길 바랍니다!