Не попадайтесь в ловушку

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

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

Извлечение Async Action Creator

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

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

У уведомлений нет идентификаторов, поэтому у вас будет условие гонки, если вы покажете два уведомления достаточно быстро. Когда первый тайм-аут заканчивается, он отправляет

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

  • Чтобы решить эти проблемы, вам нужно извлечь функцию, которая централизует логику тайм-аута и отправляет эти два действия. Это может выглядеть так:
  • без дублирования этой логики или наличия условий гонки с другими уведомлениями: 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.')    

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

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

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

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

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

(Кстати, я новичок в stackoverflow, дайте мне руководство, пожалуйста.)

Thunk Middleware

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

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

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

10 мая ’16 в 19:43 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 выдаст ее 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 распознавать таких «специальных» создателей действий (мы называем их thunk создатели действий), мы теперь можем использовать их в любом месте, где мы будем использовать обычные создатели действий. Например, мы можем использовать их с 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 action и вы хотите предотвратить его при определенных условиях?

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

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

Однако целью извлечения создателя действия было централизовать эту повторяющуюся логику во многих компонентах. К счастью, Redux Thunk предлагает вам способ прочитать текущее состояние магазина Redux. В дополнение кdispatch, Это также передает getState в качестве второго аргумента функции, которую вы возвращаете создателю thunk action. Это позволяет 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() только для условной диспетчеризации различных действий, вместо этого рассмотрите возможность помещения бизнес-логики в редукторы.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Вы можете запросить отображение уведомления
  • Вы можете запросить уведомление о скрытии
  • Уведомление не должно отображаться более 4 секунд
  • . {{}} Чтобы получить элемент, вызовите эту функцию следующим образом:
  • Может быть не более 3 уведомлений отображаться одновременно
  • Если уведомление запрашивается, когда уже есть 3 отображаемых уведомления, поставьте его в очередь / отложите.

Результат

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

toasts

Code

Здесь я назвал уведомление 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 и генераторы изначально немного сложны, но как только вы их поймете, такую ​​систему довольно просто построить.

Одновременно могут отображаться несколько уведомлений

  • когда в очередь помещается слишком много уведомлений, выделяйте меньше времени отображения для каждого уведомления, чтобы размер очереди мог уменьшаться быстрее.
  • обнаруживать изменения размера окна и соответственно изменять максимальное количество отображаемых уведомлений (например, рабочий стол = 3, портрет телефона = 2, пейзаж телефона = 1)

Честно, удачи в правильной реализации такого рода вещей с помощью thunks ,

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

Я бы порекомендовал также взглянуть на {SAM} шаблон SAM . Шаблон SAM рекомендует включать «предикат следующего действия», где (автоматически) такие действия, как «уведомления исчезают автоматически через 5 секунд» срабатывает после обновления модели (SAM-модель ~ хранилище состояний редуктора). Глядя на вывод

Шаблон поддерживает последовательное выполнение действий и мутаций модели по одному, поскольку «состояние управления» модели «контролирует», какие действия включены и / или автоматически выполняются предикатом следующего действия. Вы просто не можете предсказать (в общем), в каком состоянии будет система до обработки действия и, следовательно, будет ли разрешено / возможно ваше следующее ожидаемое действие.

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

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

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

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

Руководство по началу работы с SAM доступно здесь

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

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

redux-thunk . В редуксном документе есть руководство по асинхронным действиям, таким как setTimeout. На данный момент существует четыре примера проектов: Использовать Redux Thunk

Использовать Redux Saga

  1. Единственное отличие состоит в том, что в подключенном компоненте у вас обычно нет доступа к самому хранилищу, но вы получаете либо
  2. У уведомлений нет идентификаторов, поэтому у вас будет условие гонки, если вы покажете два уведомления достаточно быстро. Когда первый тайм-аут заканчивается, он отправляет
  3. Принято, ответ потрясающий.
  4. Но чего-то не хватает:

Нет примеров запускаемых проектов, только некоторые фрагменты кода.

Пример кода для других альтернатив, таких как:

  1. , поэтому я создал
  2. репозиторий Hello Async
    1. Redux Saga

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

  1. В принятом ответе уже представлены примеры фрагментов кода для Async Code Inline, Async Action Generator и Redux Thunk. Для полноты картины я предоставляю фрагменты кода для Redux Saga:
  2. Действия просты и чисты.

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

Генераторы ES6

// component.js

import { connect } from 'react-redux'

// ...

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

// ...

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

По сравнению с Redux Thunk

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

Саги основаны на Плюсы

// index.js

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

const sagaMiddleware = createSagaMiddleware()

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

sagaMiddleware.run(saga)

Вы не попадете в ад обратного вызова.

Вы можете легко проверить свои асинхронные потоки.

  • Минусы
  • Это зависит от генераторов ES6, который является относительно новым.
  • Ваши действия остаются чистыми.

запускаемый проект

  • , если приведенные выше фрагменты кода не отвечают на все ваши вопросы.

Как правильно клонировать объект JavaScript? Попробовав различные популярные подходы (создатели действий, сценарии, саги, эпики, эффекты, пользовательское промежуточное ПО), я все еще чувствовал, что, возможно, есть место для улучшений, поэтому я задокументировал свое путешествие в этой статье блога, Где Я положил свою бизнес-логику в приложение React / Redux?

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

, которая позволяет вам перехватывать действия для проверки, проверки, авторизации, а также предоставления способа выполнения асинхронного ввода-вывода. Код для простого уведомления 5s будет выглядеть примерно так: (обратите внимание на комментарий, все, что я сделал, я привел

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

Некоторые обычные функции могут быть просто объявлены как отмена, регулирование, отмена и только с использованием ответа от последнего запроса (takeLatest). Избыточная логика оборачивает ваш код, предоставляя вам эту функциональность.

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

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

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

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

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

Что такое наблюдаемый редукс?

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

Эпос является основным примитивом наблюдаемого редукса.

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

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

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

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

Пункт 2. Наш 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;
}

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

Если вам нужна обработка тайм-аута для отдельных действий, вы можете попробовать промежуточное ПО промежуточного ПО { *} «Промежуточное ПО Redux Thunk позволяет вам создавать создателей действий, которые возвращают функцию вместо действия. Thunk можно использовать для отсрочки отправки действия или для отправки, только если выполняется определенное условие. Внутренняя функция получает хранить методы отправки и getState в качестве параметров «. ревизии jQuery 3.0

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

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

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

  • в мс — для определенной продолжительности тайм-аута
  • 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)
}

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

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

В этом случае должны быть вопросы: «Как очистить старое состояние?», «Как уведомить компонент, который имеет время Изменено «

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

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

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

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

Мотивация заключается в том, что функциональность «исчезновения уведомлений» действительно является проблемой пользовательского интерфейса. Так что это упрощает тестирование вашей бизнес-логики.

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

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

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

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

Это просто. Используйте пакет

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

trim-redux и напишите так в или другом месте и уничтожьте его в componentDidMount, что даст функцию componentWillUnmount Глядя на вывод

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

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

Redux сама по себе является довольно многословной библиотекой, и для таких вещей вам нужно использовать что-то вроде . В редуксном документе , так что вы сможете отправить закрытие уведомления через несколько секунд. 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],
});

JavaScript — Как передать аргументы командной строки в программу Node.js? — Переполнение стека