setState不会立即更新状态

169

我想问一下,为什么我的状态在 onClick 事件中没有改变。我之前搜索过,需要在构造函数中绑定 onClick 函数,但是状态仍然没有更新。

这是我的代码:

import React from 'react';
import Grid from 'react-bootstrap/lib/Grid';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import BoardAddModal from 'components/board/BoardAddModal.jsx';    
import style from 'styles/boarditem.css';

class BoardAdd extends React.Component {
    constructor(props) {
        super(props);    
        this.state = {
            boardAddModalShow: false
        };    
        this.openAddBoardModal = this.openAddBoardModal.bind(this);
    }

    openAddBoardModal() {
        this.setState({ boardAddModalShow: true }); // set boardAddModalShow to true

        /* After setting a new state it still returns a false value */
        console.log(this.state.boardAddModalShow);   
    }

    render() {    
        return (
            <Col lg={3}>
                <a href="javascript:;" 
                   className={style.boardItemAdd} 
                   onClick={this.openAddBoardModal}>
                    <div className={[style.boardItemContainer,
                                     style.boardItemGray].join(' ')}>
                        Create New Board
                    </div>
                </a>
            </Col>
        );
    }
}

export default BoardAdd

10
你接受的这个答案在这个问题上没有意义。setState函数不会返回一个Promise。如果它起作用了,只是因为await将一个异步“tick”引入了该函数,并且恰好在那个“tick”期间处理了状态更新。这并不是一定保证的。正如此回答所说,你需要使用完成回调(如果确实需要在状态更新后执行某些操作,这很少见;通常,你只想重新渲染,这会自动发生)。请注意,原文中的"which is unusual; normally, you just want to re-render" 这句话表示:“这很少见;通常,您只需要重新渲染”。 - T.J. Crowder
3
如果您取消接受当前被接受的答案或接受正确答案,这将有助于许多其他问题的重复使用。将错误答案置于首位会误导他人,因此请您考虑更改。 - Guy Incognito
15个回答

215

你的状态需要一些时间来变异,由于 console.log(this.state.boardAddModalShow) 在状态变异之前执行,因此你会得到先前的值作为输出。因此,你需要在 setState 函数的回调中编写控制台。

openAddBoardModal() {
  this.setState({ boardAddModalShow: true }, function () {
    console.log(this.state.boardAddModalShow);
  });
}

setState是异步的。这意味着你不能在一行代码中调用它并假定状态已在下一行中更改。

根据React文档

setState()不会立即更改this.state,而是创建了一个待处理的状态转换。在调用此方法后访问this.state可能会潜在地返回现有值。不存在调用setState和调用之间同步操作的保证,调用可能会被批处理以获得性能提升。

为什么要使setState异步

这是因为setState改变状态并导致重新渲染。这可能是一个昂贵的操作,将其同步可能会使浏览器无响应。

因此,setState调用也是异步的,并且进行了批处理以获得更好的UI体验和性能。


到目前为止,这是一个很好的想法,但是如果我们不能使用console.log因为您正在使用eslint规则怎么办? - maudev
2
@maudev,eslint规则会阻止您使用console.logs,以避免它们最终出现在生产环境中,但上述示例纯粹用于调试。console.log可以被替换为一个考虑到更新状态的操作。 - Shubham Khatri
1
如果回调函数不像你说的那样工作怎么办?我的没有! - Ghasem
1
@VaibhavSidapara 请查看帖子。如果有帮助,请告诉我。 - Shubham Khatri
1
@ShubhamKhatri 谢谢,我昨天解决了。我使用了相同的 useEffect 钩子。 - Vaibhav Sidapara
显示剩余3条评论

59

幸运的是setState()带有一个回调函数,这正是我们获得更新后的状态的地方。

考虑下面这个例子。

this.setState({ name: "myname" }, () => {                              
        //callback
        console.log(this.state.name) // myname
      });

当回调函数被触发时,this.state 是更新后的状态。
您可以在回调函数中获取到变异/更新后的数据。


1
您还可以使用此回调函数来传递任何函数,例如 initMap() - Dende
1
太棒了!终于解决了我的问题。非常感谢。 - Ahmet Halilović
1
这在ReactJS函数组件中会发出警告。 - Gopal Mishra
不要依赖状态,它会更新状态并通过变量传递更新值。let categories = [...selectedCategory, value] setSelectedCategory(categories); setInputValue(categories)使用这段代码,您将获得更新的值,因为您不依赖于状态。 - santu prajapati

21

如果有人想使用钩子来完成这个操作,你需要使用 useEffect

