父状态更改后,ReactJs的子项未更新

xqk2d5yq  于 2022-10-15  发布在  React
关注(0)|答案(1)|浏览(166)

我正在尝试使用ReactJS构建一个动态组件。因此,我有一个产品列表,每次用户想要添加新产品时,他都会单击+按钮。这将创建一个新产品并将其添加到列表中。该列表有一个名为Delete All的按钮,用于删除所有产品。
要删除所有产品,我在组件创建时使用了产品列表中初始化为“Created”的状态,当用户单击“Delete All”时,我将其更改为“Delete”,并使用ComponentWillUpdate回调更新Childs(产品)。
以下是一些代码:
Index.js

render() {
    return (
      <div className="App">
        <h1>Dynamic List</h1>
        <ProductList />
        <hr />
        <h1>Static List</h1>
        <ProductListStatic />
      </div>
    );
  }

Product.js

class Product extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      state: "created"
    };
  }

  componentDidUpdate(prevProps) {
    if (
      this.props.parentState === "deleted" &&
      this.state.state === "created"
    ) {
      this.setState({ state: "deleted" });
    }
  }

  render() {
    if (this.state.state !== "deleted") {
      return (
        <div style={{ margin: "5px" }}>
          Product.....
          <button onClick={this.props.addNewProduct}>+</button>
        </div>
      );
    } else {
      return null;
    }
  }
}

我的ProductList.js

class ProductList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      products: [],
      state: "created"
    };
  }

  componentDidMount() {
    let newProducts = [...this.state.products];
    newProducts.push(
      <Product
        key={0}
        addNewProduct={this.addNewProduct}
        parentState={this.state.state}
      />
    );
    this.setState({ products: newProducts });
  }

  addNewProduct = () => {
    let length = this.state.products.length;
    const newProducts = [
      ...this.state.products,
      <Product
        key={length}
        addNewProduct={this.addNewProduct}
        parentState={this.state.state}
      />
    ];
    this.setState({ products: newProducts });
  };

  deleteAll = () => {
    this.setState({ state: "deleted" });
  };

  render() {
    return (
      <div>
        {this.state.products}
        <button style={{ marginTop: "20px" }} onClick={this.deleteAll}>
          Delete All
        </button>
      </div>
    );
  }
}

好吧,这并没有更新蔡尔兹夫妇,我不知道为什么。我认为这是因为这份名单是动态的。我尝试了一下使用静态列表,它运行得很好(代码如下)。
ProductListStatic.js

class ProductListStatic extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      products: [],
      state: "created"
    };
  }

  deleteAll = () => {
    console.log("delete all");
    this.setState({ state: "deleted" });
  };

  render() {
    let products = [
      <Product
        key={0}
        addNewProduct={this.addNewProduct}
        parentState={this.state.state}
      />,
      <Product
        key={1}
        addNewProduct={this.addNewProduct}
        parentState={this.state.state}
      />
    ];
    return (
      <div>
        {products}
        <button style={{ marginTop: "20px" }} onClick={this.deleteAll}>
          Delete All
        </button>
      </div>
    );
  }
}

以下是一个演示:SandBox live dome
有人知道为什么它不适用于动态产品吗?

iszxjhcz

iszxjhcz1#

问题是状态数组中的JSX元素不知道在父数组重新呈现时需要重新呈现它们。在静态版本中,子对象是直接在JSX中创建的,因此重新渲染的行为是正常的。将子对象的data存储在状态数组中,然后在每次呈现时通过在该状态数组上调用.map来创建它们是一个合理的解决方案。
除此之外,总体设计似乎不必要地复杂。我不认为子对象Product应该关心它们何时呈现,或者是否应该通过子对象的回调将产品添加到父对象中。将所有这些信息保存在家长中要简单得多。呈现可以是隐式的--如果我们想要一个新项目,将它推到产品/项目数组上,如果我们想清除所有项目,则将数组设置为[]。不需要this.state.state
这里有一个概念验证:

const mockProduct = () => ({
  name: [
    "apple", "banana", "cherry", "kiwi",
    "watermelon", "pineapple", "blueberry"
  ][~~(Math.random()*7)],
  price: ~~(Math.random() * 5) + 0.99
});

const Product = props => 
  <div>
    <span>{props.name}</span>
    <span>{props.price}</span>
  </div>
;

class Products extends React.Component {
  constructor(props) {
    super(props);
    this.state = {products: [mockProduct()]};
    this.addNewProduct = this.addNewProduct.bind(this);
    this.deleteAll = this.deleteAll.bind(this);
  }

  addNewProduct() {
    this.setState(state => ({
      products: state.products.concat(mockProduct())
    }));
  }

  deleteAll() {
    this.setState({products: []});
  }

  render() {
    return (
      <React.Fragment>
        <div>
          {this.state.products.map((e, i) =>
            <div 
              style={{display: "flex"}} 
              key={`${i}-${e.name}`} 
            >
              <Product name={e.name} price={e.price} />
              <button onClick={this.addNewProduct}>+</button>
            </div>
          )}
        </div>
        <div>
          <button onClick={this.deleteAll}>delete all</button>
        </div>
      </React.Fragment>
    );
  }
}

ReactDOM.createRoot(document.querySelector("#app"))
  .render(<Products />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

如果您计划为每个产品添加其他按钮和数据,则可以使用行容器。然后,当更改应该应用于项时,行将需要回调来通知父级。这取决于您的设计目标,在这一点上还为时过早(在从Product中消除了渲染方面的顾虑之后,就没有多少东西可以证明它的存在了)。

相关问题