this.props.children 不会在父状态改变时重新渲染

IT技术 reactjs
2021-04-03 03:15:29

我有一段代码

import React, {Component} from 'react';

class App extends Component {
  render() {
    return (
      <Container>
        <Child/>
      </Container>
    )
  }
}

class Container extends Component {
  render() {
    console.log('Container render');
    return (
      <div onClick={() => this.setState({})}>
        {this.props.children}
      </div>
    )
  }
}

class Child extends Component {
  render() {
    console.log('Child render');
    return <h1>Hi</h1>
  }
}

export default App;

当点击 'Hi' msg 时,只有Container组件保持重新渲染,但Child组件不会重新渲染。

为什么Child组件不会在Container状态更改时重新渲染

我会推理,它不会发生,因为它是Container组件的一个属性,但仍然this.props.child被评估为ChildJSX 中的一个组件,所以不确定。

<div onClick={() => this.setState({})}>
  {this.props.children}
</div>

完整示例https://codesandbox.io/s/529lq0rv2n(检查控制台日志)

3个回答

这个问题很老了,但既然你似乎没有得到令人满意的答案,我也试一试。

正如你自己观察到的,改变

// Scenario A

<div onClick={() => this.setState({})}>
  {this.props.children}
</div>

// Scenario B

<div onClick={() => this.setState({})}>
  <Child />
</div>

事实上,最终会

容器渲染
子渲染

在控制台中,每次单击时。

现在,引用你的话

据我了解,如果 setState() 被触发,Container 组件的渲染函数将被调用,所有子元素都应重新渲染。

你似乎非常接近理解这里发生的事情。

到目前为止,你是对的,因为Container'srender被执行,所以从它返回的组件必须调用它们自己的render方法。

现在,正如你所说的,正确的,

<Child />

// is equal to

React.createElement(Child, {/*props*/}, /*children*/)

从本质上讲,你从上面得到的只是object描述要在屏幕上显示的内容,一个 React Element

这里的关键是要了解React.createElement(Child, {/*props*/}, /*children*/)执行发生,在上述各场景。

那么让我们看看发生了什么:

class App extends Component {
  render() {
    return (
      <Container>
        <Child/>
      </Container>
    )
  }
}

class Container extends Component {
  render() {
    console.log('Container render');
    return (
      <div onClick={() => this.setState({})}>
        {this.props.children}
      </div>
    )
  }
}

class Child extends Component {
  render() {
    console.log('Child render');
    return <h1>Hi</h1>
  }
}

您可以App像这样重写返回值

<Container>
  <Child/>
</Container>

// is equal to

React.createElement(
  Container, 
  {}, 
  React.createElement(
    Child, 
    {}, 
    {}
  )
)

// which is equal to a React Element object, something like

{
  type: Container,
  props: {
    children: {
      type: Child,    // |
      props: {},      // +---> Take note of this object here
      children: {}    // |
    }
  }
}

你也可以Container像这样重写返回值

<div onClick={() => this.setState({})}>
  {this.props.children}
</div>

// is equal to

React.createElement(
  'div', 
  {onClick: () => this.setState({})}, 
  this.props.children
)

// which is equal to React Element

{
  type: 'div',
  props: {
    children: this.props.children
  }
}

现在,this.props.childrenApp返回的 React 元素中包含的内容相同

{
  type: Child,
  props: {},
  children: {}
}

确切地说,这两个东西在引用上是相同的,这意味着在这两种情况下,它们在内存中都是完全相同的。

现在,无论Containerget 被重新渲染多少次,因为它children在渲染之间总是引用相同的东西(因为 React Element 是在App关卡中创建的并且没有理由更改),它们不会被重新渲染。

简而言之,如果 React 元素在引用上 ( ) 等于它在之前渲染中的内容,则React 不会费心再次===渲染它。

现在,如果您要更改Container您将拥有:

<div onClick={() => this.setState({})}>
  <Child />
</div>

// is equal to

React.createElement(
  'div', 
  {onClick: () => this.setState({})}, 
  React.createElement(
    Child, 
    {}, 
    {}
  ) 
)

