Как отправить действие Redux с таймаутом?

У меня есть действие, которое обновляет состояние уведомления из моего приложения. Обычно это уведомление будет ошибкой или какой-то информацией. Затем мне нужно отправить другое действие через 5 секунд, которое вернет состояние уведомления в начальное, поэтому никакого уведомления. Основная причина этого-обеспечить функциональность, при которой уведомления автоматически исчезают через 5 секунд.

мне не повезло с помощью setTimeout и возвращать другое действие и не могу найти как это делается онлайн. Поэтому любые советы приветствуются.

13 ответов


на ловушка мышления библиотека должна предписывать, как все делать. Если вы хотите сделать что-то с таймаутом в JavaScript, вам нужно использовать setTimeout. Нет причин, по которым действия Redux должны отличаться.

Redux тут предлагают некоторые альтернативные способы борьбы с асинхронными вещами, но вы должны использовать их только тогда, когда понимаете, что повторяете слишком много кода. Если у вас нет этой проблемы, используйте то, что язык предлагает и идет на простейшее решение.

Запись Асинхронного Кода Inline

это самый простой способ. И здесь нет ничего специфического для 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() или конкретные создатели действий, введенные в качестве реквизита. Однако это не делает любая разница для нас.

Если вам не нравится делать опечатки при отправке одних и тех же действий из разных компонентов, вы можете извлечь создателей действий вместо отправки объектов действий inline:

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

до сих пор мы не использовали промежуточное программное обеспечение или другую передовую концепцию.

Извлечение Асинхронного Действия Creator

выше подход работает штраф в простых случаях, но вы можете обнаружить, что у него есть несколько проблем:

  • это заставляет вас дублировать эту логику в любом месте вы хотите, чтобы показать уведомление.
  • уведомления не имеют идентификаторов, поэтому у вас будет состояние гонки, если вы покажете два уведомления достаточно быстро. Когда закончится первый тайм-аут, он отправит 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 interval ID and call
  // clearInterval(), 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.')    

это выглядит проще, но менее!--87-->мы не рекомендуем такой подход. Главная причина, по которой мы не любим это потому, что это заставляет магазин быть одноэлементным. Это делает его очень трудно реализовать сервер рендеринга. На сервер, вы хотите, чтобы каждый запрос имел свое собственное хранилище, чтобы разные пользователи получали разные предварительно загруженные данные.

одноэлементный магазин также затрудняет тестирование. Вы больше не можете издеваться над магазином при тестировании создателей действий, поскольку они ссылаются на конкретный реальный магазин, экспортированный из определенного модуля. Вы даже не можете сбросить его состояние извне.

таким образом, хотя вы технически можете экспортировать одноэлементный магазин из модуля, мы не рекомендуем его. Не делайте этого, если вы не уверены что ваше приложение никогда не добавит рендеринг сервера.

возвращаясь к предыдущей версии:

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

это решает проблемы с дублированием логики и спасает нас от условий гонки.

Thunk Middleware

для простых приложений, подход должен быть достаточно. Не беспокойтесь о middleware, если вы довольны этим.

в больших приложениях, однако, вы можете найти определенные неудобства вокруг него.

например, как жаль, что мы должны пройти dispatch вокруг. Это делает его сложнее отдельный контейнер и презентационные компоненты потому что любой компонент, который отправляет действия Redux асинхронно указанным выше способом, должен принять dispatch в качестве опоры, чтобы он мог пройти его дальше. Вы не можете просто связать создателей действий с connect() больше, потому что showNotificationWithTimeout() на самом деле не является создателем действий. Он не возвращает действие Redux.

кроме того, это может быть неудобно чтобы помнить, какие функции являются синхронными создателями действий, как showNotification() и не совпадает помощники как showNotificationWithTimeout(). Вы должны использовать их по-разному и быть осторожны, чтобы не перепутать их друг с другом.

это была мотивация для найти способ "узаконить" этот шаблон предоставления dispatch вспомогательной функции и помочь Redux "видеть" таких асинхронных создателей действий как особый случай обычных создателей действий а не совсем другой функции.

