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

ReactJS:映射组件的构造函数调用了多少次?,reactjs,constructor,components,render,array.prototype.map,Reactjs,Constructor,Components,Render,Array.prototype.map,我有一个React组件,它在另一个组件中使用Array.prototype.map多次渲染 第一个组件的构造函数应该被调用多少次?我期望的是映射数组的长度的倍,但似乎只调用了一次 在我的特定代码中,它是在最后一次渲染之前调用的 示例代码: class ComponentB extends Component { constructor(props) { super(props) // added upon first reply this.handleObjectAdd =

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

第一个组件的构造函数应该被调用多少次?我期望的是映射数组的长度的倍,但似乎只调用了一次

在我的特定代码中,它是在最后一次渲染之前调用的

示例代码:

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>
    )
  }
}
此外,无论数组元素的数量如何,构造函数的日志行始终显示在最后一个构造函数日志行之前

为什么日志输出如上所述?如果您有任何见解,我将不胜感激


这被标记为的副本,但它不是。

我理解您的问题,即为什么当我将新元素放在数组的最开始处时,似乎为最后一个元素创建了新的React组件

原因是要使用索引的键

基于文档中的logiccheck部分,因为只有最后一个元素(新索引为oldLength+1)具有唯一的键,所以它将从头开始创建。而所有其他的都只是重新渲染和更新。换句话说,您的代码更新N-1个元素,而不是只创建1个新元素,而保留所有其他元素不变

要处理这个问题,您不应该依赖于键中的索引,而应该使用其他一些可预测的、稳定的和unqiue值。在你的情况下,这是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>
    )
  }
}
您的初始代码几乎没有语法错误,而变异状态directlyn从未这样做过,所以它在单击按钮后从未重新呈现。下次发帖时请检查代码,这样会使理解/回答更容易

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

而不是仅仅创建一个新的,并保持所有其他不变


React不尝试最小化操作计数。在比较[1,2,3]和[0,1,2,3]时,有两种可能的方法:要么“在开始时插入0,移动所有其他元素”,要么“将所有元素减量1,并在结束时额外插入3”。如果您提供良好的属性作为关键,React将选择第一个解决方案。但让key={index}表示“反应,使用第二种方法,我知道我在做什么”。React不会分析调用.setState之前运行的代码,它只依赖于键值。

谢谢您的回答。我的错误是发布了一个较旧的代码,后来我修复了实际代码中的变异状态部分。我也很感谢您对这些键的解释,这加强了我对列表和键的理解。你回答了为什么只有一次调用构造函数,我很难理解为什么没有重复调用它。我的另一个问题是:当我将新元素添加到列表的开头时,为什么会在第二个元素到最后一个元素时调用它?我将在我的初始代码中保留错误。看,React不会尝试最小化操作计数。在比较[1,2,3]和[0,1,2,3]时,有两种可能的方法:要么“在开始时插入0,移动所有其他元素”,要么“将所有元素减量1,并在结束时额外插入3”。如果您提供良好的属性作为关键,React将选择第一个解决方案。但让key={index}表示“反应,使用第二种方法,我知道我在做什么”。React不会分析调用之前运行的代码。setState,它只依赖于键值。我想我现在理解了为什么在最后一次渲染之前调用它:最后一次渲染调用实际上是针对列表开头添加的最后一个元素,在本例中,不是针对列表中存在的最后一个元素。所以,我认为React调用现有元素的呈现,然后调用新元素的构造函数,然后调用新元素的呈现。没错。最后一个是创建的,下一个是呈现的。您对操作的最后评论对于理解有关键和索引的文档非常有帮助。谢谢
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>
    )
  }
}