反应时执行componentDidMount和componentWillUnmount

时间:2019-05-09 04:19:19

标签: reactjs

我在React玩了几年了,在某些情况下仍然对安装/卸载机制感到困惑。

由于挂载/卸载是执行副作用的地方,所以我不希望随机调用它们。所以我需要弄清楚它们是如何工作的。据我目前所了解,当虚拟dom不存在于实际dom中时,往往会被卸载。但是,这似乎不是全部,我也无法对此进行推理

function TestMount(props) {
  useEffect(() => {
    console.log("componentDidMount", props.name);
    return () => {
      console.log("componentWillUnount", props.name);
    };
  }, []);
  return <h1>Test content {" " + JSON.stringify(props.name)}</h1>;
}

function Update({ click }) {
  return <button onClick={click}>Update</button>;
}

function App() {
  const [count, setCount] = useState(0);
  const Component = name => <TestMount name={name} />;
  return (
    <div className="App">
      <h1>{count}</h1>
      <Component name="one" />
      {Component("two")}
      <Update click={() => setCount(x => x + 1)} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

第一个组件正在重新安装应用渲染,而第二个组件则没有?为什么会这样?

2 个答案:

答案 0 :(得分:4)

Component是每次呈现App时的新功能,因此<Component name="one" />也每次都重新安装,它们被视为不同的组件。

Component("two")调用的结果为<TestMount name={"two"} />,每次渲染TestMountApp保持不变,因此不会重新安装。

Component对于它的用途而言是无效的组件,因为name参数不是字符串,所以将name字符串作为TestMount属性传递给name组件但是当Component<Component name="one" />一样使用时,props对象。 name => <TestMount name={name} /> render函数,为了清晰起见,最好相应地命名为renderTestMount,因为不应像Component("two")那样直接调用组件。

如果假定函数可以互换地用作组件或呈现函数,则应将签名更改为({ name }) => <TestMount name={name} />

可以通过记住<Component name="one" />来实现Component的预期行为:

const Component = useCallback(({ name }) => <TestMount name={name} />, []);

但是,由于Component不依赖于App范围,因此正确的方法是在外部进行定义:

const Component = ({ name }) => <TestMount name={name} />;

function App() {...}

例如,这就是React Router Route对组件和render function具有单独的componentrender道具的原因。这样可以防止不必要地重新安装需要在当前作用域中动态定义的路由组件。

答案 1 :(得分:1)

解决此问题的关键是React组件和React元素之间的区别,简而言之Reactelement而非Component一样聪明

组件与元素

Component是用于使用<>操作创建元素的模板。就我的预期而言,<>与OOP世界中的new运算符非常相似。

React如何在渲染之间执行更新

每次调用render方法(或功能组件)。新元素是使用<>创建的,但是React足够聪明,可以告诉渲染器之间创建的元素实际上是相同的,即它是以前创建的,并且可以在因为元素是由相同的组件创建的

关于不同的组件

但是,当用于生成元素的Component的标识发生更改时(即使这些组件看起来相同),React也会相信会有新的东西出现,因此它删除(卸载)前一个元素并添加(装载)新元素。因此,将调用componentDidMountcomponentWillUnmount

如何令人困惑

认为我们有一个Component,当我们使用element生成<Component />时,react可以告诉相同的元素,因为它们是由相同的Component生成的 但是,HOCComponent=()=><Component />; element= <HOCComponent />每次生成element时,都会使用不同的Component。它实际上是动态构建的HOC。由于HOC是在render函数内部动态创建的,因此乍一看可能会造成混淆。

是真的

我从未找到有关上述想法的官方文件。但是下面的代码足以证明

function TestMount(props) {
  useEffect(() => {
    console.log("componentDidMount", props.name);
    return () => {
      console.log("componentWillUnount", props.name);
    };
  }, []);
  return <h1>Test content {" " + JSON.stringify(props.name)}</h1>;
}

function Update({ click }) {
  return <button onClick={click}>Update</button>;
}

let _Component;
function cacheComponent(C) {
  if (C && !_Component) {
    _Component = C;
  }
  return _Component || null;
}

const CacheComponent2 = once(({ name }) => <TestMount name={name} />, []);

function App() {
  const [count, setCount] = useState(0);
  // can be used as a HOC of TestMount or a plain function returnnung a react element
  const Component = name => <TestMount name={name} />;
  const CacheComponent1 = cacheComponent(Component);
  const CacheComponent3 = useCallback(
    ({ name }) => <TestMount name={name} />,
    []
  );

  return (
    <div className="App">
      <h1>{count}</h1>
      {/* used as HOC */}
      <Component name="one" />
      {/* used as function returnning the element */}
      {Component("two")}
      <CacheComponent1 name="three" />
      <CacheComponent2 name="four" />
      <CacheComponent3 name="five" />
      <Update click={() => setCount(x => x + 1)} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Edit react-unmount-case

上面的代码还提供了三种不同的方式来避免意外的安装/卸载。所有解决方案都是以某种方式缓存HOC的身份