если вы все еще с нами и вы узнаете как проблема в вашем приложении, вы можете использовать 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 middleware даст это 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.'))

наконец, отправка асинхронного действия (действительно, серии действий) выглядит не иначе, чем отправка одного действия синхронно деталь. Это хорошо, потому что компонентам все равно, происходит ли что-то синхронно или асинхронно. Мы просто абстрагировались от этого.

обратите внимание, что, поскольку мы" научили "Redux распознавать таких" специальных " создателей действий (мы называем их подумать создатели действий), теперь мы можем использовать их в любом месте, где мы будем использовать обычных создателей действий. Например, мы можем использовать их с 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)

состояние чтения в Thunks

обычно редукторы содержат бизнес-логику для определения следующего состояния. Однако редукторы срабатывают только после отправки действий. Что делать, если у вас есть побочный эффект (например, вызов API) в создателе действия thunk, и вы хотите предотвратить его при некоторых условиях?

без использования промежуточного программного обеспечения thunk вы просто сделаете эту проверку внутри компонента:

// component.js
if (this.props.areNotificationsEnabled) {
  showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}
точка извлечение создатель акции централизовать эту повторяющуюся логику по многим компонентам. К счастью, Redux Thunk предлагает вам способ читать текущее состояние магазина Redux. В дополнение к dispatch, он также передает getState в качестве второго аргумента функции, которую вы возвращаете от создателя действия thunk. Это позволяет thunk читать текущее состояние магазина.
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() только для условной отправки различных действий рассмотрите возможность ввода бизнес-логики в редукторы.

Следующие Шаги

теперь, когда у вас есть основная интуиция о том, как работает thunks, проверьте Redux асинхронный пример, который использует их.

вы можете найти много примеров, в которых thunks возвращают обещания. Это не обязательно, но может быть очень удобно. Redux не заботится о том, что вы возвращаете из thunk, но он дает вам его возвращаемое значение от dispatch(). Вот почему вы можете вернуть обещание из thunk и дождаться его завершения, позвонив dispatch(someThunkReturningPromise()).then(...).

вы также можете разделить сложные создатели действий thunk на несколько меньших создателей действий thunk. The dispatch метод, предоставляемый thunks, может принимать сами thunks, поэтому вы можете применить шаблон рекурсивно. Опять же, это лучше всего работает с обещаниями, потому что вы можете реализовать асинхронный поток управления сверху.

для некоторых приложений вы можете оказаться в ситуации, когда ваши требования к асинхронному потоку управления слишком сложны, чтобы быть выраженными с помощью thunks. Например, повторная попытка неудачных запросов, поток повторной аутентификации с помощью токенов или пошаговое внедрение могут быть слишком подробными и подверженными ошибкам при написании таким образом. В этом случае может потребоваться более продвинутые решения асинхронного потока управления, такие как Redux Saga или Цикл Redux. Оцените их, сравните примеры, соответствующие вашим потребностям, и выберите тот, который вам нравится больше всего.

наконец, не используйте ничего (включая thunks), если у вас нет подлинной потребности в них. Помните, что в зависимости от требований, ваше решение может выглядеть так просто, как

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

не потейте, если вы не знаете, почему вы это делаете.


использование Redux-saga

как сказал Дэн Абрамов, если вы хотите более продвинутый контроль над асинхронным кодом, вы можете взглянуть на redux-saga.

этот ответ-простой пример, если вы хотите получить лучшие объяснения того, почему redux-saga может быть полезен для вашего приложения, проверьте этот и другие ответы.

общая идея заключается в том, что Redux-saga предлагает интерпретатор генераторов ES6, который позволяет вам легко писать асинхронный код, который выглядит как синхронный код (вот почему вы часто найдете бесконечные циклы while в Redux-saga). Каким-то образом Redux-saga строит свой собственный язык непосредственно внутри Javascript. Redux-saga может показаться немного трудным для изучения сначала, потому что вам нужно базовое понимание генераторов, но также понять язык, предлагаемый Redux-saga.

я попытаюсь здесь описать здесь систему уведомлений, которую я построил поверх redux-saga. Этот пример работает только в производство.

