Этот сайт больше не обновляется.Перейдите на react.dev

Введение: знакомство с React

These docs are old and won’t be updated. Go to react.dev for the new React docs.

The updated Tutorial teaches modern React and includes live examples.

Вам не обязательно знать React, чтобы проходить это введение.

Прежде чем начать

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

Совет

Это введение рассчитано на людей, которые предпочитают учиться на практике. Если вам больше нравится изучать предмет от начала до конца, то начните с пошагового руководства. Введение и руководство в чём-то дополняют друг друга.

Введение состоит из нескольких разделов:

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

Что мы собираемся писать?

В этом введении мы покажем как создать интерактивную игру крестики-нолики на React.

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

Мы советуем вам поиграть в крестики-нолики, прежде чем продолжите чтение. Одна из особенностей, которую вы можете заметить — это нумерованный список справа от игрового поля. Этот список отображает историю всех игровых ходов и обновляется по мере игры.

Вы можете закрыть игру в крестики-нолики, как только познакомитесь с ней. Мы начнём с простой заготовки. Следующим шагом мы настроим окружение, чтобы вы могли начать создание игры.

Предварительные требования

Мы предполагаем, что вы немного знакомы с HTML и JavaScript. Однако, вы сможете изучать введение, даже если знакомы только с другими языками программирования. Мы также полагаем, что вы знакомы с такими понятиями программирования как функции, объекты, массивы и, в меньшей степени, классы.

Если вам нужно повторить основы JavaScript, мы рекомендуем прочитать вот этот учебник. Обратите внимание, что мы используем некоторые особенности из ES6 (последней версии JavaScript), такие как стрелочные функции, классы, операторы let и const. Вы можете воспользоваться Babel REPL, чтобы узнать во что компилируется ES6-код.

Настройка окружения

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

Вариант 1: Пишем код в браузере

Это самый быстрый способ для старта.

Для начала откройте эту Заготовку в новой вкладке. Вы увидите пустое поле для игры в крестики-нолики и код на React, который мы будем постепенно изменять.

Можете пропустить следующую часть и перейти к Обзору React.

Вариант 2: Локальное окружение для разработки

Это не является обязательным и не требуется этим вводным руководством!


Опционально: Инструкции для написания кода в вашем любимом редакторе

Эти настройки потребуют больше работы, но позволят продолжить разработку с использованием вашего любимого редактора. Вот что нужно сделать:

  1. Убедиться, что у вас установлена последняя версия Node.js.
  2. Выполнить инструкции по установке Create React App для создания нового проекта.
npx create-react-app my-app
  1. Удалить все файлы в папке src/ нового проекта.

Примечание:

Не удаляйте саму папку src, только файлы внутри неё. Следующим шагом мы заменим стандартные файлы нашими примерами.

cd my-app
cd src

# Для пользователей Mac или Linux:
rm -f *

# Для пользователей Windows
del *

# Затем вернёмся в папку проекта
cd ..
  1. Создайте файл с именем index.css в папке src/ и добавьте в него вот этот CSS код.
  2. Создайте файл с именем index.js в папке src/ и добавьте в него этот JS код.
  3. В начало файла index.js в папкеsrc/ добавьте следующие три строчки:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';

Теперь, когда вы запустите npm start в папке проекта и откроете http://localhost:3000 в браузере, вы должны увидеть пустое поле для крестиков-ноликов.

Мы рекомендуем выполнить эти инструкции для настройки подсветки синтаксиса в вашем редакторе.

Помогите, я застрял!

Если вы застряли — обратитесь к сообществу. В частности Чат Reactiflux — это прекрасное место, где вам помогут. Если вы не дождались ответа или всё ещё не решили своей проблемы, пожалуйста, задайте вопрос, и мы вам поможем.

Обзор

Теперь, когда вы готовы, перейдём к рассмотрению React!

Что такое React?

React — это декларативная, эффективная и гибкая JavaScript-библиотека для создания пользовательских интерфейсов. Она позволяет вам собирать сложный UI из маленьких изолированных кусочков кода, называемых «компонентами».

React имеет несколько разных видов компонентов, но мы начнём с подклассов React.Component:

class ShoppingList extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <h1>Список покупок для {this.props.name}</h1>
        <ul>
          <li>Instagram</li>
          <li>WhatsApp</li>
          <li>Oculus</li>
        </ul>
      </div>
    );
  }
}

// Пример использования: <ShoppingList name="Марк" />

Скоро мы перейдём к этим забавным, похожим на XML, тегам. Мы используем компоненты, чтобы сообщить React, что мы хотим увидеть на экране. Каждый раз, когда наши данные меняются, React эффективно обновляет и повторно рендерит наши компоненты.

ShoppingList является примером классового компонента React. Компонент принимает параметры, которые называются пропсами (props, сокращение от properties — свойства), и возвращает из метода render() иерархию представлений для отображения.

