Redux: запрос на успех или поток ошибок из компонента (используя redux-saga)

это единственное, что я еще не нашел стандартного решения.

У меня есть настройка магазина с redux-saga для обработки побочных эффектов, я отправляю действие (которое имеет асинхронные побочные эффекты) из компонента, и теперь хочу, чтобы компонент сделал что-то после обработки побочных эффектов (например, перейдите к другому маршруту/экрану, всплывающему модальному/тосту или чему-либо еще).

также я хочу, чтобы отобразить индикатор загрузки или любые ошибки на неудача.

до redux этот вид потока был прямо вперед, он выглядел бы примерно так:

try {
  this.setState({loading: true});
  const result = await doSomeRequest();
  this.setState({item: result, loading: false});
} catch (e) {
  this.setState({loading: false, error: e});
}

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

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

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

Я действительно пытался найти такой (казалось бы, общий) сценарий в redux docs, redux-saga docs или Stackoverflow / Google, но без успеха.

Примечание: также с redux-thunk я думаю, что это поведение прямо вперед, чтобы достичь, так как я могу просто .затем на отправке асинхронного действия и получит действие успеха или действие ошибки в catch (исправьте меня, если я ошибаюсь, никогда не использовал thunk). Но я еще не видел такого же поведения, достигнутого с redux-saga.

Я придумал 3 конкретных решений:

  1. большинство примитивное решение, обрабатывающее только действия "success" / "failed" из компонента. Теперь это решение я не большой поклонник. В моей конкретной реализации нет действия, которое указывает, что асинхронный запрос был запущен. Побочные эффекты обрабатываются прямо в компоненте, а не абстрагируются в саге. Много потенциальных повторений кода.

  2. запуск однократной саги прямо перед отправкой действия запроса, которое 'success' / 'failed' действия друг против друга и позволяет реагировать на первое происходящее действие. Для этого я написал помощника, который абстрагирует ход саги: https://github.com/milanju/redux-post-handling-example/blob/master/src/watchNext.js Этот пример Мне нравится намного больше, чем 1. поскольку это просто и декларативно. Хотя я не знаю, имеет ли создание саги во время выполнения каких-либо негативных последствий, или, может быть, есть другой "правильный" способ достичь что я делаю с redux-saga?

  3. помещая все, что связано с действием (загрузка, successFlag, ошибка) в хранилище и реагируя в componentWillReceiveProps на изменения действия ((!этот.реквизит.успех & & nextProps.успех)) означает, что действие завершилось успешно). Это похоже на второй пример, но работает с любым решением для обработки побочных эффектов, которое вы выбираете. Возможно, я контролирую что-то вроде обнаружения действия, которое не работает, если реквизит град в очень быстро и реквизит собирается в componentWillReceiveProps будет "накапливаться" и компонент пропускает переход от неудачи к успеху в целом?

пожалуйста, не стесняйтесь взглянуть на пример проекта, который я создал для этого вопроса, который имеет полный пример реализованных решений: https://github.com/milanju/redux-post-handling-example

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

  1. я что-то неправильно понял? 'Решения' я придумал не для меня. Возможно, я смотрю на проблему под неправильным углом.
  2. есть ли какие-либо проблемы с приведенными выше примерами?
  3. есть ли какая-либо передовая практика или стандартные решения для этой проблемы?
  4. как вы управляете описанным потоком?

Спасибо за чтение.

3 ответов


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

затем вы сравниваете состояние (запрошенное / успешное / неудачное) со старым состоянием и соответственно обрабатываете различные переходы.

Дайте мне знать, если я что-то неправильно.


я достиг точки асинхронного обратного вызова действия в компоненте с помощью saga следующим образом:

class CallbackableComponent extends Component {
  constructor() {
    super()
    this.state = {
      asyncActionId: null,
    }
  }

  onTriggerAction = event => {
    if (this.state.asyncActionId) return; // Only once at a time
    const asyncActionId = randomHash();
    this.setState({
      asyncActionId
    })
    this.props.asyncActionWithId({
      actionId: asyncActionId,
      ...whateverParams
    })
  }

  static getDerivedStateFromProps(newProps, prevState) {
    if (prevState.asyncActionId) {
      const returnedQuery = newProps.queries.find(q => q.id === prevState.asyncActionId)
      return {
        asyncActionId: get(returnedQuery, 'status', '') === 'PENDING' ? returnedQuery.id : null
      }
    }
    return null;
  }
}

С queries редуктор такой:

import get from 'lodash.get'

const PENDING = 'PENDING'
const SUCCESS = 'SUCCESS'
const FAIL = 'FAIL'

export default (state = [], action) => {
  const id = get(action, 'config.actionId')
  if (/REQUEST_DATA_(POST|PUT|DELETE|PATCH)_(.*)/.test(action.type)) {
    return state.concat({
      id,
      status: PENDING,
    })
  } else if (
    /(SUCCESS|FAIL)_DATA_(GET|POST|PUT|PATCH)_(.*)/.test(action.type)
  ) {
    return state
      .filter(s => s.status !== PENDING) // Delete the finished ones (SUCCESS/FAIL) in the next cycle
      .map(
        s =>
          s.id === id
            ? {
                id,
                status: action.type.indexOf(SUCCESS) === 0 ? SUCCESS : FAIL,
              }
            : s
      )
  }
  return state
}

в конце концов, мой CallbackableComponent знает, если запрос закончил, проверив, если this.state.asyncActionId присутствует.

но это происходит за счет:

  1. добавление записи в магазин (хотя это неизбежно)
  2. добавление большой сложности на деталь.

Я бы ожидал:

  1. the asyncActionId логика, которая будет проводиться на стороне саги (например. когда асинхронное действие connected с помощью mapActionsToProps возвращает id при вызове: const asyncActionId = this.props.asyncAction(params), как setTimeout)
  2. хранилище часть забора redux-saga, как react-router отвечает за добавление текущего маршрута в магазин.

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


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

mapStateToProps = state => {
 return {
   loading: state.loading,
   error  : state.error,
   data   : state.data 
 };
}

и компонент будет отображаться как:

return(
   {this.props.loading  && <span>loading</span>}
   {!this.props.loading && <span>{this.props.data}</span>}
   {this.props.error    && <span>error!</span>}
)

и когда requested действие отправляется, его редуктор обновит состояние магазина, чтобы быть {loading: true, error: null}.

и когда succeeded действие отправляется, его редуктор обновит состояние магазина, чтобы быть {loading: false, error: null, data: results}.

и когда failed действие отправляется своим reducer обновит состояние магазина, чтобы быть {loading: false, error: theError}.

таким образом, вам не придется использовать componentWillReceiveProps . Надеюсь, все было ясно.