React.js. Состояние компонента

10.08.2021

Теги: FrontendJavaScriptReact.jsWeb-разработкаКомпонентТеория

Объект state описывает внутреннее состояние компонента и чем-то похож на объект props. Но если пропсы представляет данные, которые приходят извне, состояние определяется внутри и доступно только внутри компонента. Также существенное отличие state от props в том, что значения в state можно и нужно изменять.

Важный момент — значения из state должны использоваться при рендере. Если какое-то свойство объекта state не используется в рендере — нет смысла сохранять его в state. Для обновления состояния предназначен метод setState():

this.setState({welcome: "Привет, React!"});

Изменение состояния вызовет повторный рендер компонента. При этом нам не обязательно обновлять все свойства объекта состояния, мы можем обновить только некоторые. Тогда необновленные свойства будут сохранять свои старые значения.

Нельзя изменять свойства состояния напрямую, в этом случае повторного рендера компонента происходить не будет.

this.state.welcome = "Привет, React!";

Документация говорит — думайте о setState(), как о запросе, а не как о команде немедленного обновления компонента. Для увеличения производительности React может задержать его выполнение, а затем обновить несколько компонентов за один проход. Это делает чтение this.state сразу после вызова setState() потенциальной ловушкой.

Давайте разберем это на примере и посмотрим, как это обойти:

class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        };
    }

    handleClick = () => {
        this.setState({count: this.state.count + 1});
    };

    render() {
        return (
            <div>
                <strong>{this.state.count}</strong>
                <button onClick={this.handleClick}>Увеличить</button>
            </div>
        );
    }
}

Теперь попробуем вызвать setState() несколько раз:

class Counter extends React.Component {
    /* ..... */
    handleClick = () => {
        this.setState({count: this.state.count + 1});
        // вроде бы у нас новое значение this.state.count и мы можем увеличить его еще раз
        this.setState({count: this.state.count + 1});
        // вроде бы у нас новое значение this.state.count и мы можем увеличить его еще раз
        this.setState({count: this.state.count + 1});
    };
    /* ..... */
}

Несколько вызовов setState() в течение одного и того же цикла могут быть объединены вместе. Мы пытаемся увеличить счетчик более одного раза в одном цикле, это будет эквивалентно:

Object.assign(
    prevState,
    {count: state.count + 1},
    {count: state.count + 1},
    ...
)

Так что в итоге мы получаем одно увеличение count на единицу, а не три увеличения на единицу. Чтобы этого избежать, можно использовать в качестве первого аргумента setState() функцию, а не объект.

class Counter extends React.Component {
    /* ..... */
    handleClick = () => {
        this.setState(prevState => ({count: prevState.count + 1}));
        this.setState(prevState => ({count: prevState.count + 1}));
        this.setState(prevState => ({count: prevState.count + 1}));
    };
    /* ..... */
}

Эта функция принимает два аргумента — предыдущее состояние и пропсы, а должна вернуть объект, содержащий новые значения свойств объекта состояния:

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

Метод setState() может принимать второй необязательный параметр. Это колбэк, вызываемый после выполнения setState() и повторного рендера компонента. Вместо этого в большинстве случаев для такой логики рекомендуется использовать componentDidUpdate().

class Counter extends React.Component {
    /* ..... */
    handleClick = () => {
        console.log('Перед setState() count =', this.state.count);
        this.setState(
            {count: this.state.count + 1},
            () => console.log('Новое значение count =', this.state.count)
        );
        console.log('После setState() count =', this.state.count);
    };
    /* ..... */
}
Первый раз кликаем
Перед setState() count = 0
После setState() count = 0
Новое значение count = 1
Второй раз кликаем
Перед setState() count = 1
После setState() count = 1
Новое значение count = 2

Состояние для функциональных компонентов

Для этого предназначен хук useState(). Как нетрудно догадаться из названия, хук позволяет работать с состоянием в функциональных компонентах.

const [value, setValue] = useState(initValue);

Таким образом мы создали состояние, переменная value хранит состояние, а функция setValue() позволяет изменять значение value. Единственный аргумент useState() — это начальное состояние. Исходное значение аргумента используется только при первом рендере.

Давайте вернемся к примеру компонента Clicker в классовом стиле:

import React from 'react';

class Clicker extends React.Component {
    state = {
        count: 0,
    };

    handleClick = () => {
        this.setState({count: this.state.count + 1});
    };

    render() {
        return (
            <div>
                <strong>{this.state.count}</strong>
                <button onClick={this.handleClick}>Увеличить</button>
            </div>
        );
    }
}

И сделаем все то же самое, но с использованием функционального компонента:

import React, {useState} from 'react';

export default function Clicker() {
    // сохраняем состояние в переменной count
    const [count, setCount] = useState(0);

    const handleClick = () => {
        setCount(count + 1);
    };

    return (
        <div>
            <strong>{count}</strong>
            <button onClick={handleClick}>Увеличить</button>
        </div>
    );
}

Если новое состояние вычисляется с использованием предыдущего состояния, можно передать функцию в setState(). Функция получит предыдущее значение и должна вернуть новое значение:

const [state, setState] = useState(initState);
setState((prevState) => {
    const nextState = prevState + 1;
    return nextState;
});

Аргумент initState — это значение, используемое во время начального рендеринга, в дальнейшем оно не используется. Если начальное состояние является результатом дорогостоящих вычислений, можно передать функцию, которая будет выполняться только один раз при монтировании.

const [state, setState] = useState(() => {
    const initState = expensiveComputation(props);
    return initState;
});

Если обновить состояние хука тем же значением, что и текущее состояние, React досрочно выйдет из хука без повторного рендера дочерних элементов и запуска эффектов.

Поиск: JavaScript • React.js • Web-разработка • Frontend • Компонент • Теория • Состояние • setState

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.