卸载ReactDOM.render的节点,但没有触发React卸载的生命周期的问题

1,002 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

最近遇到一个问题,当使用条件渲染卸载通过ReactDOM.render渲染的节点时,节点内部的React组件将不会触发卸载生命周期。下面我们来看一下这个问题。

问题案例

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

function Comp() {
  useEffect(() => {

    return () => {
      console.log('Comp unmount')
    }
  }, [])

  return (
    <div style={{ width: 100, height: 100, backgroundColor: 'red' }}>
      asyncApp
    </div>
  )
}


function Test() {
  useEffect(() => {
    const container = document.getElementById('test')
    ReactDOM.render(<Comp />, container)
    
    return () => {
        console.log('Test unmount')
    }
  }, [])

  return (
    <div id='test'></div>
  )
}


function Home() {
  const [visible, setVisible] = useState(false);

  return (
    <div>
      {visible && <Test />}
      <button
        style={{ position: 'absolute', top: '50%', left: '50%' }}
        onClick={() => setVisible(!visible)}
      >
        开/关
      </button>
    </div>
  )
}

以上案例,是我从公司中的真实场景中抽象出来的demo,公司中有部分核心组件内部提供了使用函数渲染的机制,即ReactDOM.render实现的节点渲染。然后发现在被渲染的组件内部,所有的卸载阶段的生命周期都没有被触发。导致了一些挂载到全局的事件无法被卸载掉。

问题的原因是,ReactDOM.render渲染的节点内部是一个相对独立的react的上下文环境,案例中的场景可以想象成一个react环境包裹另外一个react环境,当通过条件渲染卸载组件时,外层环境被正常卸载,包括内层环境所依赖的容器。内层的节点也在此时被杀掉。对内层的节点来说,这种卸载是连同react的根节点一同被卸载,属于强制下线的操作。react根内部的生命周期自然也无法正常被触发。

解决这样的问题,只需要一行代码。还是案例

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';

function Comp() {
  useEffect(() => {

    return () => {
      console.log('Comp unmount')
    }
  }, [])

  return (
    <div style={{ width: 100, height: 100, backgroundColor: 'red' }}>
      asyncApp
    </div>
  )
}


function Test() {
  useEffect(() => {
    const container = document.getElementById('test')
    ReactDOM.render(<Comp />, container)
    
    return () => {
        console.log('Test unmount')
        ReactDOM.unmountComponentAtNode(container)
       
    }
  }, [])

  return (
    <div id='test'></div>
  )
}


function Home() {
  const [visible, setVisible] = useState(false);

  return (
    <div>
      {visible && <Test />}
      <button
        style={{ position: 'absolute', top: '50%', left: '50%' }}
        onClick={() => setVisible(!visible)}
      >
        开/关
      </button>
    </div>
  )
}

内层react的节点,使用unmountComponentAtNode方法进行手动卸载,就可以达到触发内部节点的卸载阶段的生命周期的目的了。

问题比较简单,权当记录。 哈哈,我承认我又水了一篇博客