"setState(…): Can only update a mounted or mounting component" 在 componentDidMount 里面

"setState(…): Can only update a mounted or mounting component" inside componentDidMount

相关问题here但我不确定如何使解决方案适应这个问题。

我正在尝试为带有选项卡的登录页面创建一个可重用的组件。每个选项卡都是可重用组件的 child,并且有自己的存储定义为道具:

<LandingPage>
    <LandingPage.Tab store={store1}/>
    <LandingPage.Tab store={store2}/>
    ...
    <LandingPage.Tab store={storeN}/>
</LandingPage>

我想在安装 parent 组件时从每个选项卡的存储中获取数据,以便在选项卡之间快速切换。在 componentDidMount 函数中,我遍历每个 child 并将 child 商店的 onChange 回调分配给匿名箭头函数:

var LandingPage = React.createClass({
    getInitialState: function () {
        return {
            data: [] /* each index will be an array of data for a different tab */
        };
    },
    componentDidMount: function () {
        var self = this;
        React.Children.forEach(this.props.children, function (child, index) {
            child.props.store.onChange(() => {
                self.setDataAtIndex(index, child.props.store.getData());
            });
        });
    },
    setDataAtIndex: function (index, newData) {
        var data = this.state.data.slice();
        data[index] = newData;
        this.setState({
            data: data
        });
    },
    ...
});

但是,当页面首次加载时,我收到来自 React 的警告消息:

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the LandingPage component.

我很困惑,因为我认为如果我在 componentDidMount 函数中,我可以假设组件已安装。当我刷新页面时,此警告消息消失。

有人可以解释这种行为并告诉我如何正确构建代码以消除警告消息吗?

这个函数...

() => { // this calls setState
    self.setDataAtIndex(index, child.props.store.getData());
}

每次选项卡存储更改时都会调用,无论是否安装了 LandingPage 组件。那是个问题。您需要告诉商店在 LandingPage 卸载时停止调用此函数。在不修改商店的情况下,您可以只使用 no-op 覆盖更改侦听器,就像这样...

componentWillUnmount: function () {
    var self = this;
    React.Children.forEach(this.props.children, function (child, index) {
        child.props.store.onChange(() => {});
    });
}

现在,当组件未安装时,应该调用 () => {},它不会调用 setState,因此是无害的

虽然查理马丁的回答是一个聪明的解决方法,但我最终决定将回调存储在状态变量中,类似于我链接到的相关问题。这是更新后的代码:

var LandingPage = React.createClass({
    getInitialState: function () {
        return {
            data: [] /* each index will be an array of data for a different tab */
            callbacks: [] /* each index will store a callback reference */
        };
    },
    componentDidMount: function () {
        var self = this;
        React.Children.forEach(this.props.children, function (child, index) {
            var fn = function() {
                self.setDataAtIndex(index, child.props.store.getAll());
            };
            child.props.store.onChange(fn);
            self.saveCallback(index, fn);
        });
    },
    componentWillUnmount: function () {
        var self = this;
        React.Children.forEach(this.props.children, function (child, index) {
            child.props.store.offChange(self.state.callbacks[index]);
        });
    },
    saveCallback: function (index, fn) {
        var callbacks = this.state.callbacks;
        callbacks[index] = fn;
        this.setState({
            callbacks: callbacks
        });
    },
    ...
});

作为参考,onChange() 和 offChange() 定义为:

function onChange(callback) {
    this.on('change', callback);
}
function offChange(callback) {
    this.removeListener('change', callback);
}