React组件在状态改变时未重新渲染

95
我有一个React类,它会访问API以获取内容。我已经确认数据已经返回,但它没有重新渲染:
var DealsList = React.createClass({
  getInitialState: function() {
    return { deals: [] };
  },
  componentDidMount: function() {
    this.loadDealsFromServer();
  },
  loadDealsFromServer: function() {
    var newDeals = [];

    chrome.runtime.sendMessage({ action: "findDeals", personId: this.props.person.id }, function(deals) {
      newDeals = deals;
    });

    this.setState({ deals: newDeals });
  },
  render: function() {
    var dealNodes = this.state.deals.map(function(deal, index) {
      return (
        <Deal deal={deal} key={index} />
      );
    });
    return (
      <div className="deals">
        <table>
          <thead>
            <tr>
              <td>Name</td>
              <td>Amount</td>
              <td>Stage</td>
              <td>Probability</td>
              <td>Status</td>
              <td>Exp. Close</td>
            </tr>
          </thead>
          <tbody>
            {dealNodes}
          </tbody>
        </table>
      </div>
    );
  }
});
然而,如果我像下面这样添加一个debuggernewDeals被填充了,然后一旦我继续,我就可以看到数据:
  loadDealsFromServer: function() {
    var newDeals = [];

    chrome.runtime.sendMessage({ action: "findDeals", personId: this.props.person.id }, function(deals) {
      newDeals = deals;
    });
    debugger
    this.setState({ deals: newDeals });
  },

这是调用“deals”列表的内容:

var Gmail = React.createClass({
  render: function() {
    return (
      <div className="main">
        <div className="panel">
          <DealsList person={this.props.person} />
        </div>
      </div>
    );
  }
});
12个回答

138

我的情景有点不同。我认为像我这样的新手会感到困惑 - 所以在这里分享。

我的状态变量是一个由useState管理的JSON对象数组,如下所示:

const [toCompare, setToCompare] = useState([]);

然而,当在下面的函数中将toCompare更新为setToCompare时,重新渲染不会触发。将其移动到另一个组件也没有起作用。只有当其他事件触发重新渲染时,更新后的列表才会显示出来。
const addUniversityToCompare = async(chiptoadd) =>
  {
      var currentToCompare = toCompare;
      currentToCompare.push(chiptoadd);
      setToCompare(currentToCompare);
  }

这对我来说是解决方案。基本上,分配数组是复制引用,React 不会将其视为更改,因为数组的引用未更改,只有其中的内容更改。因此,在下面的代码中,仅使用 slice 复制了数组,没有进行任何更改,在修改后重新分配给它。完全正常工作。
const addUniversityToCompare = async (chiptoadd) => {
    var currentToCompare = toCompare.slice();
    currentToCompare.push(chiptoadd);
    setToCompare(currentToCompare);
}

希望对像我这样的人有所帮助。任何人,请告诉我如果你觉得我错了-或者还有其他方法。


16
哇,我的问题几乎是一模一样的。谢谢! - stevejboyer
35
我也是。在ES6中,我们可以使用扩展运算符来克隆状态,以促使React意识到状态的更改:let newItems = [...items]; - S Meaden
是的,Meaden,你说得对。我同意。你觉得更新我的解决方案并包含这一点作为更好的方式,让人们使用它作为更通用的方法来解决这个问题,这样有意义吗?很高兴能帮忙。 - KManish
这里也有抄袭问题。好笑! - Crashtor
1
在这里做个复制品!等等,在最后,我们总是从别人那儿复制代码,包括他们的问题 ;) - Nelson Garcia
显示剩余6条评论

113

我想补充的是一个非常简单但很容易犯错的错误,即:

this.state.something = 'changed';

...然后不明白为什么它没有渲染,于是搜索并进入这个页面,才意识到应该写成:

this.setState({something: 'changed'});

只有在使用setState更新状态时,React才会触发重新渲染。


1
这正是我遇到的确切问题。很奇怪,因为当尝试更新props时,它们会抛出警告,但在这种情况下却没有。 - AndrewJM
3
@AndrewJM 他们无法发出警告。如果您编写了this.state ='something',则可以,因为您将触发state的setter,但在上面的示例中,代码会触发getter,该getter返回一个对象,然后它最终会在仅为状态副本的对象上设置字段。 - Stijn de Witt
1
@TheBigCheese 最好写一个(好的)问题来描述你的问题。这里有一些可以帮助你的人,但并不是在另一个问题的评论中。 - Stijn de Witt
2
@StijndeWitt 我登录Stackoverflow给你的答案点赞了。谢谢兄弟 ;D - Esterlinkof
1
初学者理解和记忆的非常重要的概念。 - invinciblemuffi
显示剩余2条评论

