【问题标题】:Should Promises be avoided in React components?在 React 组件中应该避免 Promise 吗?
【发布时间】:2017-12-14 07:44:36
【问题描述】:

我最近在 React 中遇到了这个错误:

warning.js:36 警告:setState(...): Can only update amounted or 安装组件。这通常意味着您在一个 未安装的组件。这是一个无操作。请检查代码 BillingDetails 组件。

挖掘后我发现这是因为我在未安装的组件中执行setState这样的:

componentWillMount() {
    this.fetchBillings(this.props.userType);
}

componentWillReceiveProps({ userType }) {
    if (this.props.userType !== userType) {
        this.fetchBillings(userType);
    }
}

fetchBillings = userType => {
    switch (userType) {
        case USER_TYPE.BRAND:
            this.props.fetchBrandBillings()
                .then(() => this.setState({ isLoading: false }));
            return;
        default:
    }
};

fetchBillings 是一个 redux-axios action creator,它返回一个承诺

export const fetchBrandBillings = () => ({
    type: FETCH_BRAND_BILLINGS,
    payload: {
        request: {
            method: 'GET',
            url: Endpoints.FETCH_BRAND_BILLINGS,
        },
    },
});

问题是当用户在站点快速移动时,组件可能会在 promise 解决时被卸载。

我发现项目周围的很多地方都在做这样的事情:

componentWillMount() {
    const { router, getOrder, params } = this.props;
    getOrder(params.orderId).then(action => {
        if (action.type.endsWith('FAILURE')) {
            router.push(`/dashboard/campaign/${params.campaignId}`);
        }
    })
}

现在我开始认为在组件中使用 Promise 可能是反模式,因为组件可以随时卸载...

【问题讨论】:

  • 我认为redux 使用promise 来改变状态,然后使用react-reduxconnect 将组件与状态挂钩更习惯。然后库负责确保您不会不恰当地改变组件。
  • promise 没问题,但是你必须在 componentDidMount 和 componentWillReceiveProps 中做,而不是在渲染之前或渲染期间
  • @AnisSmail 在组件中有isLoading 标志似乎是个好地方,特别是如果您需要渲染几个相同的组件并且每个组件都必须执行 api 请求。无论如何isLoading 只是其中一种情况,例如,有时您想更改路线。
  • 好的,我明白了。也许看看这个答案:stackoverflow.com/a/37771337/4099279,我不知道是否有更干净的方法来“停止” axios 请求
  • @Tomasz,看看这个:github.com/mzabriskie/axios#cancellation 可以取消正在进行的 axios 请求。我不知道 redux-axios 是如何处理它的

标签: reactjs redux redux-thunk


【解决方案1】:

问题是当用户在站点快速移动时,组件可能会在 promise 解决时被卸载。

由于原生 Promise 是不可中断的,因此这是完全自然的,应该始终如一。您可以通过多种方式克服这个问题,但最终您需要跟踪组件是否仍以一种或另一种方式安装,并且当承诺解决/拒绝时不做任何事情。

另外,来自docs 关于componentWillMount:

避免在此方法中引入任何副作用或订阅。

考虑到这一点,我建议改用componentDidMount 来启动您的提取。总体:

componentDidMount() {
    this._isMounted = true;
    this.fetchBillings(this.props.userType);
}

componentWillReceiveProps({ userType }) {
    if (this.props.userType !== userType) {
        this.fetchBillings(userType);
    }
}

componentWillUnmount() {
    this._isMounted = false;
}

fetchBillings = userType => {
    switch (userType) {
        case USER_TYPE.BRAND:
            this.props.fetchBrandBillings().then(() => {
                if (this._isMounted) {
                    this.setState({ isLoading: false });
                }
            });
            return;
        default:
    }
};

此外,虽然这与您的问题没有直接关系,但您需要考虑到您将有多个并行 fetch 调用并行运行,从而导致数据竞争。也就是说,以下只是等待随时发生:

start fetch0
start fetch1
finish fetch1 -> update
...
finish fetch0 -> update

为避免这种情况,您可以使用时间戳跟踪您的请求。

【讨论】:

    猜你喜欢
    • 2018-06-18
    • 2017-04-16
    • 2014-10-24
    • 2014-10-05
    • 2012-10-14
    • 2013-05-19
    • 2018-08-16
    • 2018-06-16
    • 1970-01-01
    相关资源
    最近更新 更多