ReactJS:映射组件的构造函数被调用了多少次?

ReactJS: How many times is a mapped component's constructor called?

我有一个 React 组件,它在另一个组件中使用 Array.prototype.map() 多次渲染。

第一个组件的构造函数应该被调用多少次?我期待与映射数组的长度一样多的次数,但它似乎只被调用了一次。

在我的特定代码中,它是在最后一个 render() 之前调用的。

示例代码:

class ComponentB extends Component {
  constructor(props) {
    super(props) // added upon first reply
    this.handleObjectAdd = this.handleObject.bind(this);
    this.state.objects = [];
  }

  handleObjectAdd() {
    this.state.objects.unshift({prop1: this.state.objects.length + 1});
  }

  render() {
      return (
        <div>
          <button onClick={this.handleObjectAdd}>ADD</button>
          { this.state.objects.map((object, index) =>
              <ComponentA key={index} details={object}/>
            )
          }
        </div>
      )
    })
  }
}

class ComponentA extends Component {
  constructor(props) {
    super(props) // added upon first reply
    console.log('ComponentA constructor called');
  }
  render() {
    console.log('ComponentA render() called');
    return (
      <input type="text" value={this.props.details.prop1}></input>
    )
  }
}

对于 5 个元素的数组(所有 ComponentA 实例),我得到以下行:

ComponentA render() called
ComponentA render() called
ComponentA render() called
ComponentA render() called
ComponentA constructor called
ComponentA render() called

此外,构造函数的日志行始终出现在最后一个构造函数日志行之前,无论数组元素的数量如何。

为什么日志输出如上?将不胜感激。

这被标记为 React Rerender dynamic Components (Solr) 的副本,但它不是。

我理解你的问题 "why when I put new element to the very start of array it seems new React component is created for the very last element?"

原因是 keyindex 一起使用。

基于其逻辑(检查文档中的 Lists and Keys 部分)因为只有最后一个元素(新索引为 oldLength + 1)具有唯一性 key 只有它会从头开始创建。而所有其他人只是重新渲染和更新。换句话说,您的代码更新 N - 1 元素,而不是仅仅创建 1 个新元素并保持所有其他元素不变。

要解决这个问题,您不应依赖 key 中的 index,而应使用其他一些可预测的、稳定的和独特的值。在你的例子中是 prop1。然后将为第一个元素调用构造函数。

这是更新版本

class ComponentB extends Component {
  constructor(props) {
    super(props) // added upon first reply
    this.state = {
      objects: []
    };
  }

  handleObjectAdd = () => {
    this.setState(oldState => ({
      objects: [
          {prop1: this.state.objects.length + 1}, 
          ...oldState.objects
      ]
    }))
  }


  render() {
    return (
      <div>
        <button onClick={this.handleObjectAdd}>ADD</button>
        {this.state.objects.map(obj =>
          <ComponentA key={obj.prop1} details={obj} />
        )}
      </div>
    )
  }
}

class ComponentA extends Component {
  constructor(props) {
    super(props) // added upon first reply
    console.log('ComponentA constructor called');
  }
  render() {
    console.log('ComponentA render() called');
    return (
      <input type="text" value={this.props.details.prop1}></input>
    )
  }
}

您的初始代码几乎没有语法错误,并且直接改变了状态(永远不要那样做),因此它在单击按钮后永远不会重新呈现。下次 post 提问时请检查代码 - 这会让 understanding/answering 更容易。

[UPD] 从下面的评论中提取了一些片段

instead of just creating 1 new and leaving all other unchanged.

React 不会尝试最小化操作数。在比较 [1,2,3] 和 [0, 1, 2, 3] 时,有两种可能的方式:'insert 0 at the beginning, shifting everything else' 或 'decrement all the elements by 1 and additionally insert 3 at the end'。如果您提供良好的 属性 作为关键,React 会选择第一个解决方案。但是如果有 key={index} 你实际上是在说“反应,使用第二种方法,我知道我在做什么”。在调用 .setState 之前,React 不会分析您 运行 的代码,它只依赖键值。