【问题标题】:React child components don't re-render when mapped from an array从数组映射时,React 子组件不会重新渲染
【发布时间】:2021-04-22 20:54:34
【问题描述】:

我正在根据用户输入按需加载一些反应组件(以及其他信息)。
要渲染的组件保存在一个数组中,渲染方法使用array.map 来包含组件。

问题是,如果我触发主应用组件的forceUpdate(),映射的组件将不会更新。

代码示例:https://codesandbox.io/s/react-components-map-from-array-ekfb7

【问题讨论】:

  • forceUpdate 不是你应该使用的东西,除非是非常特殊的情况。几乎可以肯定,这表明其他设置不正确。
  • 在状态中存储组件实例也是一种反模式。而是将标识符存储在状态中,并在您的 map 回调中创建实例。
  • 它是使用i18n-react 进行本地化的更大项目的一部分。如果用户更改语言,我会在应用根组件外触发forceUpdate 以更新所有翻译。
  • 只存储标识符会使整个回调更加复杂,因为要映射的组件将是不同的组件,并且可能需要额外的参数。但我会记住这一点。
  • 它不必是单个标识符,这通常就是您所需要的。如果您需要有关每个新组件的一组复杂信息,只需存储一个对象即可。我将在我的答案中添加一个示例。

标签: javascript arrays reactjs react-component


【解决方案1】:

日期没有更新,因为您正在 add 函数中创建组件的实例,从那时起您将引用该实例而不让 react 管理更新。

这就是为什么将组件实例存储在状态或其他变量中是一种反模式。

问题演示

下面我创建了一个仍然使用forceUpdate 的工作示例,只是为了证明问题所在。

请注意,我不是将组件置于状态,而是将其推入数组以增加其长度。然后 React 可以正确管理更新。

class TestComponent extends React.Component {
  render() {
    return <p>{Date.now()}</p>;
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);

    this.comps = [1];
  }

  add() {
    this.comps.push(1);
    this.forceUpdate();
  }

  render() {
    return (
      <div className="App">
        <h1>Components map example</h1>
        <p></p>
        <h2>Static TestComponent (ok):</h2>
        <TestComponent />
        <h2>TestComponents mapped from an array (not ok):</h2>
        {this.comps.map((comp, id) => {
          return <div key={id}><TestComponent /></div>;
        })}
        <h2>All should update when the App component renders</h2>
        <p>
          <button onClick={() => this.add()}>Add TestComponent</button>
          <button onClick={() => this.forceUpdate()}>forceUpdate App</button>
        </p>
      </div>
    );
  }
}

ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
这仍然是一个不太理想的解决方案。但它确实表明了问题所在。

更好的解决方案

如果您需要预先了解更多关于每个组件实例的信息,您可以使数组更复杂。

我还建议使用 state 来存储 comps 数组,并完全删除 forceUpdate

class TestComponent extends React.Component {
  render() {
    return <p>{Date.now()} {this.props.a} {this.props.b}</p>;
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      comps: [{ a: 'a', b: 'b' }]
    }
  }

  add = () => {
    // add your custom props here
    this.setState(prev => ({comps: [ ...prev.comps, { a: 'c', b: 'd' } ]}));
  }

  render() {
    return (
      <div className="App">
        <h1>Components map example</h1>
        <p></p>
        <h2>Static TestComponent (ok):</h2>
        <TestComponent />
        <h2>TestComponents mapped from an array (not ok):</h2>
        {this.state.comps.map((compProps, id) => {
          return <div key={id}><TestComponent {...compProps} /></div>;
        })}
        <h2>All should update when the App component renders</h2>
        <p>
          <button onClick={() => this.add()}>Add TestComponent</button>
        </p>
      </div>
    );
  }
}

ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

现在请注意,map 回调中的每个组件都可以根据您的逻辑拥有自己独特的一组道具。但是应该重新渲染的部分会正确地这样做。

【讨论】:

  • 我认为您的“更好的解决方案”几乎就是我所需要的。如果我也存储组件类引用,那么我可以映射具有不同属性的多个不同组件类型。这是我的意思的working example。谢谢!
【解决方案2】:

为了在 React 中更新,你必须把你的数据放在 state 中,然后 setState

setState() 计划更新组件的状态对象。当状态改变时,组件通过重新渲染来响应,这意味着用新的状态更新屏幕。

import React from "react";
import "./styles.css";

class TestComponent extends React.Component {
  render() {
    return <p>{Date.now()}</p>;
  }
}

export class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      comps: [<TestComponent />],
    }
  }

  add = () => {
    this.setState({ comps: this.state.comps.concat(<TestComponent />) })
  }

  render() {
    return (
      <div className="App">
        <h1>Components map example</h1>
        <p></p>
        <h2>Static TestComponent (ok):</h2>
        <TestComponent />

        <h2>TestComponents mapped from an array (not ok):</h2>
        {
          this.state.comps.map((comp, id) => {
            return <div key={id}>{comp}</div>;
          })
        }

        <h2>All should update when the App component renders</h2>
        <p>
          <button onClick={this.add}>Add TestComponent</button>
        </p>
      </div>
    );
  }
}

【讨论】:

  • 即使我使用您描述的状态,映射的 TestComponents 也不会更新。请参阅codesandbox.io/s/react-components-map-from-array-forked-ru5zj 并单击“forceUpdate App”按钮。
  • 为什么需要使用forceUpdate?为什么不直接使用setState
  • 他想知道为什么映射的 TestComponents 没有更新
  • 它是使用i18n-react 进行本地化的更大项目的一部分。如果用户更改语言,我会在应用程序根组件外触发forceUpdate 以更新所有翻译。它工作正常,预计映射的组件不会更新。
猜你喜欢
  • 2020-02-13
  • 2023-03-13
  • 2018-08-12
  • 2020-04-18
  • 2020-05-01
  • 2023-03-28
  • 2019-02-26
  • 2019-05-20
  • 1970-01-01
相关资源
最近更新 更多