【问题标题】:ReactJS: "Can't perform a React state update on an unmounted component" when open dialogReactJS:打开对话框时“无法对未安装的组件执行 React 状态更新”
【发布时间】:2019-10-10 19:02:42
【问题描述】:

从另一个类组件打开对话框时出现错误:“无法对未安装的组件执行 React 状态更新。这是一个无操作,但它表明您的应用程序中存在内存泄漏。要修复,请取消componentWillUnmount 方法中的所有订阅和异步任务”

index.js

import ...

class AdMenu extends Component {
    componentWillMount = () => {
        this.onSearch();
    };

    onOpenInsert = () => {
        showDetailDialog();
    };

    onSearch = () => {
        fetch(_url, ...)
            .then(response => {
                if (response.ok) {
                    return response.json();
                } else {
                    throw response;
                }
            })
            .then(responseJson => {
                this.setState({...});
            })
            .catch(response => {...});
    };

    render() {
        return (
            <div>
                <DetailDialog />
                <Button color="primary" onClick={this.onOpenInsert}>Add New</Button>
                <BootstrapTable .... />
            </div>
        );
    }
}

export default withTranslation()(AdMenu);

DetailDialog.js


export var showDetailDialog = function () {
    this.setState({open: true});
    console.log('Mounted: ' + this.mounted);
};

class DetailDialog extends React.Component {
    mounted = false;
    controller = new AbortController();
    constructor(props) {
        super(props);
        this.state = {open: false};
        showDetailDialog = showDetailDialog.bind(this);
    }
    componentDidMount() {
        console.log('componentDidMount');
        this.mounted = true;
    }

    componentWillUnmount(){
        console.log('componentWillUnmount');
        this.mounted = false;
    }
    onClose = () => {
        this.setState({open: false});
    };

    render() {
        return (
            <Modal isOpen={this.state.open} toggle={this.onClose} className={"modal-primary"} >
                <ModalHeader toggle={this.onClose}>Detail</ModalHeader>
                <ModalBody>
                    ...
                </ModalBody>
            </Modal>
        );
    }
}

export default withTranslation()(DetailDialog);

我有一个 DetailDialog 导出类组件和函数 showDetailDialog。它已导入 index.js 页面。

当我第一次打开页面并单击打开对话框时,工作正常。但是当我在菜单中通过路由器切换到另一个页面然后第二次再次打开页面时,控制台日志中出现错误。

我尝试使用 this.mounted var 检查卸载的组件,但我不知道如何设置状态以在组件第二次和下一次卸载时打开详细对话框。

我尝试使用 controller = new AbortController();和 componentWillUnmount() 中的 controller.abort() 但不起作用。

或者有解决这个问题的办法吗?

谢谢!

图片:https://prnt.sc/nsp251

error image in console log

CodeSandbox 上的来源:https://codesandbox.io/s/coreuicoreuifreereactadmintemplate-5unwj

步骤测试:

  • 点击广告菜单(第一次)

  • 点击广告组

  • 点击广告菜单(第 2 次)

  • 点击广告菜单中的打开对话框

  • 查看控制台日志浏览器

文件:src/views/category

节点 v11.12.0

Npm 6.7.0

窗口 10

【问题讨论】:

  • 使用 componentDidMount() 而不是 componentWillMount() 并检查问题是否仍然存在。顺便说一句,请始终避免componentWillMount()
  • 另外,相关:stackoverflow.com/a/53949849/482868 我认为,您应该将您的this.setState() 包装到ifs 中,以检查组件是否仍然安装。有意义吗?
  • 感谢 Igor Soloydenko!,我尝试了 componentWillMount() { this.mounted = true;但仍然错误,因为当 didMount 时没有再次安装某些东西,或者当 willUnmount 时某些东西没有取消。我不知道那是什么:|
  • 我也试过 if (this.mounted) { this.setState({ open: true }); }else{//怎么办?}。它没有错误,也没有显示对话框
  • @IgorSoloydenko:或者是否有另一种方法可以从 index.js 打开详细对话框而不导出函数 showDetailDialog ?

标签: reactjs opendialog


【解决方案1】:

您的问题是使用外部函数showDetailDialog 来访问DetailDialog 组件的状态。 AdMenu 组件中使用的函数和DetailDialog 组件在其构造函数中绑定的函数不是一回事。

一种解决方案是使用Refs 并在组件本身上公开open 函数。

class DetailDialog extends Component {
    open = () => this.setState({ open: true });
}

/* ... */

class AdMenu extends Component {
    constructor(props) {
        super(props);
        this.detailDialog = React.createRef();
        this.onOpenInsert = this.onOpenInsert.bind(this);
    }

    onOpenInsert() {
        this.detailDialog.current.open();
    }

    render() {
        return (
            <DetailDialog ref={this.detailDialog} />
            { ... }
        );
    }
}

但是React documentation for Refs不推荐这种方式。

refs 有几个很好的用例:

  • 管理焦点、文本选择或媒体播放。
  • 触发命令式动画。
  • 与第三方 DOM 库集成。

避免将 refs 用于任何可以以声明方式完成的事情。

例如,不要在 Dialog 组件上公开 open()close() 方法,而是将 isOpen 属性传递给它。

根据文档的建议,您可以在 AdMenu 组件上声明 detailOpen 状态并将其作为 open 属性传递给 DetailDialog 组件。

class AdMenu extends Component {
    constructor(props) {
        super(props);
        this.state = {
            detailOpen: false
        }
    }

    onOpenInsert() {
        this.setState({ detailOpen: true });
    }
    
    onDialogClose() {
        this.setState({ detailOpen: false });
    }
    
    /* ... */

    render() {
        return (
            <DetailDialog open={this.state.detailOpen} onClose={this.onDialogClose} />
            { ... }
        );
    }
}

/* ... */

class DetailDialog extends Component {
    /* ... */

    render() {
        return (
            <Modal isOpen={this.props.open} toggle={this.props.onClose}>
                <ModalHeader toggle={this.props.onClose}>Detail</ModalHeader>
                <ModalBody>
                    ...
                </ModalBody>
            </Modal>
        );
    }
}

您选择哪种方法取决于您。

【讨论】:

  • 非常感谢!首先,我在 AdMenu 中使用了 detailOpen 状态,但是当我需要从 AdMenu 向 DetailDialog 传递数据时,我也通过 DetailDialog 的 props 传递数据。在 DetailDialog 中,我想将数据传递到 Input 文本中,所以我必须使用 state.data = props.data。 (因为不能改变道具的价值。
  • 此外,当我有很多对话框需要在 AdMenu 中调用时,我必须为每个对话框创建 state/openDialog func/closeDialog func。如detailOpen、approveDialogOpen、...State var。并打开关闭对话框功能。代码变得混乱
  • 这些是操作子组件的最常用方法。此外,更多的对话和额外的数据不是问题的一部分。
【解决方案2】:

将你写在componentWillMount中的逻辑移动到AdMenu中的componentDidMount

【讨论】:

  • 谢谢,但仍然出错。我的事,DetailDialog 中的问题
  • 在DetailDialog中试试这个toggle={() =&gt; this.onClose()} ...?
  • 你能用你的代码创建一个jsfiddle吗?它可以帮助找出代码有什么问题
  • 我编辑了我的帖子,添加链接沙箱:codesandbox.io/s/coreuicoreuifreereactadmintemplate-5unwj
猜你喜欢
  • 2020-03-26
  • 1970-01-01
  • 2021-08-05
  • 2021-07-01
  • 2020-11-21
  • 2019-11-09
  • 2019-05-30
  • 2021-05-30
相关资源
最近更新 更多