Расширенная спецификация системы уведомлений

  • вы можете запросить уведомление будет отображаться
  • вы можете запросить уведомление, чтобы скрыть
  • уведомления не должно отображаться более 4 секунд
  • несколько уведомлений могут отображаться одновременно
  • одновременно может отображаться не более 3 уведомлений
  • если запрошено уведомление в то время как там уже 3 отображаются уведомления, затем очередь / отложить его.

результат

скриншот моего производственного приложения Стэмпл.co

toasts

код

здесь я назвал уведомление a 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 уведомления, а 4-й появится немного позже, как только 1-е уведомление исчезнет.

обратите внимание, что я специально не рекомендую посылать TOAST_DISPLAY_REQUESTED из JSX. Вы бы предпочли добавить еще одну сагу, которая слушает ваши уже существующие события приложения, а затем отправить TOAST_DISPLAY_REQUESTED: компонент, который запускает уведомление, не должен быть тесно связан с системой уведомлений.

вывод

мой код не идеально, но работает в производстве с 0 ошибками в течение нескольких месяцев. Redux-saga и генераторы немного сложны изначально, но как только вы их поймете, такую систему довольно легко построить.

это даже довольно легко реализовать более сложные правила, такие как:

  • когда слишком много уведомлений "поставлены в очередь", дайте меньше времени отображения для каждого уведомления, чтобы размер очереди мог уменьшаться быстрее.
  • обнаружение изменений размера окна и изменение максимального количества соответственно отображаются уведомления (например, desktop=3, Phone portrait = 2, phone landscape = 1)

Honnestly, удачи в реализации такого рода вещей должным образом с thunks.

Примечание Вы можете сделать то же самое с redux-observable который очень похож на redux-saga. Это почти то же самое и является вопросом вкуса между генераторами и RxJS.


Я бы рекомендовал также взглянуть на Сэм pattern.

шаблон SAM выступает за включение "следующего действия-предиката", где (автоматические) действия, такие как" уведомления исчезают автоматически через 5 секунд", запускаются после обновления модели (Sam model ~ reducer state + store).

шаблон выступает за секвенирование действий и мутаций модели по одному, потому что "состояние управления" модели "управляет", которое действия включаются и / или автоматически выполняются предикатом next-action. Вы просто не можете предсказать (в общем), в каком состоянии будет система ДО обработки действия и, следовательно, будет ли ваше следующее ожидаемое действие разрешено/возможно.

Так, например, код,

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

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

не будет разрешено с SAM, потому что тот факт, что действие hideNotification может быть отправлено, зависит от модели, успешно принимающей значение " showNotication: истинный." Могут быть другие части модели, которые мешают ей принять его, и поэтому не было бы причин запускать действие hideNotification.

Я настоятельно рекомендую реализовать правильный предикат следующего действия после обновления хранилища и нового состояния элемента управления модели. Это самый безопасный способ реализовать поведение, которое вы ищете.

вы можете присоединиться к нам на Gitter, если хочешь. Существует также Сэм становится руководство доступно здесь.


вы можете сделать это с помощью redux-thunk. Есть руководство в документе redux для асинхронных действий, таких как setTimeout.


репозиторий с образцами проектов

Current есть четыре образца проектов:

  1. Запись Асинхронного Кода Inline
  2. Извлечение Асинхронного Действия Creator
  3. Используйте Redux Thunk
  4. Используйте Redux Saga

принятый ответ является удивительным.

но чего-то не хватает:

  1. нет запускаемые примеры проектов, только некоторые фрагменты кода.
  2. нет примера кода для других альтернатив, таких как:
    1. Redux Saga

поэтому я создал Привет Асинхронного репозиторий для добавления недостающих вещей:

  1. выполнимые проекты. Вы можете скачать и запустить их без изменения.
  2. предоставить пример кода для нескольких вариантов:

Redux Saga

принятый ответ уже предоставляет примеры фрагментов кода для встроенного асинхронного кода, генератора асинхронных действий и Redux Thunk. Для полноты я предоставляю фрагменты кода для 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

саги основаны на генераторы 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, которые являются относительно новыми.

пожалуйста, обратитесь к запускаемого проекта если фрагменты кода выше не отвечайте на все ваши вопросы.


