调用 setState 后 ReactJS 元素未更新

Posted

技术标签:

【中文标题】调用 setState 后 ReactJS 元素未更新【英文标题】:ReactJS element not updating after setState being called 【发布时间】:2018-09-20 04:57:57 【问题描述】:

下午好, 我有一个响应 API 调用而动态呈现的 React 组件。我已将其中一个元素的值设置为组件内的状态。在 onClick 函数 (minusOne) 期间,这个值应该会改变。 该值最初根据状态成功渲染,该函数确实改变了状态,但是尽管状态发生变化,渲染的元素仍然保持不变。有没有人知道为什么会这样? 如果您有任何问题,请随时提出!

export class Cart extends React.Component 
constructor(props) 
    super(props);
    this.state=
        quantities: []
    ;
    this.minusOne = this.minusOne.bind(this);


minusOne(i) 
    var self = this;
    return function() 
        let quantities = self.state.quantities;
        if (quantities[i] > 1) 
            quantities[i] --;
        
        self.setState(
            quantities
        )
    

componentDidMount() 
    let cart = this.props.cartTotals;
    this.setState(
        cart
    );
    if(cart.lines) 
        let cartTotal = [];
        let quantities = [];
        for (var i = 0; i < cart.lines.length; i++) 
            if(cart.lines[i]) 
                quantities.push(cart.lines[i].quantity);
            
        

        //Initial setting of state
        this.setState(
            quantities
        )

        Promise.all(
            cart.lines.map(
                (cart, i) => axios.get('http://removed.net/article/' + cart.sku)
            )
        ).then(res => 
            const allCartItems = res.map((res, i) => 
                const data = res.data;
                return(
                    <div key=i className="cart-item-container">
                        <img className ="cart-item-picture" src=data.image name=data.name />
                        <div className="cart-item-description">
                            <p>data.name</p>
                            <p>data.price.amount data.price.currency</p>
                        </div>
                        <div className="cart-item-quantity">
                            <button onClick=this.minusOne(i) name="minus">-</button>



                            //This is the troublesome element
                            <p className="cart-current-quantity">this.state.quantities[i]</p>



                            <button name="plus">+</button>
                        </div>
                    </div>
                )
            )
            this.setState(
                allCartItems
            )
        )
    

render() 
    return (
                    this.state.allCartItems
    );
  

感谢阅读!任何建议都会有所帮助。

【问题讨论】:

尝试将你的 jsx 移动到 then 渲染方法,你的代码像这样不可读 【参考方案1】:

有两个问题:

    首先,您需要在render() 中进行渲染(包括onClick 所在的位置)。 ConponentDidMount 只被调用一次并且应该执行初始化但不渲染。 那么,minusOne 有问题:quantities 指向this.state.quantities。因此,您正在更改旧状态,React 会同时查看旧状态和新状态,发现没有任何变化,并且不会渲染,尽管值已更改。

如果您将 this.state.quantities 复制到一个新数组中,例如:

newQ = this.state.quantities.slice(0, -1);

然后修改newQ,然后做

this.setState( quantities: newQ  );

它应该可以工作。

【讨论】:

【参考方案2】:

我认为您不需要在 minusOne(i) 方法中返回函数。只需更新状态就足够了。您应该通过特定的 id 更改数组。 let quantities = self.state.quantities; let mutatedQuantities = quantities.map((el, index) => return (index === i) ? el - 1 : el; ) this.setState(quantities: [...mutatedQuantities])

【讨论】:

