为什么 React 会丢弃整个 DOM 子树并从头开始重新创建?

IT技术 javascript reactjs
2021-04-13 06:33:32

有两个助手可用于在渲染时添加内容:

  ...

  const DisplayA = () => <div className={'containerA'}>
    <button onClick={handleToggleA}>{"A toggled: " + toggledA.toString()}</button>
  </div>

  const displayB = () => <div className={'containerB'}>
    <button onClick={handleToggleB}>{"B toggled: " + toggledB.toString()}</button>
  </div>

  return (
    <>
      <DisplayA />
      { displayB() }
    </>
  );

  ...

问题是在第一个 helper 中,React 总是丢弃整个子树并从头开始重新创建,如下所示:

子树被丢弃并重新创建

演示

我知道,第一种方法是 React.createElement 的语法糖,因此每次渲染都会创建一个新组件。然而,第二种方式,每个渲染也会创建一个不同的箭头函数。

  1. 为什么 React 不知道如何以第一种方式重用子树,但知道第二种方式?引擎盖下发生什么?

  2. 我们如何发现 DOM 子树何时被丢弃并在每次渲染时重新创建?假设不应该创建内联组件而只使用内联函数就足够了吗?

请注意,助手可以来自props,例如(渲染props模式)。

2个回答

这将取决于DisplayA定义的范围功能组件通常应该在文件的顶层定义。在您的演示中DisplayA是一个在renderof内部创建的组件App,因此每次App渲染都会创建一个新的功能组件,而不是对同一组件的新调用。

为了解决这个问题DisplayA,在文件中制作顶级并将props传递给它。

const DisplayA = ({handleToggle, toggled}) => <div className={'containerA'}>
  <button onClick={handleToggle}>{"A toggled: " + toggled.toString()} </button>
</div>

const App = () => {
  ...
  return <>
    <DisplayA handleToggle={() => {...}} toggle={...} />
  </>
}

第二种方法不是创建一个传递给react进行协调的组件,而是一个在渲染时调用的函数,并将包含的元素放入该组件的渲染中。

更准确地说,如果函数相等oldElement === newElement,那么它将重新使用旧元素树
2021-06-08 06:33:32

在第一种情况下,您没有调用DisplayA. 相反,您让 react 决定何时渲染它。请注意在转换时如何React.createElement(DisplayA)不调用此函数。当 react 渲染子树时,它决定需要重新渲染什么。

处理树更改/更新的过程称为协调。react 文档中,它说相同的类型将尝试维护状态,而不同的组件类型将始终对 DOM 树执行拆卸。

后者发生在您的DisplayA组件上,因为它在每次渲染时都有不同的值。尽管组件呈现相同的视图,但 React 无法确定这是同一个组件,因为DisplayA每次的值都指向不同的组件引用。在这种情况下,您使用的是函数组件,因此该值是对新函数的引用。变量名恰好相同——它在运行时没有任何重要性。

在第二种情况下,displayB您显式调用函数并呈现其结果。因为函数是纯函数,这相当于拉取它的返回值并内联到父组件中:

return (
  <>
    <DisplayA />
    { 
      <div className={'containerB'}>
        <button onClick={handleToggleB}>{"B toggled: " + toggledB.toString()}</button>
      </div>
    }
  </>
)

注意这个片段的第二个孩子现在是一个div. 因为divs 是原始元素,所以它们由文字字符串表示,'div'而不是对组件的引用。React 在渲染之间知道这是同一棵树,因此不会破坏它。如果您有任何具有稳定参考的外部组件,这也将起作用——它将被视为相同类型的元素