// which is equal to

{
  type: 'div',
  props: {
    children: {
      type: Child,
      props: {},
      children: {}
    }
  }
}

但是在这种情况下,如果您要重新渲染Container,则必须重新执行

React.createElement(
  Child, 
  {}, 
  {}
)

对于每个渲染这将导致在渲染之间引用不同的 React 元素,因此 React 实际上也会重新渲染Child组件,即使最终结果将是相同的。

参考

我想你不小心发布了两次相同的链接,这是你说它总是呈现孩子的那个。那是因为,您已将状态提升到 App 组件的级别。如果它的状态发生变化,它必须重新执行它返回的所有内容的函数(渲染)。因此,每次您键入内容时,其状态都会更新,因此 Inputfield 和 Button 每次都会重新呈现。尝试移动 Inputfield 组件中的状态,看看会发生什么 :) ( codesandbox.io/s/twilight-hooks-m65l7?file=/src/index.js )
2021-06-02 03:15:29
很好的解释!用“return React.Children.map(this.props.children, child=>React.isValidElement(child) ? React.cloneElement(child) : child)”替换“return this.props.children”迫使我的孩子们渲染父状态更新。
2021-06-09 03:15:29
只是好奇,如果我们使用这种组件组合,并假设我们不仅有一个子组件,会发生什么?我在这里试验了例子这正如预期的那样。但这一个我们有一个子组件冒泡事件,我们在输入字段父组件中更新状态,并且子组件按钮总是被渲染。这不应该每次都是相同的参考并做出反应 jsut skip 吗?
2021-06-19 03:15:29

<Child />组件没有重新渲染,因为props都没有改变。React 使用虚拟 DOM 的概念,它是您的组件及其数据的表示。

如果组件的 props 没有改变,则不会重新渲染组件。这就是让 React 保持快速的原因。

在您的示例中,没有发送到 的propsChild,因此永远不会重新渲染。如果您希望它每次都重新渲染(为什么要这样做?),例如您可以使用纯函数

const Child = () => <h1>Hi</h1>;
一开始我也是这么认为的,但是你怎么解释初始渲染呢?当应用程序最初安装时,在呈现 Container 之后呈现 Child(请参阅控制台日志)。如果 Child 是在 App 中呈现的,那么在初始装载期间,“Child 呈现”应该出现在“Container 呈现”之前,不是吗?
2021-05-29 03:15:29
谢谢您的回答。据我了解,如果 setState() 被触发,Container 组件的渲染函数将被调用,所有子元素都应重新渲染。我可以使用 <Child/> 而不是 {this.props.children} 并且每次都会重新渲染,即使没有要更新的道具。检查示例
2021-06-08 03:15:29
<Child />由 呈现App,这就是为什么它在呈现时不会重新Container 呈现。Container 所做的一切- 它只返回传递的值。您可以通过执行触发重新渲染React.cloneElement(this.props.children)
2021-06-18 03:15:29

更改{this.props.children}<Child />在 Container 组件中,(现在您可以<Child />从 App 组件中删除)。

如果您单击 div,您将在控制台中同时获得“子渲染”和“容器渲染”。

(在这个例子中,你的孩子是静态组件。那么重新渲染是没有意义的。)

子组件内容是静态的(内容不会随时更改)。在这种情况下不需要重新渲染组件。但是,如果您使用的是从 Container 组件获得的一些值,通过 props 传递的值,那么您的 Child 需要重新渲染,并且随着时间的推移会发生变化。
2021-05-31 03:15:29
是的,我知道使用 <Child /> 而不是 {this.props.children} 的情况。仍然没有看到 JSX 中的差异,因为 this.props.children 将返回相同的 <Child/> 元素。您能否详细说明您说您的孩子是静态组件是什么意思
2021-06-18 03:15:29
感谢您的阐述。是的,我们可以称之为静态的。它仍然没有解释为什么当用作 <Child/> 而不是 {this.props.children} 时,它每次都会重新渲染。检查示例
2021-06-20 03:15:29