Метод render возвращает описание того, что вы хотите увидеть на экране. React берёт это описание и отображает результат. Если точнее, render возвращает React-элемент, который является легковесным описанием того, что нужно отрендерить. Большинство React-разработчиков используют специальный синтаксис под названием «JSX» для упрощения описания структуры. Во время компиляции синтаксис <div /> преобразовывается в React.createElement('div'). Пример выше равнозначен вот этому:

return React.createElement('div', {className: 'shopping-list'},
  React.createElement('h1', /* ... h1 children ... */),
  React.createElement('ul', /* ... ul children ... */)
);

Смотрите полную версию.

Если вам интересно, то createElement() более подробно описан в справочнике API, однако, мы не будем им пользоваться в этом введении. Вместо этого мы продолжим использовать JSX.

JSX обладает всей мощью JavaScript. В JSX вы можете использовать любые JavaScript-выражения внутри фигурных скобок. Каждый React-элемент является JavaScript-объектом, который можно сохранить в переменную или использовать внутри программы.

Приведённый выше компонент ShoppingList рендерит только встроенные DOM-компоненты вроде <div /> или <li />. Но вы также можете создавать и рендерить собственные компоненты. Например, теперь вы можете ссылаться на весь компонент со списком покупок, написав <ShoppingList />. Каждый React-компонент инкапсулирован и может использоваться независимо. Это позволяет создавать сложный UI из простых компонентов.

Разберёмся со стартовым кодом

Если вы собираетесь работать над практической частью в вашем браузере, откройте этот код в новой вкладке начальный код. Если вы собираетесь работать над практикумом локально, откройте src/index.js в папке вашего проекта (вы уже использовали этот файл в разделе настройки).

Этот стартовый код — база, с которой мы начнём разработку. Мы будем использовать готовые CSS-стили, чтобы сосредоточиться на изучении React и написании игры крестики-нолики.

Изучив код, вы обнаружите три React-компонента:

  • Square (Клетка)
  • Board (Поле)
  • Game (Игра)

Компонент Board рендерит 9 компонентов Square, каждый из которых рендерит по одному элементу <button>. Компонент Game рендерит поле c заглушками, которые мы изменим позже. Пока у нас нет ни одного интерактивного компонента.

Передача данных через пропсы

Для начала давайте попробуем передать некоторые данные из нашего компонента Board в компонент Square.

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

class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />;  }
}

Изменим метод render внутри Square, заменив {/* TODO */} на {this.props.value}:

class Square extends React.Component {
  render() {
    return (
      <button className="square">
        {this.props.value}      </button>
    );
  }
}

До:

React Devtools

После: Вы должны увидеть число внутри каждого отрендеренного квадрата.

React Devtools

Посмотреть полный код этого шага

Поздравляем! Вы только что «передали проп» из родительского компонента Board в дочерний компонент Square. Передача пропсов это то, как данные в React-приложениях «перетекают» от родительских компонентов к дочерним.

Добавим взаимодействие с компонентом

Попробуем при клике на компонент Square ставить «X». Вначале изменим тег кнопки, который возвращается из метода render() компонента Square:

class Square extends React.Component {
  render() {
    return (
      <button className="square" onClick={function() { console.log('клик'); }}>        {this.props.value}
      </button>
    );
  }
}

Теперь, если вы кликнете по Square, то увидите «клик» в консоли инструментах разработки браузера.

Примечание

Чтобы избежать странного поведения this, мы будем использовать синтаксис стрелочных функций для обработчиков событий (здесь и ниже):

class Square extends React.Component {
 render() {
   return (
     <button className="square" onClick={() => console.log('клик')}>       {this.props.value}
     </button>
   );
 }
}

Обратите внимание что в onClick={() => console.log('клик')}, мы передаём функцию в качестве значения пропа onClick. React вызовет эту функцию при клике. Пропуск () => и запись onClick={console.log('клик')}довольно распространённая ошибка. Такой код будет выдавать сообщение при каждом перерендере.

Следующим шагом мы хотим, чтобы компонент Square «запоминал», что по нему кликнули и поставили «X». Для «запоминания» компоненты используют состояние.

Компоненты React могут получить состояние, устанавливая this.state в конструкторе. this.state должно рассматриваться как приватное свойство React-компонента, определяемое им самим. Давайте сохраним текущее значение Square в this.state и изменим его при клике.

Сперва добавим конструктор к классу, чтобы инициализировать состояние:

class Square extends React.Component {
  constructor(props) {    super(props);    this.state = {      value: null,    };  }
  render() {
    return (
      <button className="square" onClick={() => console.log('клик')}>
        {this.props.value}
      </button>
    );
  }
}

Примечание

В JavaScript-классах вам всегда нужно вызывать super при объявлении конструктора подкласса. Все классовые React-компоненты, у которых есть constructor, должны начинаться с вызова super(props).

Теперь изменим метод render компонента Square для отображения текущего значения из состояния при клике:

  • Заменим this.props.value на this.state.value внутри тега <button>.
  • Заменим обработчик onClick={...} на onClick={() => this.setState({value: 'X'})}.
  • Поместим свойства className и onClick на разных строчках для лучшей читабельности.