function App() {
  const [x, setX] = useState(5)
  const [y, setY] = useState(15) 

  console.log("Element is rendered:", x, y)

  // setting y does not trigger the effect
  // the second argument is an array of dependencies
  useEffect(() => console.log("re-render because x changed:", x), [x])

  function handleXClick() {
    console.log("x before setting:", x)
    setX(10)
    console.log("x in *line* after setting:", x)
  }

  return <>
    <div> x is {x}. </div>
    <button onClick={handleXClick}> set x to 10</button>
    <div> y is {y}. </div>
    <button onClick={() => setY(20)}> set y to 20</button>
  </>
}

输出:

Element is rendered: 5 15
re-render because x changed: 5
(press x button)
x before setting: 5
x in *line* after setting: 5
Element is rendered: 10 15
re-render because x changed: 10
(press y button)
Element is rendered: 10 20

现场版本


16

由于 setSatate 是一个异步函数,因此您需要像这样将状态作为回调打印到控制台。

openAddBoardModal(){
    this.setState({ boardAddModalShow: true }, () => {
        console.log(this.state.boardAddModalShow)
    });
}

8

setState() 并不总是立即更新组件。它可能会批处理或延迟更新到稍后。这使得在调用 setState() 后立即读取 this.state 成为一个潜在的陷阱。相反,使用 componentDidUpdate 或者 setState 回调函数(setState(updater, callback)),它们都保证在更新应用后触发。如果需要基于先前的状态设置状态,请查看下面的更新程序参数。

setState() 总是会导致重新渲染,除非 shouldComponentUpdate() 返回 false。如果使用可变对象且无法在 shouldComponentUpdate() 中实现条件渲染逻辑,则仅当新状态与先前状态不同时调用 setState() 可以避免不必要的重新渲染。

第一个参数是一个带有以下签名的更新程序函数:

(state, props) => stateChange

state是指在应用更改时组件状态的引用。它不应该直接被修改。相反,更改应该通过根据来自状态和属性的输入构建新对象来表示。例如,假设我们想通过props.step将状态中的值增加:

this.setState((state, props) => {
    return {counter: state.counter + props.step};
});

4
setState()视为一个更新组件的请求,而不是立即执行的命令。为了获得更好的性能,React可能会延迟这个请求,并在单个过程中更新多个组件。React不能保证状态更改会立即生效。 更多信息请查看此链接
在您的情况下,您已发送了更新状态的请求。React需要时间来响应。如果您尝试立即console.log该状态,则会获得旧值。

3
以上解决方案不适用于useState hooks。 可以使用以下代码。
setState((prevState) => {
        console.log(boardAddModalShow)
        // call functions
        // fetch state using prevState and update
        return { ...prevState, boardAddModalShow: true }
    });

这对我来说毫无意义。我无法将其翻译到我的应用程序中。我想要做的就是立即 setMyVariable(false) 这个钩子变量。为什么这样很难? - velkoon

2
虽然有很多好的答案,但如果有人在搜索与用户输入相关的UI组件的替代方案时着陆在此页面上,这个答案将会很有用。
虽然useState似乎是一种方便的方法,但状态不会立即设置,因此您的网站或应用程序看起来很卡...而且如果您的页面足够大,React需要花费很长时间来计算在状态更改时应该更新什么...
我的建议是使用refs,并在您想要UI立即响应用户操作时直接操作DOM。
在React中,为此目的使用状态确实是一个糟糕的想法。

2

如果您想追踪状态是否正在更新,则另一种方法是

_stateUpdated(){
  console.log(this.state. boardAddModalShow);
}

openAddBoardModal(){
  this.setState(
    {boardAddModalShow: true}, 
    this._stateUpdated.bind(this)
  );
}

以这种方式,您可以在调试时每次尝试更新状态时调用方法“_stateUpdated”。

2

这个回调函数非常混乱。最好使用 async/await 代替:

async openAddBoardModal(){
    await this.setState({ boardAddModalShow: true });
    console.log(this.state.boardAddModalShow);
}

61
这没有意义。React的 setState 函数不返回一个Promise。 - T.J. Crowder
24
@T.J.Crowder是正确的。setState不返回一个Promise,所以不应该使用await等待它完成。尽管如此,我认为我能理解一些人为什么这样做可以工作,因为awaitsetState的内部操作放在函数调用栈中优先于其余部分处理,这样看起来就像状态已经设置了一样。如果setState有或实现了任何新的异步调用,这个答案将失败。要正确实现这个功能,你可以使用这个:await new Promise(resolve => this.setState({ boardAddModalShow: true }, () => resolve())) - Mike Richards
我发现了这个问题,但是这个解决方案对我没有用,我还没有解决我的问题,但我在这里找到了一个好的阅读材料:https://ozmoroz.com/2018/11/why-my-setstate-doesnt-work/ - carmolim
1
@OndřejŠevčík 很可能是竞态条件让它看起来工作了,但它可能会在未来某一天再次出现问题。 - Emile Bergeron
@Rupak 你需要使用Hooks。 - Spock
显示剩余3条评论

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