State - состояние React компонента


Классы в React используем в тех случаях, когда нам нужно хранить внутреннее состояние компонента, которое изменяется в ходе выполнения программы. Такое состояние называется State. State очень похоже на Props - свойства, разница только в том, что State можно изменить, а Props - нет.

State это всегда объект.
Хранить State можно в конструкторе

export default class TodoListItem extends Component {
  
  constructor() {
    super();
    this.state = {
      done: false
    }
  }
}

Или при помощи полей класса (экспериментальное свойство пока ещё не вошедшее в стандарт).
Дополнено:  уже вошедшее

export default class TodoListItem extends Component {
  
  state = {
      done: false
    };
 }

Напишем код. который позволяет в зависимости от значения  done добавлять к пунктам списка дел отметку о выполнении. Для этого достаточно присвоить им класс  done, для которого в css указаны соответствующие стили.

1. Деструктурируем state

const { done } = this.state;

2. Создаём переменную classNames

let classNames = 'todo-list-item';

Указываем, что именно указанные в ней классы получает пункт списка дел

<span className={classNames}>

3. Добавляем условие: если done === true, к classNames добавляем класс done

if (done){
      classNames +=  ' done';
    }

4. Для пункта списка, по которому кликнули, меняем значение done. Изменить state напрямую нельзя. для этого используется метод setState()

onLabelClick = () => {
      this.setState({
        done: true
      })
    };

Код полностью

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import './TodoListItem.css';

export default class TodoListItem extends Component {
  
  state = {
      done: false
    };
  
  onLabelClick = () => {
      this.setState({
        done: true
      })
    };
  
  render() {
    const { label, important = false } = this.props;
    const { done } = this.state;
    
    let classNames = 'todo-list-item';
    if (done){
      classNames += ' done';
    }

  return (
    <span className={classNames}>
      <span
        className="todo-list-item-label"
        onClick = {this.onLabelClick}>
        {label}
      </span>
    </span>
  );
};


Добавим ещё один класс , которым будем отмечать важные дела. Добавлять его будем при клике на кнопку

export default class TodoListItem extends Component {
  
  state = {
      done: false,
      important: false
    };
  
  onLabelClick = () => {
      this.setState({
        done: true
      })
    };
  
  onButtonClick = () => {
      this.setState({
        important: true
      })
    };
  
  render() {
    const { label } = this.props;
    const { done, important } = this.state;
    
    let classNames = 'todo-list-item';
    if (done){
      classNames += ' done';
    }
    
    if (important){
      classNames += ' important';
    }
    
    const style = {
      color: important ? 'steelblue' : 'black',
      fontWeight: important ? 'bold' : 'normal'
    };

  return (
    <span className={classNames}>
      <span
        className="todo-list-item-label"
        onClick = {this.onLabelClick}>
        {label}
      </span>

      <button type="button"
              className="btn btn-outline-success btn-sm float-right"
              onClick = {this.onButtonClick}>
        <i className="fa fa-exclamation" />
      </button>
    </span>
  );
};
}

Здесь важно запомнить один момент: для изменении setState() достаточно изменить не весь объект state целиком, а только одно нужное нам свойство.

Асинхронность state

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

Указанный ниже вариант работающий, но неверный

  onButtonClick = () => {
      this.setState({
        important: !this.state.important
      })
    };

здесь мы меняем состояние state при клике на кнопку.
Но метод setState() иногда (но не всегда) может работать асинхронно.
То есть state обновляется не мгновенно .

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

  onButtonClick = () => {
      this.setState((state) => {
        return {
          important: !state.important
        };
      })
    };

Указанный в параметрах функции state говорит реакту, что функцию нужно выполнить только тогда, когда значение state уже установлено.

Небольшой рефракторинг.  state можно сразу деструктурировать, в результате код немного изменится


  onButtonClick = () => {
      this.setState(({important}) => {
        return {
          important: !important
        };
      })
    };