状态更新可能是异步的

x-y*_*uri 2 javascript asynchronous setstate reactjs

他们究竟是什么意思?如果我的理解正确的,我不能用this.state计算新的状态时,除非我通过一个函数作为第一个参数,setState():

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

// Correct
this.setState(prevState => {return {a: f(prevState)}});
Run Code Online (Sandbox Code Playgroud)

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

if (this.state.a)
    this.setState({b: 2});
Run Code Online (Sandbox Code Playgroud)

道具怎么样?

// Correct or wrong?
this.setState({name: f(this.props)});
Run Code Online (Sandbox Code Playgroud)

据说我this.state打电话后不能指望改变this.setState:

this.setState({a: 1});
console.log(this.state.a);   // not necessarily 1
Run Code Online (Sandbox Code Playgroud)

然后,说我有一个用户列表.还有一个选择,我可以让一个用户当前:

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]));
        });
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

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

第二个想法,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');
}
Run Code Online (Sandbox Code Playgroud)

这样我就currentUserOption可以免费排队.

jer*_*red 11

你并没有真正提出一个非常具体的问题."这意味着什么"并不是很重要.但是你通常似乎理解基础知识.

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

所以你要么这样做:

// Method #1
this.setState({foo: this.state.foo + 1}, this.someCallback);
Run Code Online (Sandbox Code Playgroud)

或这个:

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

主要的区别在于,使用方法#1,foo将根据您调用时的状态setState()增加1 ,而在方法#2中,foo将根据之前状态中的任何状态增加1.箭头功能运行.因此,如果您setState()在实际状态更新之前的"相同"时间进行多次调用,则方法#1可能会发生冲突和/或基于过时状态,而使用方法#2时,它们可以保证最多 -到目前为止状态,因为它们在状态更新阶段一个接一个地同步更新.

这是一个说明性的例子:


方法#1 JSBIN示例

// 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')
);
Run Code Online (Sandbox Code Playgroud)

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

> 1
> 1
> 1
Run Code Online (Sandbox Code Playgroud)

而最终的价值this.state.n将是1.所有的setState()电话都是排队时的价值n0,让他们都简单地将它设置为0 + 1.


方法#2 JSBIN示例

// 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')
);
Run Code Online (Sandbox Code Playgroud)

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

> 3
> 3
> 3
Run Code Online (Sandbox Code Playgroud)

而最终的价值n将是3.与方法#1一样,所有setState()呼叫都同时排队.但是,由于它们使用一个函数来按顺序使用最新状态进行同步更新 - 包括由并发状态更新所做的状态更改 - 它们n正如您期望的那样正确地增加三次.


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

  • @FarhanShirgillAnsari `setState()` 调用会作为 React 内部循环的一部分进行排队和“批量”处理。每次状态更改后都不会进行“渲染”,而是尽可能将它们一起处理。如果同时同步触发多个状态更新,则可能只会导致一次重新渲染。 (2认同)