【问题标题】:State updates might be asynchronous状态更新可能是异步的
【发布时间】:2017-07-21 23:43:20
【问题描述】:

它们到底是什么意思?如果我正确理解it,则在计算新状态时不能使用this.state,除非我将函数作为第一个参数传递给setState()

// Wrong
this.setState({a: f(this.state)});

// Correct
this.setState(prevState => {return {a: f(prevState)}});

但我可以使用this.state 来决定要做什么:

if (this.state.a)
    this.setState({b: 2});

道具呢?

// Correct or wrong?
this.setState({name: f(this.props)});

据说我不能指望this.state 在调用this.setState 后会改变:

this.setState({a: 1});
console.log(this.state.a);   // not necessarily 1

然后,假设我有一个用户列表。还有一个我可以让一个用户成为当前用户的选择:

export default class App extends React.Component {
    ...

    setCurrentUserOption(option) {
        this.setState({currentUserOption: option});
        if (option)
            ls('currentUserOption', option);
        else
            ls.remove('currentUserOption');
    }

    handleAddUser(user) {
        const nUsers = this.state.users.length;
        this.setState(prevState => {
            return {users: prevState.users.concat(user)};
        }, () => {
            // here we might expect any number of users
            // but if first user was added, deleted and added again
            // two callbacks will be called and setCurrentUserOption
            // will eventually get passed a correct value

            // make first user added current
            if ( ! nUsers)
                this.setCurrentUserOption(this.userToOption(user));
        });
    }

    handleChangeUser(user) {
        this.setState(prevState => {
            return {users: prevState.users.map(u => u.id == user.id ? user : u)};
        }, () => {
            // again, we might expect any state here
            // but a sequence of callback will do the right thing
            // in the end

            // update value if current user was changed
            if (_.get(this.state, 'currentUserOption.value') == user.id)
                this.setCurrentUserOption(this.userToOption(user));
        });
    }

    handleDeleteUser(id) {
        this.setState(prevState => {
            return {users: _.reject(prevState.users, {id})};
        }, () => {
            // same here

            // choose first user if current one was deleted
            if (_.get(this.state, 'currentUserOption.value') == id)
                this.setCurrentUserOption(this.userToOption(this.state.users[0]));
        });
    }

    ...
}

在应用批量更改状态后,所有回调是否都按顺序执行?

再想一想,setCurrentUserOption 基本上就像setState。它将更改排入this.state。即使按顺序调用回调,我也不能依赖 this.state 被先前的回调更改,可以吗?所以最好不要提取setCurrentUserOption方法:

handleAddUser(user) {
    const nUsers = this.state.users.length;
    this.setState(prevState => {
        let state = {users: prevState.users.concat(user)};
        if ( ! nUsers) {
            state['currentUserOption'] = this.userToOption(user);
            this.saveCurrentUserOption(state['currentUserOption']);
        }
        return state;
    });
}

saveCurrentUserOption(option) {
    if (option)
        ls('currentUserOption', option);
    else
        ls.remove('currentUserOption');
}

这样我就可以免费排队更改currentUserOption

【问题讨论】:

  • 仔细检查一下您是否使用了任何状态管理框架,例如 redux 或 Flux?答案可能会有所不同
  • 我要去。但现在我想让它在没有redux 的情况下工作。

标签: javascript reactjs asynchronous setstate


【解决方案1】:

你并没有真正提出一个非常具体的问题。 “这是什么意思”并不多说。但是您通常似乎了解基础知识。

有两种可能的方式调用setState():或者传递一个对象以合并到新状态,或者传递一个函数返回一个对象,该对象以类似于第一种方式的方式合并。

所以你要么这样做:

// Method #1
this.setState({foo: this.state.foo + 1}, this.someCallback);

或者这个:

// Method #2
this.setState((prevState) => {return {foo: prevState.foo + 1}}, this.someCallback);

主要区别在于,在方法 #1 中,foo 将根据您调用 setState() 时的状态而增加 1,而在方法 #2 中,@ 987654328@ 将根据箭头函数运行时的先前状态增加 1。因此,如果您有多个 setState() 调用在实际状态更新之前的“同一”时间发生,使用方法 #1 它们可能会发生冲突和/或基于过时的状态,而使用方法 #2 它们保证具有最新状态,因为它们在状态更新阶段一个接一个地同步更新。

这是一个说明性示例:


Method #1 JSBIN Example

// Method #1
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {n: 0};
    this.increment.bind(this);
  }
  componentDidMount() {
    this.increment();
    this.increment();
    this.increment();
  }
  increment() {
    this.setState({n: this.state.n + 1}, () => {console.log(this.state.n)});
  }
  render() {
    return (      
      <h1>{this.state.n}</h1>      
    );
  }
}

React.render(
  <App />,
  document.getElementById('react_example')
);

在上面:你会在控制台中看到这个:

> 1
> 1
> 1

this.state.n 的最终值将是1。当n 的值为0 时,所有setState() 调用都已入队,因此它们都只需将其设置为0 + 1


Method #2 JSBIN Example

// Method #2
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {n: 0};
    this.increment.bind(this);
  }
  componentDidMount() {
    this.increment();
    this.increment();
    this.increment();
  }
  increment() {
    this.setState((prevState) => {return {n: prevState.n + 1}}, () => {console.log(this.state.n)});
  }
  render() {
    return (      
      <h1>{this.state.n}</h1>      
    );
  }
}

React.render(
  <App />,
  document.getElementById('react_example')
);

在上面,您会在控制台中看到:

> 3
> 3
> 3

n 的最终值将是3。与方法 #1 一样,所有的 setState() 调用都同时加入队列。但是,由于它们使用一个函数来同步更新以使用最新状态(包括并发状态更新对状态所做的更改),它们正确地将 n 增加了三倍,正如您所期望的那样。


现在,为什么 console.log() 在方法 #2 中显示 3 三次,而不是 123?答案是setState() 回调在 React 的状态更新阶段结束时一起发生,而不是在特定状态更新发生后立即发生。所以在这方面方法 #1 和 #2 是相同的。

【讨论】:

  • 组件挂载并进入 DOM 后,increment() 被调用 3 次,每次调用 setState。既然每个 setState 都会修改状态,为什么渲染不被调用 3 次。我看到它最终只被调用一次。请指教。
  • @FarhanShirgillAnsari setState() 调用作为 React 内部循环的一部分在“批处理”中排队和处理。每次更改状态后都没有“渲染”,而是尽可能地一起处理它们。如果多个状态更新同时被同步触发,则可能只会导致一次重新渲染。
猜你喜欢
  • 1970-01-01
  • 2020-02-07
  • 1970-01-01
  • 2022-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-12
  • 1970-01-01
相关资源
最近更新 更多