我返回该函数的原因是因为它在渲染时被调用。尝试一下您的解决方案,看来我遇到了同样的问题:(【参考方案3】:

--- 已编辑 --- 我删除了我之前写的所有内容以使其更简洁。

您的问题是您将要呈现的内容分配给componentDidMount 中的变量。这个函数只被调用一次,因此你只分配变量 allCartItems 一次。 setState 函数没有任何效果,因为它不会触发 componentDidMount,因此您的变量 allCartItems 不会被重新分配。

你能做什么?那么你可以做很多事情来增强你的代码。首先我会告诉你如何解决你的问题,然后再给你一些进一步的改进

要解决调用setState 时组件不更新的问题,您应该将jsx 移至render 方法。在componentDidMount 中,您只需获取渲染组件所需的所有数据,一旦拥有它,您就可以设置一个标志,例如准备好为真。您可以在下面看到一个代码示例。

import React from 'react';

class Cart extends React.Component 
  constructor(props) 
    super(props);
    this.state = 
      carts: null,
      ready: false,
    ;
  

  componentDidMount() 
    fetch('www.google.com').then((carts) => 
      this.setState(
        carts,
        ready: true,
      );
    );
  

  render() 
    const myCarts = <h2> Count this.state.carts </h2>;

    return (
      
        this.state.ready
        ? myCarts
        : <h2> Loading... </h2>
      
    );
  

我为您制作了一个带有简单计数器的演示,其中解释了您的案例以及如何使其发挥作用。您可以查看codesandbox。在 NotWorkingCounter 中,您可以看到与未更新的变量组件相同的问题。在 WorkingCount 中,您可以看到一个示例,在该示例中,我实现了我在上面写的内容,等待您的数据到达,然后才渲染它。

关于代码的更多建议:

下面这两种语法是相同的。一个更简洁。

class Cart extends React.Component 
  constructor(props) 
    super(props);
    this.state = 
      carts: null,
      ready: false,
    ;
  


class Cart extends React.Component 
    state = 
      carts: null,
      ready: false,
    

如果你想绑定你的上下文,我建议使用箭头函数。您可以在下面看到简化的示例以及如何以更少的语法实现相同目标的示例。

export class Cart extends React.Component 
  constructor(props) 
      super(props);
      this.minusOne = this.minusOne.bind(this);
  

  minusOne(i) 
    ///
  


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

  minusOne = (i) => 
    /// minus one function
  

如果你使用箭头函数并且要小很多,你的减号也可以被重写,在

  minusOne = (i) => (i) => 
    let quant = self.state.quantities[i];
    if(quant > 1) 
      this.setState(
        quantities: quant-1,
      )
    
  

在您的componentDidMount 中,您调用this.setState 两次。每次调用此函数时,您的组件都会重新渲染。所以在你的组件中发生的事情是当你挂载你的组件时它第一次被渲染,一旦它被挂载componentDidMount被调用,你在那里再次调用this.setState两次。这意味着在用户看到你的组件之前,你的组件在最好的情况下被渲染了 3 次。如果您收到多个承诺,这意味着您将重新渲染您的状态。这会给您的组件带来大量负载来应对。如果您将每个组件重新渲染 3 次或更多次,那么一旦应用程序增长,您最终会遇到一些性能问题。尽量不要在您的 componentDidUpdate 中多次调用 setState。 在您的情况下,您第一次致电 setState 是完全没有必要的,只会增加负载。您仍然可以访问您的承诺中的数量。只需在你的 promise.then() 结束时调用 setState 一次,同时使用这两个元素。

在下面的示例中,您使用索引 i 作为键。这不是一个好的案例实践,react 还应该在控制台中至少记录一个警告。您需要使用不是索引的唯一标识符。如果你使用索引,你会得到很难首次亮相的副作用和奇怪的渲染。阅读更多内容here

then(res => 
              const allCartItems = res.map((res, i) => 
                  const data = res.data;
                  return(
                      <div key=i className="cart-item-container">

另一个建议是将所有var 替换为constlet,因为var 将您的变量暴露给全局范围。如果您不明白这意味着什么,请阅读this。

最后但并非最不重要的是看看object deconstruction。它可以帮助您清理代码并使其更能抵抗不必要的副作用。

【讨论】:

您好,谢谢您的回复。好的,我想我明白你的意思,我已经检查了你提供的演示,它看起来确实是同一个问题。这个“CounterComponent”在哪里,因为我没有看到您演示的解决方案?目前我正在尝试重构我的代码,但是 componentWillReceiveProps 函数在生命周期的任何阶段都没有触发。 我编辑了我的答案。我希望这会对你有所帮助。我还添加了一些关于如何改进代码样式的提示。如果您有任何问题,请发表评论。

以上是关于调用 setState 后 ReactJS 元素未更新的主要内容,如果未能解决你的问题,请参考以下文章

ReactJS 状态未更新在 setState 回调中

在 setState 不起作用后,Reactjs 将值作为道具传递

ReactJs:获取无法读取未定义错误的属性“setState”[重复]

承诺解决后未触发reactjs render() this.setState 被重新分配

ReactJS TypeError:无法读取未定义的属性“setState”[重复]

Axios ReactJS - 无法读取未定义的属性“setState”