попробовав различные популярные подходы (создатели действий, thunks, sagas, epics, effects, custom middleware), я все еще чувствовал, что, возможно, есть место для улучшения, поэтому я задокументировал свое путешествие в этой статье блога,где я могу поместить свою бизнес-логику в приложение React/Redux?

подобно обсуждениям здесь, я попытался сравнить и сравнить различные подходы. В конце концов, это привело меня к введению новой библиотеки redux-логика что черпает вдохновение из эпосов, саг, пользовательских middleware.

Он позволяет перехватывать действия для проверки, проверки, авторизации, а также предоставляет способ выполнения асинхронного ввода-вывода.

некоторые общие функции могут быть просто объявлены как debouncing, дросселирование, отмена и только с помощью ответа от последнего запроса (takeLatest). redux-logic обертывает ваш код, предоставляя вам эту функциональность.

это освобождает вас от реализации вашего основного бизнеса логика как угодно. Вам не нужно использовать наблюдаемые или генераторы, если вы этого не хотите. Используйте функции и обратные вызовы, обещания, асинхронные функции (async / await)и т. д.

код для выполнения простого уведомления 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);
  }
});
    

У меня есть более продвинутый пример уведомления в моем РЕПО, который работает аналогично тому, что описал Себастьян Лорбер, где вы можете ограничить отображение до N элементов и повернуть через любой, который встал в очередь вверх. redux-пример логического уведомления

У меня есть выбор redux-logic jsfiddle живые примеры, а также полные примеры. Я продолжаю работать над документами и примерами.

Я хотел бы услышать ваши отзывы.


Я понимаю, что этот вопрос немного старый, но я собираюсь ввести другое решение, используя redux-observable ака. Эпический.

цитирование официальной документации:

что redux-observable?

промежуточное ПО на основе RxJS 5 для Redux. Создание и отмена асинхронных действий для создать побочные эффекты и многое другое.

эпос является основным примитивом redux-observable.

это функция, которая принимает поток действий и возвращает поток действий. Действия, действия.

в более или менее словах вы можете создать функцию, которая получает действия через поток, а затем возвращает новый поток действий (используя общие побочные эффекты, такие как таймауты, задержки, интервалы и запросы).

позвольте мне опубликовать код, а затем объяснить немного больше о это

магазине.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))
)
.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. Как и в сагах, вы должны объединить эпики, чтобы получить функцию верхнего уровня, которая получает поток действий и возвращает поток действий, поэтому вы можете использовать ее с промежуточным программным обеспечением фабрика createEpicMiddleware. В нашем случае нам нужен только один, поэтому у нас есть только наш rootEpic поэтому нам не нужно ничего комбинировать, но это хороший факт.

пункт 2. Наши!--35-->rootEpic который заботится о логике побочных эффектов занимает всего около 5 строк кода, что является удивительным! Включая тот факт, что это довольно декларативно!

пункт 3. Построчно rootEpic объяснение (в комментарии)

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;
}

надеюсь, это поможет!


Если вы хотите обработку тайм-аута для выборочных действий, вы можете попробовать промежуточное подход. Я столкнулся с аналогичной проблемой для выборочной обработки действий на основе обещаний, и это решение было более гибким.

давайте скажем, что ваш создатель действия выглядит так:

//action creator
buildAction = (actionData) => ({
    ...actionData,
    timeout: 500
})

timeout может содержать несколько значений в приведенном выше действии

  • номер в ms - для определенной продолжительности тайм-аута
  • true-для постоянного таймаута продолжительность. (обрабатывается в промежуточном по)
  • undefined-для немедленной отправки

ваша реализация промежуточного ПО будет выглядеть следующим образом:

//timeoutMiddleware.js
const timeoutMiddleware = store => next => action => {

  //If your action doesn't have any timeout attribute, fallback to the default handler
  if(!action.timeout) {
    return next (action)
  }

  const defaultTimeoutDuration = 1000;
  const timeoutDuration = Number.isInteger(action.timeout) ? action.timeout || defaultTimeoutDuration;

//timeout here is called based on the duration defined in the action.
  setTimeout(() => {
    next (action)
  }, timeoutDuration)
}

