我们相信:世界是美好的,你是我也是。平行空间的世界里面,不同版本的生活也在继续...

首先,react可不可以主动卸载一个根组件?肯定是可以的,react必然有这个能力从根源上卸载。那换成卸载子组件呢?那么,也必须是有这个能力的。那么,如何卸载这个根组件或者子组件呢?这就是本文中要探讨的问题。

苏南大叔:react教程,如何卸载一个根组件或子组件?root.unmount() - root-unmount
react教程,如何卸载一个根组件或子组件?root.unmount()(图6-1)

苏南大叔的平行空间笔记本博客,记录苏南大叔和计算机代码的故事。测试环境:create-react-app@5.0.1react@18.2.0react-dom@18.2.0node@16.14.2

测试标的组件

苏南大叔的目标是这样的:
1、使用react的方式卸载组件,而不是通过html操作dom的方式来实现这个需求。
2、卸载组件的时候,希望能够把关联的dom元素也一并卸载。比如利用传送门脱离原有体系的组件。

苏南大叔:react教程,如何卸载一个根组件或子组件?root.unmount() - 测试组件
react教程,如何卸载一个根组件或子组件?root.unmount()(图6-2)

测试组件:

<>
  <div className='class' ref={this.nodeRef}>带class的组件</div>
  {this.state.show.a1 && <p>普通组件a1</p>}
  {this.state.show.a2 && (<div onClick={this.handleClick}>
    传送门组件外部a2
    <Modal>
      <button>传送门组件内部</button>
    </Modal>
  </div>)}
</>

有关传送门组件的定义:
https://seosn.com/say/react-portal.html

方案一,.parentNode.removeChild()

首先需要肯定的是:react最终也是在页面上形成了传统的元素节点。所以,使用.removeChild的方式,是必然可以删除节点的。但是,这操作超出了react的概念。附加在被删除节点上的ref或者state之类的数据,可能会造成一些问题。

let _node = document.querySelector('.class');
_node.parentNode.removeChild(_node);

或者

let _node = this.nodeRef.current;
_node.parentNode.removeChild(_node);

原理上就是使用真实节点的父节点来删除对应真实节点。所以,它必然可以生效,但是也必然必能关联删除附加节点(比如传送门生成的节点)。

苏南大叔:react教程,如何卸载一个根组件或子组件?root.unmount() - 方案一
react教程,如何卸载一个根组件或子组件?root.unmount()(图6-3)

方案二,root.unmount()/root.render("")

这个方案仅仅针对根组件root生效,是无法控制子组件的。而root一般是通过props.root从最外层传递进来的变量,例如:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App root={root} />
);

这样的话,在<App/>内部,就可以通过props.root来进行整体组件卸载了。两种方式:

props.root.unmount();

或者

props.root.render("");

苏南大叔:react教程,如何卸载一个根组件或子组件?root.unmount() - 方案二
react教程,如何卸载一个根组件或子组件?root.unmount()(图6-4)

ReactDOM.unmountComponentAtNode(真实dom),在最新的react@18系列中已经不存在这个函数了。

方案三,.createRoot().unmount()/.createRoot().render("")

这个方案可以处理子组件,但是不能处理根组件root。原理上来说,上一个方案中的root是已经被.createRoot()过的。已经被处理过的.createRoot()不能再次被处理,这也是root不能使用本方案的原因。

这个方案原理就是把子组件的真实dom,变成一个react的根组件,然后再unmount()或者render(""),达到卸载子组件的目的。现在假设真实dom是个classNameaaa的子组件。

ReactDOM.createRoot(document.querySelector('.aaa')).unmount();

或者

ReactDOM.createRoot(document.querySelector('.aaa')).render("");

苏南大叔:react教程,如何卸载一个根组件或子组件?root.unmount() - 方案三
react教程,如何卸载一个根组件或子组件?root.unmount()(图6-5)

虽然这个方案能够达到目的,但是苏南大叔总是感觉哪里不对劲。这个函数的结果并不像是对原来的组件进行卸载,而且对原结构进行了破坏...

方案四,state && <组件/>【推荐】

苏南大叔认为:react和传统的页面逻辑的区别就在于:行动的主动权在谁手里。不同于传统页面,react更像是一种自发式的组织形式,并没有谁能够主动掌控组件的生死。所以从react的方式来思考问题的话,主动卸载某个组件的函数是不是理论上不存在啊...

那么,以react的思路来解释这个需求的话。苏南大叔认为:完美方案就是state && <组件/>。当不想要这个组件的时候,就切换一下state即可。对应组件就会被卸载,而不是隐藏。

this.state = { show: { a1: true, a2: true } }
{this.state.show.a1 && <p>a1</p>}
{this.state.show.a2 && (<div>
  <modal>content</modal>
</div>)}
let _next_show = { ...this.state.show, ...{ a1: false } };
this.setState({ show: _next_show });

苏南大叔:react教程,如何卸载一个根组件或子组件?root.unmount() - 方案四
react教程,如何卸载一个根组件或子组件?root.unmount()(图6-6)

测试代码

以下是本文的完整测试代码:

import React, { createRef } from 'react';
import ReactDOM from 'react-dom/client';
import ReactDOMPortal from 'react-dom';

const root2 = document.getElementById('root-modal');
class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }
  componentDidMount() {
    root2.appendChild(this.el);
  }
  componentWillUnmount() {
    root2.removeChild(this.el);
  }
  render() {
    return ReactDOMPortal.createPortal(
      this.props.children,
      this.el
    );
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.root = props.root;
    this.handleClick = this.handleClick.bind(this);
    this.nodeRef = createRef(null);
    this.state = { show: { a1: true, a2: true } }
  }
  handleClick() {
    // document.querySelector('.class').parentNode.removeChild(document.querySelector('.class'));
    // this.nodeRef.current.parentNode.removeChild(this.nodeRef.current);
    // this.root.unmount();
    // this.root.render("");
    // ReactDOM.unmountComponentAtNode(this.nodeRef.current);
    // ReactDOM.createRoot(document.querySelector('.class')).unmount();
    // ReactDOM.createRoot(document.querySelector('.class')).render("");
    let _next_show = { ...this.state.show, ...{ a2: false } };
    this.setState({ show: _next_show });
  }
  render() {
    return (
      <>
        <div className='class' ref={this.nodeRef}>带class的组件</div>
        {this.state.show.a1 && <p>普通组件a1</p>}
        {this.state.show.a2 && (<div onClick={this.handleClick}>
          传送门组件外部a2
          <Modal>
            <button>传送门组件内部</button>
          </Modal>
        </div>)}
      </>
    );
  }
}
export default App;

结束语

react的角度来想这件事情的话,本文中的这个需求的大部分内容可能是个伪需求。因为主动让某个东西做某事,这并不符合react的思路,更优的思路是:根据某个状态或情形,组件自己主动做某事。驱动力来自自身,而非外界强迫。

如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。

 【福利】 腾讯云最新爆款活动!1核2G云服务器首年50元!

 【源码】本文代码片段及相关软件,请点此获取更多信息

 【绝密】秘籍文章入口,仅传授于有缘之人   react