【问题标题】:Failed fetch causes infinite component life-cycle loop获取失败导致无限组件生命周期循环
【发布时间】:2017-05-26 18:21:40
【问题描述】:

使用连接的容器,我有一个由更高阶的减速器(如下所示)包裹的减速器,以捕获和处理错误。在componentDidMount 期间调用获取请求并且失败时,连接的容器将自行卸载componentWillUnmount。这会导致容器中的无限循环,因为它将再次挂载,获取将失败,并且容器将自行卸载。

任何想法为什么在连接组件中使用高阶 reducer 会导致这种情况?

错误处理高阶reducer:

export const errorHandler = (reducer: (state: any, action: { type: string }, initialState: any) => {}) => {
    const errorState = fromJS({
        error: {
            hasError: false,
            message: "",
        },
    });

    const initialState = errorState.merge(reducer(undefined, { type: undefined }, undefined));

    return (state = initialState, action) => {
        switch (action.type) {
            case ACTIONS.SET_ERROR:
                return state.setIn(["error", "hasError"], true)
                    .setIn(["error", "message"], action.message);

            case ACTIONS.CLEAR_ERROR:
                return state.set("error", errorState.get("error"));

            default:
                return reducer(state, action, initialState);
        }
    };
};

示例容器:

class Page extends Component {
    componentDidMount() {
        this.props.fetch(....);
    }
    componentWillUnmount() {
        this.props.clearData();
        this.props.cancelRequests();
    }
}

export default connect(
    (state) => ({
        error: state.data.get("error", ""),
    }),
    {
        clearError,
        clearData,
        cancelRequests,
    },
)(Page);

Reducer 示例:

export fetch = () => ({
   type: ACTIONS.FETCH
});

export default errorHandler((state, action) => {
   switch(action.type) {
     default:
        return state;
   }
}));

史诗:

export const fetch = (action$: any, store: any, api: API) => {
    return action$.ofType(ACTIONS.FETCH)
        .mergeMap((action: any) =>
            fromPromise(api.fetch(action.data))
                .pluck("Data")
                .map(data) =>
                    fetchFulfilled(data),
                )
                .catch((response) => {
                    const toPromise = typeof response.json === "function" ? response.json() : new Promise((resolve) => resolve(response));

                    return fromPromise(toPromise)
                        .pluck("Message")
                        .map((Message: string) =>
                            setError(Message));
                })
                .takeUntil(action$.ofType(ACTIONS.CANCEL_REQUESTS)));
};

【问题讨论】:

  • 通常会卸载组件,因为它们的父级不再呈现它们。 的父级是什么样的? (我实际上不知道组件可以自行卸载的任何情况,但也许有可能)
  • 这正是问题所在。在进一步研究了这个问题后,我发现了一个使用相同 errorHandler() 高阶减速器的父组件。每当 setError() 被触发时,父级会捕获该事件,并重新渲染其子级(包括此处的 Page)。不再使用全局 setError() 操作完全解决了问题。

标签: javascript reactjs redux rxjs redux-observable


【解决方案1】:

根据我们在 cmets 中的对话:

通常会卸载组件,因为它们的父级不再呈现它们。的父母长什么样子?您可能会在其中查找组件卸载的原因。

我不知道组件可以自行卸载(无需 hack)的任何情况

【讨论】:

    【解决方案2】:

    我认为你只需要捕获错误而不是让异常被 React 安装代码捕获。

    try {
       this.props.fetch(....);
    }
    catch (e) {
       //Do whatever is appropriate to handle the fetch failure. Maybe you want...
       this.setState({ error: {hasError: true, message: '' + e});
    }
    

    我认为上面的 setState() 调用不适合您预期的 reducer 实现,但这是您可以解决的单独问题(或提出更多问题)。您的问题的主要部分似乎是停止卸载/重新安装行为。

    【讨论】:

    • 感谢您的帮助!我忘了提到我正在使用 Redux-Observable,所以在这种情况下,this.props.fetch() 只是调度一个动作——而不是执行请求。不幸的是,将它包装在 try/catch 中是行不通的。原始帖子中的示例已更新。
    猜你喜欢
    • 2020-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-24
    • 1970-01-01
    • 2015-06-25
    相关资源
    最近更新 更多