【问题标题】:When react perform componentDidMount and componentWillUnmount当 react 执行 componentDidMount 和 componentWillUnmount
【发布时间】:2019-05-09 04:19:19
【问题描述】:

我用 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);

组件一是重新加载应用程序渲染超时而组件二不是?为什么会发生这种情况?

【问题讨论】:

  • 因为{name: 'one'} !== {name: 'one'},但是'two' === 'two'
  • @zerkms 看不到它与 react 中的挂载机制有什么关系
  • @zerkms 真正相关的是(()=&gt;'one')!==(()=&gt;'one'),但'two'==='two'
  • 我不知道你为什么这么认为,你有 {name: 'one'} 道具,我看不出你在哪里有匿名函数 () =&gt; 'one'
  • @zerkms ()=&gt;'one' 实际上是合法的 react 组件,另一个 ()=&gt;'one' 是一个 另一个 react 组件,如果有不同的身份。如果初始化不同的组件来创建元素,react 将卸载第一个组件并重新安装第二个组件,而不是更新“相同”的组件

标签: reactjs


【解决方案1】:

Component 是每次渲染App 时的一个新函数,所以每次都重新挂载&lt;Component name="one" /&gt;,它们被认为是不同的组件。

Component("two") 调用的结果是&lt;TestMount name={"two"} /&gt;TestMount 在每次渲染App 时都保持不变,因此不会重新挂载。

Component 对于它的用途来说是无效组件,将name 字符串作为name 属性传递给TestMount 组件,因为name 参数不是字符串,而是当使用Component 时的props 对象&lt;Component name="one" /&gt;name =&gt; &lt;TestMount name={name} /&gt;渲染函数,为了清楚起见,最好将其命名为 renderTestMount,因为不应该像 Component("two") 那样直接调用组件。

如果一个函数应该作为组件或渲染函数互换使用,签名应该更改为({ name }) =&gt; &lt;TestMount name={name} /&gt;

通过记忆Component 可以实现&lt;Component name="one" /&gt; 的预期行为:

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

但由于Component不依赖于App作用域,正确的方法是在外面定义:

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

function App() {...}

例如,这就是 React Router Route 有单独的 componentrender 组件和 render function 属性的原因。这可以防止对需要在当前范围内动态定义的路由组件进行不必要的重新挂载。

【讨论】:

  • 感谢您的回答,react-router 中的rendercomponent 之间的区别突然对我有意义。问题的关键在于React擅长element而不是Component,注意你不能在App之外useCallback由于钩子限制,lodash.once应该是你的意思
  • 不客气。你不需要在 App 外使用 useCallback,组件在渲染之间已经保持不变,这样它就是常规组件。
  • “组件是无效组件,因为它接受名称作为参数而不是道具” --- 怎么回事?它是一个函数,它可以有任何名称的参数,无论是name 还是foo
  • @zerkms 参数可以有任何名称,但它是 name 表明它应该接收名称字符串作为像 Component("two") 这样的 arg,而当它用作组件时它会接收 props 对象.
  • @zerkms &lt;Component name="one" /&gt;{Component("two")} 不可互换。 OP 的意图是无效的,使TestMount 接收一个字符串作为name 道具。我会更新答案以使自己清楚。
【解决方案2】:

这个问题的关键是 React 组件和 React 元素之间的区别,简而言之,React 是聪明的 element 而不是 Component

组件与元素

Component 是使用&lt;&gt; 操作创建元素的模板。在我看来,&lt;&gt; 很像 OOP 世界中的 new 运算符。

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

每次调用render 方法(或功能组件)时。新元素是使用&lt;&gt; 创建的,但是,React 足够聪明,可以判断在渲染之间创建的元素实际上是相同的,即它之前已创建并且可以重复使用只要该元素是由同一个组件创建

不同的组件怎么样

然而,当用于生成元素的组件的身份发生变化时(即使组件看起来相同),React 仍然相信会有新的东西出现,因此它会删除(卸载)之前的元素并添加(安装)新的元素。因此componentDidMountcomponentWillUnmount 被调用。

怎么混淆

认为我们有一个Component,当我们使用&lt;Component /&gt; 生成element 时,react 可以分辨出相同的元素,因为它们是由相同的Component 生成的 但是,HOCComponent=()=&gt;&lt;Component /&gt;; element= &lt;HOCComponent /&gt;,每次生成element,它使用不同的Component。它实际上是一个动态构建的 HOC。由于 HOC 是在渲染函数内部动态创建的,乍一看可能会让人感到困惑。

真的吗

我从来没有找到任何关于上述想法的官方文档。但是下面的代码足以证明

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);

上面的代码还提供了三种不同的方法来避免不需要的挂载/卸载。所有的解决方案都以某种方式缓存了 HOC 的身份

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-02
    • 2020-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多