Обновления состояний могут быть асинхронными

Что именно они имеют в виду? Если я правильно понимаю, я не могу использовать 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 бесплатно.

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 будет увеличен на 1 в зависимости от состояния, в котором он находился во время вызоваsetState()в то время как в методе № 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')
);

В приведенном выше: вы увидите это в консоли:

> 1
> 1
> 1

И окончательная стоимость this.state.n было бы 1, Все setState() звонки были поставлены в очередь, когда значение n было 0так что все они просто установить его 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')
);

Выше вы увидите это в консоли:

> 3
> 3
> 3

И окончательная стоимость n было бы 3, Как и в методе № 1, все setState() звонки были поставлены в очередь одновременно. Тем не менее, поскольку они используют функцию для синхронного обновления в порядке, используя самое актуальное состояние - включая изменения состояния, сделанные одновременными обновлениями состояния - они должным образом увеличивают n три раза, как и следовало ожидать.


Теперь, почему console.log() шоу 3 три раза для метода № 2 вместо 1, 2, 3? Ответ в том, что setState() Все обратные вызовы происходят вместе в конце фазы обновления состояния в React, а не сразу после того, как происходит это обновление конкретного состояния. Так что в этом отношении методы № 1 и № 2 идентичны.

Другие вопросы по тегам