Skip to content

Latest commit

 

History

History
262 lines (198 loc) · 11.2 KB

07. Стейты компонентов как источник данных.md

File metadata and controls

262 lines (198 loc) · 11.2 KB

Стейты компонентов как источник данных

В компонентном подходе компонентны не только рендер и стили, но и логика

Принципы Реакта

iframe: https://www.youtube.com/embed/f11f8ksArp4

Эвенты, классовые компоненты, this и стейт

iframe: https://www.youtube.com/embed/eQyGf8Fe_YQ

Работа с данными

iframe: https://www.youtube.com/embed/tj3wrvHdyuo

Окей, с вёрсточкой разобрались: поняли, как верстать компонентами, переехали с селекторов на Стайлед-компонентс, поиграли с Реакт-роутером, но пора двигаться дальше.

Где хранить данные? Как с ними работать? Нужно ли писать сервис-провайдеры-фабрики как в Ангуларе?

Стейты

Слава богу, не нужно! Реакт несложный даже в работе с данными и оперирует обычными функциями, которые возвращают новые данные.

Как работает Реакт? У компонента есть стейт — состояние данных, есть рендер — вёрстка, в сумме это то самое уравнение ui = render(state) из первого урока.

Что это значит? Когда обновляется стейт, вызывается рендер и строится новый интерфейс.

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

Да, если вы хотите поставить кнопке красный цвет, не нужно писать document.getElementById("my-button").style.background = 'red', достаточно просто поставить новый стейт.

Да, если вы хотите чекбоксу поставить checked, можно в стейт положить checked: true и передать пропом checked={state.checked}.

Да, в стейте можно хранить даже данные с сервера.

Да, в стейте хранят и данные заполненных форм.

Да, это работает как на маленьких проектах, так и на крупных.

Да, Редакс уже близко. Редакс это всего лишь глобальный стейт, а не компонентный.

Посортируем список фильмов

Что такое стейт? Это просто объект с данными, к которому можно обратиться. Давайте в виде псевдокода представим что у нашего компонента есть стейт.

const state = {};

// рендерим
function Movies() {
  return (
    <div>
      <p>Sort by:</p>
      <button>alphabetical</button>
      <button>date released</button>
      <button>reviews count</button>
      <button>average rating</button>
      ...movies list
    </div>
  );
}

В этом стейте мы можем хранить данные. Почему в стейте, а не просто константой? Стейт — для динамических данных, а в интерфейсе у нас есть сортировка фильмов.

Обновилась сортировка → обновился список фильмов → обновился интерфейс.

Каждой кнопке мы добавим проп onClick, внутри которого будет функция, которая будет вызывать нашу функцию handleClick и передавать туда поле, по которому нужно отсортировать. Заодно наполним стейт начальными данными.

const state = {
  movies: [
    {
      id: 1,
      title: "City Lights",
      year: 1931,
      rating: 8.5
    },
    {
      id: 2,
      title: "American History X",
      year: 1998,
      rating: 8.5
    },
    {
      id: 3,
      title: "Wild Strawberries",
      year: 1957,
      rating: 8.1
    }
  ]
};

function changeSort(kind) {}

// рендерим
function Movies() {
  return (
    <div>
      <p>Sort by:</p>
      <button onClick={() => changeSort("alphabetical")}>alphabetical</button>
      <button onClick={() => changeSort("date")}>date released</button>
      <button onClick={() => changeSort("rating")}>average rating</button>
      ...movies list
    </div>
  );
}

Теперь давайте представим, что у нас есть некая магическая функция setState, которая принимает в себя данные и обновляет стейт.

Причем, что удобно, обновляет только указанные данные: например, если у вас в стейте три поля isFetching, movies, error, а вы вызовете setState({ isFetching: true }), то movies и error останутся такими же, какими и были.

Окей, у нас есть эта функция, давайте теперь напишем нашу функцию changeSort():

// заведём функцию сортировки
// https://davidwalsh.name/array-sort
function sortObjectsByField(field, leftObject, rightObject) {
  return leftObject[field] - rightObject[field];
}