29

这是因为来自chrome.runtime.sendMessage的响应是异步的;以下是操作顺序:

var newDeals = [];

// (1) first chrome.runtime.sendMessage is called, and *registers a callback*
// so that when the data comes back *in the future*
// the function will be called
chrome.runtime.sendMessage({...}, function(deals) {
  // (3) sometime in the future, this function runs,
  // but it's too late
  newDeals = deals;
});

// (2) this is called immediately, `newDeals` is an empty array
this.setState({ deals: newDeals });

当您使用调试器暂停脚本时,您为扩展程序提供了调用回调函数的时间;当您继续执行时,数据已经到达并且似乎可以正常工作。
为了修复此问题,您需要在Chrome扩展返回数据后进行setState调用。
var newDeals = [];

// (1) first chrome.runtime.sendMessage is called, and *registers a callback*
// so that when the data comes back *in the future*
// the function will be called
chrome.runtime.sendMessage({...}, function(deals) {
  // (2) sometime in the future, this function runs
  newDeals = deals;

  // (3) now you can call `setState` with the data
  this.setState({ deals: newDeals });
}.bind(this)); // Don't forget to bind(this) (or use an arrow function)

如果这对你不起作用,请查看此问题的其他答案,这些答案解释了您的组件可能无法更新的其他原因。

天啊!当调试器行修复它时,我应该知道这就是情况。当我第一次尝试时,我错过了绑定(this)的事情。感谢您详细的评论! - brandonhilkert

19

还有一个非常容易犯的错误,这也是我遇到问题的根源:我编写了自己的 shouldComponentUpdate 方法,但它没有检查我添加的新状态变化。


15
为了正确更新状态,您不应该改变数组本身。您需要创建一个数组的复制品,然后使用复制品来设置状态。请注意不要直接更改原始数组。
const [deals, setDeals] = useState([]);
    
   function updateDeals(deal) {
      const newDeals = [...deals]; // spreading operator which doesn't mutate the array and returns new array
      newDeals.push(deal);

      // const newDeals = deals.concat(deal); // concat merges the passed value to the array and return a new array
      // const newDeals = [...deals, deal] // directly passing the new value and we don't need to use push
    
      setDeals(newDeals);
    }

为什么直接变异不会重新渲染组件? - Thamaraiselvam
@Thamaraiselvam 很可能是因为React只将对象视为指针。因此,在指针更改之前,React无法识别对象已更改。 - Søren Jensen
这是最合适的答案。 - Delalie Rawllings
这对我有效。最恰当的回答。 - Salik Mohammad

10
在我的情况下,我正确地调用了 this.setState({}),但是我的函数没有绑定到this上,所以它没有起作用。在函数调用中添加 .bind(this) 或在构造函数中执行 this.foo = this.foo.bind(this) 可以解决这个问题。

我有这个问题。所有的函数都在构造函数中正确绑定。 - TheBigCheese

3

我的问题是我应该使用 'React.Component',而不是 'React.PureComponent'。


2

我正在更新并返回传递给我的reducer相同的对象。为了解决这个问题,我在返回状态对象之前制作了该元素的副本,就像这样。

Object.assign({}, state)

1
我在React-Native中遇到了同样的问题,即API响应和拒绝未更新状态。 apiCall().then((resp) => { this.setState({data: resp}) // 未更新 } 我通过将function更改为箭头函数来解决了这个问题。
apiCall().then((resp) => {
    this.setState({data: resp}) // rendering the view as expected
}

对我来说,这是一个绑定问题。使用箭头函数可以解决这个问题,因为箭头函数不会创建自己的this,它总是绑定到它所在的外部上下文中。


我遇到了这个问题,所有的内容都绑定在构造函数中,并且所有的承诺都有箭头函数。字段默认为空或零(数字)。当触发刷新时,它们会闪烁数值,然后变为零。但是问题是,当我在一个字段中输入内容时,它们全部都会刷新渲染并显示他们的值。 - TheBigCheese

0
如果有人遇到类似的问题,但是使用React函数组件而不是类组件,并且还使用了React Reducer,则应该将API调用移至Reducer之外。Reducer永远不应该进行API调用。请参考https://dev59.com/LFkS5IYBdhLWcg3wu4zL#39516485以获取详细响应。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接