无限循环与 redux-saga
Infinite loop with redux-saga
我正在使用 react-router v4 和 redux-saga。我试图在页面加载时进行 API 调用。当我访问 /detailpage/slug/
时,我的应用程序似乎陷入循环并无休止地调用我的 API 端点。这是我的应用程序的设置方式。假设我的导入是正确的。
index.js
const history = createHistory()
const sagaMiddleware = createSagaMiddleware()
const middleware = [routerMiddleware(history), sagaMiddleware]
const store = createStore(
combineReducers({
aReducer
}),
applyMiddleware(...middleware)
)
injectTapEventPlugin();
sagaMiddleware.run(rootSaga)
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/detailpage/:slug" component={Detail} />
<Route path="/page" component={Page} />
</Switch>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
);
reducers/index.js
const aReducer = (state={}, action) => {
switch(action.type) {
case 'SHOW_DETAIL':
console.log('Reducers: reducer called')
return Object.assign({}, state, { name: 'adfsdf' })
default:
return state;
}
}
export default aReducer;
actions/index.js
export const showDetailInfo = () => {
console.log('action called')
return {
type: 'SHOW_DETAIL'
}
}
saga.js
export function* fetchDetailsAsync() {
try {
console.log('fetching detail info')
const response = yield call(fetch, 'http://localhost:8000/object/1/', {
method: 'GET',
headers: {
'Authorization': 'Token xxxxxxxxxxxx'
}})
console.log(response);
yield put({type: 'SHOW_DETAIL', response: response.data})
} catch (e) {
console.log('error')
}
}
// watcher saga
export function* fetchDetails() {
console.log('watcher saga')
yield takeEvery('SHOW_DETAIL', fetchDetailsAsync)
}
export default function* rootSaga() {
console.log('root saga')
yield [
fetchDetails()
]
}
containers/Detail.js
const mapStateToProps = (state) => {
return {
name: 'Test'
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
console.log('mapDispatchToProps')
return {
showDetailInfo: (payload) => {
console.log('dispatching');
dispatch({ type: 'SHOW_DETAIL' })
}
}
}
const Detail = connect(
mapStateToProps,
mapDispatchToProps
)(DetailPage)
export default Detail;
components/DetailPage.js
class DetailPage extends React.Component {
componentWillMount() {
this.props.showDetailInfo();
}
render() {
return (
<Layout>
<h3>DetailPage</h3>
<p>{ this.props.name }</p>
</Layout>
)
}
}
DetailPage.PropTypes = {
showDetailInfo: PropTypes.func.isRequired,
name: PropTypes.string.isRequired
}
export default DetailPage;
我花了几天时间进行故障排除,尝试了各种想法,包括测试不同的生命周期方法,从 applyMiddleware 中删除 routerMiddleware。
我认为我的组件在每次 API 调用后都在更新,但是任何生命周期方法的 console.log 表明它不是。
作为 React 生态系统的新手,这里有很多活动部件,对我来说排除故障很有挑战性。
当然,您在下一行中明确设置了无限循环:
yield put({type: 'SHOW_DETAIL', response: response.data})
// ...
yield takeEvery('SHOW_DETAIL', fetchDetailsAsync)
saga 不会为您做任何神奇的事情,它只是订阅和生成操作和执行协程的初级层。
解决方案:
对于从 React 组件捕获的操作,以及用于乐观和实际更新状态的操作,您应该使用不同的名称。
使用 yield takeEvery('SHOW_DETAIL_REQUEST', fetchDetailsAsync)
并以此方式命名您的操作。
在成功响应中使用 yield put({type: 'SHOW_DETAIL_SUCCESS', response: response.data})
并以此方式命名您的减速器
不仅如此,您还可以对失败的 saga 请求使用 'SHOW_DETAIL_FAILURE'
。
以上名称均为通用名称
我知道您找到了答案,太好了。我有相同的症状,但问题不同,解决方案也不同。
我没有为我的动作使用普通常量,我使用的是来自我的动作的常量,这样我只需要将它写在一个位置。这是我的设置。但是我今天意识到一个问题。我的代码如下所示
export const deleteArendeAction = {
will: arende => ({
type: "WILL_TA_BORT_ARENDEN",
...arende,
}),
did: payload => ({
type: "DID_TA_BORT_ARENDEN",
...payload,
}),
error: payload => ({
type: "DID_DELETE_ARENDE_ERROR",
...payload,
}),
}
function* deleteArenden(arende) {
try {
yield arendeApi.deleteArende(arende.id)
} catch (error) {
yield put(deleteArendeAction.error(arende))
return
}
yield put(deleteArendeAction.did(arende))
}
export function* deleteArendeSaga() {
yield takeEvery(deleteArendeAction.will().type, deleteArenden)
}
我的代码看起来像那样。
它不断触发我的 takeEvery 无限。
原来,yield put(deleteArendeAction.did(arende))
这部分是罪魁祸首。因为变量 arende
的值为 { type: "WILL_TA_BORT_ARENDEN", ... }
导致了某种错误,再次触发了事件。
从技术上讲,我认为这不应该发生?但确实如此。
因此,如果您遇到这个问题,而答案并不能解决您的问题。然后仔细检查您发送到 put
的内容:P
我正在使用 react-router v4 和 redux-saga。我试图在页面加载时进行 API 调用。当我访问 /detailpage/slug/
时,我的应用程序似乎陷入循环并无休止地调用我的 API 端点。这是我的应用程序的设置方式。假设我的导入是正确的。
index.js
const history = createHistory()
const sagaMiddleware = createSagaMiddleware()
const middleware = [routerMiddleware(history), sagaMiddleware]
const store = createStore(
combineReducers({
aReducer
}),
applyMiddleware(...middleware)
)
injectTapEventPlugin();
sagaMiddleware.run(rootSaga)
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/detailpage/:slug" component={Detail} />
<Route path="/page" component={Page} />
</Switch>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
);
reducers/index.js
const aReducer = (state={}, action) => {
switch(action.type) {
case 'SHOW_DETAIL':
console.log('Reducers: reducer called')
return Object.assign({}, state, { name: 'adfsdf' })
default:
return state;
}
}
export default aReducer;
actions/index.js
export const showDetailInfo = () => {
console.log('action called')
return {
type: 'SHOW_DETAIL'
}
}
saga.js
export function* fetchDetailsAsync() {
try {
console.log('fetching detail info')
const response = yield call(fetch, 'http://localhost:8000/object/1/', {
method: 'GET',
headers: {
'Authorization': 'Token xxxxxxxxxxxx'
}})
console.log(response);
yield put({type: 'SHOW_DETAIL', response: response.data})
} catch (e) {
console.log('error')
}
}
// watcher saga
export function* fetchDetails() {
console.log('watcher saga')
yield takeEvery('SHOW_DETAIL', fetchDetailsAsync)
}
export default function* rootSaga() {
console.log('root saga')
yield [
fetchDetails()
]
}
containers/Detail.js
const mapStateToProps = (state) => {
return {
name: 'Test'
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
console.log('mapDispatchToProps')
return {
showDetailInfo: (payload) => {
console.log('dispatching');
dispatch({ type: 'SHOW_DETAIL' })
}
}
}
const Detail = connect(
mapStateToProps,
mapDispatchToProps
)(DetailPage)
export default Detail;
components/DetailPage.js
class DetailPage extends React.Component {
componentWillMount() {
this.props.showDetailInfo();
}
render() {
return (
<Layout>
<h3>DetailPage</h3>
<p>{ this.props.name }</p>
</Layout>
)
}
}
DetailPage.PropTypes = {
showDetailInfo: PropTypes.func.isRequired,
name: PropTypes.string.isRequired
}
export default DetailPage;
我花了几天时间进行故障排除,尝试了各种想法,包括测试不同的生命周期方法,从 applyMiddleware 中删除 routerMiddleware。
我认为我的组件在每次 API 调用后都在更新,但是任何生命周期方法的 console.log 表明它不是。
作为 React 生态系统的新手,这里有很多活动部件,对我来说排除故障很有挑战性。
当然,您在下一行中明确设置了无限循环:
yield put({type: 'SHOW_DETAIL', response: response.data})
// ...
yield takeEvery('SHOW_DETAIL', fetchDetailsAsync)
saga 不会为您做任何神奇的事情,它只是订阅和生成操作和执行协程的初级层。
解决方案:
对于从 React 组件捕获的操作,以及用于乐观和实际更新状态的操作,您应该使用不同的名称。
使用 yield takeEvery('SHOW_DETAIL_REQUEST', fetchDetailsAsync)
并以此方式命名您的操作。
在成功响应中使用 yield put({type: 'SHOW_DETAIL_SUCCESS', response: response.data})
并以此方式命名您的减速器
不仅如此,您还可以对失败的 saga 请求使用 'SHOW_DETAIL_FAILURE'
。
以上名称均为通用名称
我知道您找到了答案,太好了。我有相同的症状,但问题不同,解决方案也不同。
我没有为我的动作使用普通常量,我使用的是来自我的动作的常量,这样我只需要将它写在一个位置。这是我的设置。但是我今天意识到一个问题。我的代码如下所示
export const deleteArendeAction = {
will: arende => ({
type: "WILL_TA_BORT_ARENDEN",
...arende,
}),
did: payload => ({
type: "DID_TA_BORT_ARENDEN",
...payload,
}),
error: payload => ({
type: "DID_DELETE_ARENDE_ERROR",
...payload,
}),
}
function* deleteArenden(arende) {
try {
yield arendeApi.deleteArende(arende.id)
} catch (error) {
yield put(deleteArendeAction.error(arende))
return
}
yield put(deleteArendeAction.did(arende))
}
export function* deleteArendeSaga() {
yield takeEvery(deleteArendeAction.will().type, deleteArenden)
}
我的代码看起来像那样。
它不断触发我的 takeEvery 无限。
原来,yield put(deleteArendeAction.did(arende))
这部分是罪魁祸首。因为变量 arende
的值为 { type: "WILL_TA_BORT_ARENDEN", ... }
导致了某种错误,再次触发了事件。
从技术上讲,我认为这不应该发生?但确实如此。
因此,如果您遇到这个问题,而答案并不能解决您的问题。然后仔细检查您发送到 put
的内容:P