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 конкретных решений:
большинство примитивное решение, обрабатывающее только действия "success" / "failed" из компонента. Теперь это решение я не большой поклонник. В моей конкретной реализации нет действия, которое указывает, что асинхронный запрос был запущен. Побочные эффекты обрабатываются прямо в компоненте, а не абстрагируются в саге. Много потенциальных повторений кода.
запуск однократной саги прямо перед отправкой действия запроса, которое 'success' / 'failed' действия друг против друга и позволяет реагировать на первое происходящее действие. Для этого я написал помощника, который абстрагирует ход саги: https://github.com/milanju/redux-post-handling-example/blob/master/src/watchNext.js Этот пример Мне нравится намного больше, чем 1. поскольку это просто и декларативно. Хотя я не знаю, имеет ли создание саги во время выполнения каких-либо негативных последствий, или, может быть, есть другой "правильный" способ достичь что я делаю с redux-saga?
помещая все, что связано с действием (загрузка, successFlag, ошибка) в хранилище и реагируя в componentWillReceiveProps на изменения действия ((!этот.реквизит.успех & & nextProps.успех)) означает, что действие завершилось успешно). Это похоже на второй пример, но работает с любым решением для обработки побочных эффектов, которое вы выбираете. Возможно, я контролирую что-то вроде обнаружения действия, которое не работает, если реквизит град в очень быстро и реквизит собирается в componentWillReceiveProps будет "накапливаться" и компонент пропускает переход от неудачи к успеху в целом?
пожалуйста, не стесняйтесь взглянуть на пример проекта, который я создал для этого вопроса, который имеет полный пример реализованных решений: https://github.com/milanju/redux-post-handling-example
Я хотел бы получить некоторые данные о методах, которые я использую для обработки описанного поток действий.
- я что-то неправильно понял? 'Решения' я придумал не для меня. Возможно, я смотрю на проблему под неправильным углом.
- есть ли какие-либо проблемы с приведенными выше примерами?
- есть ли какая-либо передовая практика или стандартные решения для этой проблемы?
- как вы управляете описанным потоком?
Спасибо за чтение.
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
присутствует.
но это происходит за счет:
- добавление записи в магазин (хотя это неизбежно)
- добавление большой сложности на деталь.
Я бы ожидал:
- the
asyncActionId
логика, которая будет проводиться на стороне саги (например. когда асинхронное действиеconnect
ed с помощьюmapActionsToProps
возвращаетid
при вызове:const asyncActionId = this.props.asyncAction(params)
, какsetTimeout
) - хранилище часть забора
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
.
Надеюсь, все было ясно.