После этих изменений тег <button>, возвращаемый из метода render компонента Square, будет выглядеть так:

class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: null,
    };
  }

  render() {
    return (
      <button
        className="square"        onClick={() => this.setState({value: 'X'})}      >
        {this.state.value}      </button>
    );
  }
}

Вызывая this.setState из обработчика onClick (как это описано в методе render), мы говорим React, что нужно перерендерить Square при каждом клике по <button>. После обновления, this.state.value внутри Square станет X, поэтому на игровом поле мы увидим X. Если вы кликнете по любому Square, должен появиться X.

Когда вы вызываете setState внутри компонента, React так же автоматически обновляет дочерние компоненты.

Посмотреть полный код этого шага

Инструменты разработки

Расширение React Devtools для Chrome и Firefox позволяет вам изучать дерево React-компонентов внутри панели инструментов разработчика вашего браузера.

React Devtools

Расширение React DevTools позволяет просматривать пропсы и состояние ваших React-компонентов.

После установки React DevTools, вы можете кликнуть правой кнопкой мыши на любой элемент страницы и нажать Inspect (Просмотреть код), чтобы открыть инструменты разработчика. Вкладки React («⚛️ Components» и «⚛️ Profiler») появятся справа. Используйте вкладку «⚛️️ Components» для просмотра дерева компонентов.

Внимание, чтобы это работало на CodePen нужно сделать ещё несколько действий:

  1. Войти или зарегистрироваться и подтвердить вашу электронную почту (требуется для защиты от спама).
  2. Нажать кнопку «Fork».
  3. Нажать «Change View» и выбрать «Debug mode».
  4. В открывшейся новой вкладке у вас должны появиться инструменты разработчика.

Создание игры

Теперь у нас есть базовые элементы для создания игры крестики-нолики. Для полноценной игры нам необходимо реализовать поочерёдное размещение «X» и «O», а также способ определения победителя.

Подъём состояния

Сейчас каждый компонент Square хранит в себе состояние игры. Для выявления победителя мы будем хранить значение всех 9 клеток в одном месте.

Возможно, вы предполагали, что Board просто запросит у каждого Square его состояние. Хотя такой подход в React возможен, мы его не одобряем. Из-за этого код становится трудным, провоцирует ошибки и усложняет рефакторинг. Вместо этого, лучшим решением будет хранение состояния игры в родительском компоненте Board, а не в каждом отдельном Square. Компонент Board может указывать каждому Square, что именно нужно отобразить, передавая проп. Мы так уже делали, когда передавали число в каждую клетку.

Чтобы собрать данные из нескольких дочерних элементов, или чтобы дать возможность двум компонентам общаться, вам нужно объявить общее состояние внутри родительского компонента. Родительский компонент может передать состояние обратно дочерним элементам с помощью пропсов. Это поддерживает синхронизацию дочерних компонентов друг с другом и с родительским компонентом.

Подъём состояния в родительский компонент — обычное дело при рефакторинге React-компонентов. Давайте воспользуемся случаем и попробуем это сделать.

Добавим конструктор к компоненту Board и установим начальное состояние в виде массива из 9 элементов, заполненного значениями null. Эти 9 элементов соответствуют 9 квадратам:

class Board extends React.Component {
  constructor(props) {    super(props);    this.state = {      squares: Array(9).fill(null),    };  }
  renderSquare(i) {
    return <Square value={i} />;
  }

Позже, при заполнении поля, массив this.state.squares будет выглядеть примерно так:

[
  'O', null, 'X',
  'X', 'X', 'O',
  'O', null, null,
]

Метод renderSquare внутри Board сейчас выглядит так:

  renderSquare(i) {
    return <Square value={i} />;
  }

Вначале мы передали из Board проп value вниз, чтобы отобразить номера от 0 до 8 внутри каждого Square. На другом шаге мы заменили числа знаком «X», определённом в собственном состоянии Square. Именно поэтому сейчас Square игнорирует проп value, переданный в него из Board.

Мы снова воспользуемся механизмом передачи пропсов. Изменим Board, чтобы передать каждому Square его текущее значение ('X', 'O' или null). Мы уже определили массив squares в конструкторе Board. Давайте изменим метод renderSquare, чтобы читать данные из этого массива:

  renderSquare(i) {
    return <Square value={this.state.squares[i]} />;  }

Посмотреть полный код этого шага

Теперь каждый Square получает проп value, который будет, либо 'X' или 'O', либо null для пустых клеток.

Дальше нам нужно поменять то, что происходит при клике на Square. Теперь компонент Board хранит информацию о заполненных клетках. Нам нужен способ, которым Square сможет обновлять состояние Board. Поскольку состояние является приватным для компонента, где оно определено, мы не можем обновить состояние Board напрямую из Square.

Вместо этого, давайте передадим из Board в Square функцию, и будем её вызывать из Square, когда по тому кликнули. Изменим метод renderSquare в Board-компоненте на:

  renderSquare(i) {
    return (