function changeSort(kind) {
  // заведём объект,
  // который будет приводить
  // kind к полю у объекта
  const sortFieldsToKey = {
    alphabetical: "title",
    date: "year",
    rating: "rating"
  };

  // получим поле у объектов,
  // по которому нужно сортировать
  const currentSortField = sortFieldsToKey[kind];

  // сохраним текущее поле
  // чтобы подсветить кнопку активным
  // ЭТА ФУНКЦИЯ ПРИДУМАНА, ЕЁ НЕ СУЩЕСТВУЕТ
  setState({ orderField: kind });

  // отсортируем фильмы
  setState({
    movies: state.movies.sort(function(leftObj, rightObj) {
      return sortObjectsByField(currentSortField, leftObj, rightObj);
    })
  });
}

Что дальше? В рендере мы просто выведем эти фильмы через метод .map() у массива.

Этот метод проходится по массиву, на каждом элементе вызывает функцию, которая должна вернуть новое значение под этим же индексом массива, в нашем случае — Реакт-компонент.

Если тут ничего непонятно — перечитайте третий урок про базовый джаваскрипт.

function Movies() {
  return (
    <div>
      <p>Sort by:</p>
      <button onClick={() => changeSort("alphabetical")}>alphabetical</button>
      <button onClick={() => changeSort("date")}>date released</button>
      <button onClick={() => changeSort("rating")}>average rating</button>

      {state.movies.map(function(movie) {
        // movie это объект с данными

        // возвращаем Реакт-компонент
        return (
          <a href={`/movies/${movie.id}`} key={movie.id}>
            {movie.title} ({movie.year}) | rating {movie.rating}
          </a>
        );
      })}
    </div>
  );
}

Что ещё за фигурные скобки? Это Джсх, в фигурных скобках может быть любое Джс-выражение: хоть 2 + 2, хоть обращение к объекту user.firstName, хоть вызов функции formatName(user).

Что за проп key? Это обязательный у списков проп, который помогает Реакту отделить один компонент от другого при обновлении данных. Реакт справится и без них, но лучше с ними.

Мы же прошлись по массиву и получили новый массив, в котором есть Реакт-компоненты. Их мы и отрендерили.

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

function Movies() {
  const movies = state.movies.map(function(movie) {
    // movie это объект с данными

    // возвращаем Реакт-компонент
    return (
      <a href={`/movies/${movie.id}`} key={movie.id}>
        {movie.title} ({movie.year}) | rating {movie.rating}
      </a>
    );
  });

  return (
    <div>
      {/* кнопки сортировки */}

      {movies}
    </div>
  );
}

В итоге мы будем рендерить список фильмов, а при клике на сортировку — сортировать его и автоматически обновлять стейт, потому что у нас есть вымышленная функция setState(): от неё зависит Реакт и обновляет рендер сразу же после того как обновится стейт.

Итог

На этом наш урок подходит к концу.

Сегодня мы разобрались с тем, как у компонентов могут быть стейты, в которых хранятся и обновляются данные через setState().

Важно помнить, что всю работу по обновлению интерфейса Реакт берёт на себя, наша задача — писать стейт, обновлять его и описать в рендере вёрстку от этого стейта.


Кстати, а как быть, если хочется не ссылку рендерить через <a>, а свой компонент? Как туда передать стейт?

Пропами!

function Movie(props) {
  return (
    <a href={`/movies/${props.id}`}>
      {props.title} ({props.year}) | rating {props.rating}
    </a>
  );
}

function Movies() {
  const movies = state.movies.map(function(movie) {
    // movie это объект с данными

    // возвращаем Реакт-компонент
    return (
      <Movie
        id={movie.id}
        year={movie.year}
        rating={movie.rating}
        key={movie.id}
      >
        {movie.title}
      </Movie>
    );
  });

  return (
    <div>
      {/* кнопки сортировки */}

      {movies}
    </div>
  );
}