Теперь вы можете направлять все свои действия через этот промежуточный слой с помощью redux.

createStore(reducer, applyMiddleware(timeoutMiddleware))

вы можете найти некоторые подобные примеры здесь


Почему это должно быть так сложно? Это просто логика пользовательского интерфейса. Используйте специальное действие для установки данных уведомлений:

dispatch({ notificationData: { message: 'message', expire: +new Date() + 5*1000 } })

и выделенный компонент для его отображения:

const Notifications = ({ notificationData }) => {
    if(notificationData.expire > this.state.currentTime) {
      return <div>{notificationData.message}</div>
    } else return null;
}

в этом случае вопрос "как вы убирать старое?", "как уведомить компонент о том, что время изменилось"

вы можете реализовать некоторое действие таймаута, которое отправляется на setTimeout из компонента.

может быть, это просто хорошо, чтобы очистить его всякий раз, когда новый отображается уведомление.

в любом случае, должны быть некоторые setTimeout где-то, верно? Почему бы не сделать это в компоненте

setTimeout(() => this.setState({ currentTime: +new Date()}), 
           this.props.notificationData.expire-(+new Date()) )

мотивация заключается в том, что функциональность" уведомление исчезает " действительно является проблемой пользовательского интерфейса. Это упрощает тестирование бизнес-логики.

кажется, нет смысла проверять, как он реализован. Имеет смысл только проверить, когда уведомление должно тайм-аут. Таким образом, меньше кода заглушить, быстрее тесты, чище код.


соответствующий способ сделать это с помощью Redux Thunk что является популярное промежуточное ПО для Redux, согласно документации Redux Thunk:

" Redux Thunk middleware позволяет писать создателей действий, которые возвращает функцию вместо действия. Преобразователь может быть использован для задержки отправка действия или отправка только при определенном условии встречено. Внутренняя функция получает методы хранения dispatch и getState как параметры."

таким образом, в основном он возвращает функцию, и вы можете отложить отправку или поместить ее в состояние состояния.

Итак, что-то вроде этого сделает работу за вас:

import ReduxThunk from 'redux-thunk';

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

function increment() {
  return {
    type: INCREMENT_COUNTER
  };
}

function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment());
    }, 5000);
  };
}

Redux сама по себе довольно многословная библиотека, и для таких вещей вам придется использовать что-то вроде Redux-thunk, а dispatch функция, поэтому вы сможете отправить закрытие уведомления через несколько секунд.

Я создал библиотеку для решения проблемы, как многословие и композиционность, и ваш пример будет выглядеть следующим образом:

import { createTile, createSyncTile } from 'redux-tiles';
import { sleep } from 'delounce';

const notifications = createSyncTile({
  type: ['ui', 'notifications'],
  fn: ({ params }) => params.data,
  // to have only one tile for all notifications
  nesting: ({ type }) => [type],
});

const notificationsManager = createTile({
  type: ['ui', 'notificationManager'],
  fn: ({ params, dispatch, actions }) => {
    dispatch(actions.ui.notifications({ type: params.type, data: params.data }));
    await sleep(params.timeout || 5000);
    dispatch(actions.ui.notifications({ type: params.type, data: null }));
    return { closed: true };
  },
  nesting: ({ type }) => [type],
});

поэтому мы составляем действия синхронизации для отображения уведомлений внутри асинхронного действия, которое может запросить некоторую информацию о фоне или проверить позже, было ли уведомление закрыто вручную.


его простой. использовать trim-redux пакет и напишите так в componentDidMout или другом месте и убейте его в componentWillUnmount.

componentDidMount(){
   this.tm =  setTimeout(function(){ 
                      setStore({ age: 20 });
              }, 3000);
}

componentWillUnmount(){
   clearTimeout(this.tm);
}

всякий раз, когда вы делаете setTimeout пожалуйста, убедитесь, что вы также очистить тайм-аут с помощью clearTimeout, когда ваш компонент un монтируется в componentWillUnMount метод жизненного цикла

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

componentWillUnMount(){
   clearTimeout(this.timeout);
}