Tutorial: Intro To React

Сегодня мы собираемся создать интерактивную игру tic-tac-toe.

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

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

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

Предпосылки

Предположим, что вы знакомы с HTML и JavaScript, но вы должны следовать, даже если вы раньше не использовали их.

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

Как следовать вдоль

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

Если вы предпочитаете писать код в браузере

Это самый быстрый способ начать работу!

Сначала откройте этот код стартера на новой вкладке. Он должен отображать пустое поле tic-tac-toe. Мы будем редактировать этот код во время этого урока.

Теперь вы можете пропустить следующий раздел о настройке локальной среды разработки и прямо перейти к обзору.

Если вы предпочитаете писать код в своем редакторе

Кроме того, вы можете настроить проект на своем компьютере.

Примечание: это полностью необязательно и не требуется для этого урока!

Это больше работает, но позволяет работать с комфортом вашего редактора.

Если вы хотите это сделать, выполните следующие действия:

  1. Убедитесь, что установлена ​​последняя версия Node.js.
  2. Следуйте инструкциям по установке, чтобы создать новый проект.

npm install -g create-react-app

create-react-app my-app

 

  1. Удалите все файлы в src/ папке нового проекта (не удаляйте папку, просто ее содержимое).

cd my-app

rm -f src/*

  1. Добавьте файл с именем css в папку src/ с этим кодом CSS.
  2. Добавьте файл с именем js в папку src/ с этим JS-кодом.
  3. Добавьте эти три строки в начало js в папке src/:

import React from ‘react’;

import ReactDOM from ‘react-dom’;

import ‘./index.css’;

Теперь, если вы запустите npm start в папке проекта и откройте http://localhost:3000 в браузере, вы увидите пустое поле tic-tac-toe.

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

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

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

С этим с пути, давайте начнем!

Что такое Реакт?

React — это декларативная, эффективная и гибкая библиотека JavaScript для создания пользовательских интерфейсов.

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

class ShoppingList extends React.Component {

render() {

return (

<div className=»shopping-list»>

<h1>Shopping List for {this.props.name}</h1>

<ul>

<li>Instagram</li>

<li>WhatsApp</li>

<li>Oculus</li>

</ul>

</div>

);

}

}

 

// Example usage: <ShoppingList name=»Mark» />

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

Здесь ShoppingList — это класс компонента React или тип компонента React. Компонент принимает параметры, называемые props, и возвращает иерархию представлений, отображаемых с помощью метода 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.

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

Компонент ShoppingList предоставляет только встроенные компоненты DOM, но вы можете легко создавать пользовательские компоненты React, написав <ShoppingList /> . Каждый компонент инкапсулирован, поэтому он может работать независимо, что позволяет создавать сложные пользовательские интерфейсы из простых компонентов.

Начните с этого примера: Код стартера .

Он содержит оболочку того, что мы строим сегодня. Мы предоставили стили, поэтому вам нужно только беспокоиться о JavaScript.

В частности, мы имеем три компонента:

  • Квадрат
  • доска
  • Игра

Компонент «Квадрат» отображает одну <button> , а Board отображает 9 квадратов, а игровой компонент отображает доску с некоторыми заполнителями, которые мы заполним позже. На данный момент ни один из компонентов не является интерактивным.

Передача данных через реквизит

Просто, чтобы намочить ноги, давайте попробуем передать некоторые данные из компонента Board в компонент Square.

В методе renderSquare измените код, чтобы передать value 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>

);

}

}

До:

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

Просмотр текущего кода.

Интерактивный компонент

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

class Square extends React.Component {

render() {

return (

<button className=»square» onClick={() => alert(‘click’)}>

{this.props.value}

</button>

);

}

}

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

Это использует новый синтаксис функции JavaScript- стрелки . Обратите внимание, что мы передаем функцию в качестве поддержки onClick. Выполнение onClick={alert(‘click’)} будет немедленно предупреждать, а не при нажатии кнопки.

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

Во-первых, добавьте конструктор в класс для инициализации состояния:

class Square extends React.Component {

constructor(props) {

super(props);

this.state = {

value: null,

};

}

 

render() {

return (

<button className=»square» onClick={() => alert(‘click’)}>

{this.props.value}

</button>

);

}

}

В классах JavaScript вам нужно явно вызвать super(); при определении конструктора подкласса.

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

  • Замените this.props.value на this.state.value внутри <button> .
  • Замените обработчик события () => alert() с помощью () => this.setState({value: ‘X’}) .

Теперь <button> выглядит следующим образом:

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 , запланировано обновление компонента, в результате чего React объединяется в обновленном состоянии передачи и повторно передает компонент вместе с его потомками. Когда компонент rerenders, this.state.value будет ‘X’ поэтому вы увидите X в сетке.

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

Просмотр текущего кода.

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

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

Он позволяет вам проверять реквизит и состояние любого из компонентов вашего дерева.

После его установки вы можете щелкнуть правой кнопкой мыши любой элемент на странице, щелкнуть «Осмотреть», чтобы открыть инструменты разработчика, а вкладка «Реакт» появится как последняя вкладка справа.

Однако обратите внимание, что есть несколько дополнительных шагов, чтобы заставить его работать с CodePen:

  1. Войдите в систему или зарегистрируйтесь и подтвердите свой адрес электронной почты (необходимый для предотвращения спама).
  2. Нажмите кнопку «Вилка».
  3. Нажмите «Изменить вид», а затем выберите «Режим отладки».
  4. В новой вкладке, которая открывается, devtools теперь должны иметь вкладку React.

Подъемное состояние вверх

Теперь у нас есть основные строительные блоки для игры tic-tac-toe. Но прямо сейчас государство инкапсулируется в каждый квадратный компонент. Чтобы создать полноценную игру, нам нужно проверить, выиграл ли один из игроков, и поочередно помещать X и O в квадраты. Чтобы проверить, выиграл ли кто-то, нам нужно иметь значение всех 9 квадратов в одном месте, а не разделять по квадратным компонентам.

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

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

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

Вытягивание состояния вверх, как это часто встречается при реорганизации компонентов React, поэтому давайте воспользуемся этой возможностью, чтобы попробовать. Добавьте конструктор в Совет и установите его исходное состояние, чтобы он содержал массив с 9 нулями, соответствующий 9 квадратам:

class Board extends React.Component {

constructor(props) {

super(props);

this.state = {

squares: Array(9).fill(null),

};

}

 

renderSquare(i) {

return <Square value={i} />;

}

 

render() {

const status = ‘Next player: X’;

 

return (

<div>

<div className=»status»>{status}</div>

<div className=»board-row»>

{this.renderSquare(0)}

{this.renderSquare(1)}

{this.renderSquare(2)}

</div>

<div className=»board-row»>

{this.renderSquare(3)}

{this.renderSquare(4)}

{this.renderSquare(5)}

</div>

<div className=»board-row»>

{this.renderSquare(6)}

{this.renderSquare(7)}

{this.renderSquare(8)}

</div>

</div>

);

}

}

Мы заполним его позже, чтобы доска выглядела примерно так:

[

‘O’, null, ‘X’,

‘X’, ‘X’, ‘O’,

‘O’, null, null,

]

Метод renderSquare в renderSquare теперь выглядит следующим образом:

renderSquare(i) {

return <Square value={i} />;

}

Измените его, чтобы передать value квадрата.

renderSquare(i) {

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

}

Просмотр текущего кода.

Теперь нам нужно изменить то, что происходит, когда щелкнут квадрат. Компонент Board теперь хранит, какие квадраты заполнены, а это означает, что нам нужно каким-то образом использовать Square для обновления состояния правления. Поскольку состояние компонента считается закрытым, мы не можем обновлять состояние Board прямо с Square.

Обычная схема здесь — это пропустить функцию от Board to Square, которая вызывается при щелчке по квадрату. Измените renderSquare в Board снова, чтобы он renderSquare :

renderSquare(i) {

return (

<Square

value={this.state.squares[i]}

onClick={() => this.handleClick(i)}

/>

);

}

Мы разделили возвращаемый элемент на несколько строк для удобства чтения и добавили вокруг него парсеры, чтобы JavaScript не вставлял точку с запятой после return и не разбивал наш код.

Теперь мы передаем два реквизита от «Совет к квадрату»: value и onClick . Последняя функция — это функция, которую может вызвать Square. Сделаем следующие изменения для Square:

  • Замените this.state.value на this.props.value в this.state.value Square.
  • Замените this.setState() на this.props.onClick() в this.setState() Square.
  • Удалить определение constructor из Square, потому что он больше не имеет состояния.

После этих изменений весь квадратный компонент выглядит следующим образом:

class Square extends React.Component {

render() {

return (

<button className=»square» onClick={() => this.props.onClick()}>

{this.props.value}

</button>

);

}

}

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

  1. Поддержка onClick встроенного компонента DOM <button> сообщает React о настройке прослушивателя событий щелчка.
  2. Когда кнопка нажата, React вызовет обработчик события onClick, определенный методом Square render() .
  3. Этот обработчик событий вызывает this.props.onClick() . Правление площади было указано Советом.
  4. Совет передал onClick={() => this.handleClick(i)} на квадрат, поэтому при вызове он запускает this.handleClick(i) на this.handleClick(i) .
  5. Мы еще не определили метод handleClick() на handleClick() , поэтому код выходит из строя.

Обратите внимание, что атрибут onClick элемента DOM <button> имеет специальное значение для Реагирования, но мы могли бы по- handleClick метод handleClick Square или Board handleClick . Однако в приложениях React обычно используются имена on* для атрибутов и handle* для методов обработчика.

Попробуйте щелкнуть квадрат — вы должны получить сообщение об ошибке, потому что мы еще не определили handleClick . Добавьте его в класс Board.

class Board extends React.Component {

constructor(props) {

super(props);

this.state = {

squares: Array(9).fill(null),

};

}

 

handleClick(i) {

const squares = this.state.squares.slice();

squares[i] = ‘X’;

this.setState({squares: squares});

}

 

renderSquare(i) {

return (

<Square

value={this.state.squares[i]}

onClick={() => this.handleClick(i)}

/>

);

}

 

render() {

const status = ‘Next player: X’;

 

return (

<div>

<div className=»status»>{status}</div>

<div className=»board-row»>

{this.renderSquare(0)}

{this.renderSquare(1)}

{this.renderSquare(2)}

</div>

<div className=»board-row»>

{this.renderSquare(3)}

{this.renderSquare(4)}

{this.renderSquare(5)}

</div>

<div className=»board-row»>

{this.renderSquare(6)}

{this.renderSquare(7)}

{this.renderSquare(8)}

</div>

</div>

);

}

}

Просмотр текущего кода.

Мы вызываем .slice() чтобы скопировать массив squares вместо мутирования существующего массива. Перейдите в раздел, чтобы узнать, почему важна неизменность.

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

Квадрат больше не сохраняет свое состояние; он получает свою стоимость от своего родительского совета и информирует своего родителя, когда его щелкают. Мы называем компоненты такими контролируемыми компонентами .

Почему важна неизменность

В предыдущем примере кода мы предлагаем использовать оператор .slice() для копирования массива squares до внесения изменений и предотвращения мутации существующего массива. Давайте поговорим о том, что это значит и почему это важная концепция для изучения.

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

Изменение данных с мутацией

var player = {score: 1, name: ‘Jeff’};

player.score = 2;

// Now player is {score: 2, name: ‘Jeff’}

Изменение данных без мутации

var player = {score: 1, name: ‘Jeff’};

 

var newPlayer = Object.assign({}, player, {score: 2});

// Now player is unchanged, but newPlayer is {score: 2, name: ‘Jeff’}

 

// Or if you are using object spread syntax proposal, you can write:

// var newPlayer = {…player, score: 2};

Конечный результат тот же, но не изменяя (или изменяя базовые данные) напрямую, теперь у нас есть дополнительное преимущество, которое может помочь нам увеличить производительность компонентов и общей производительности.

Легкость Отмены / Повтора и Путешествия во времени

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

Изменения отслеживания

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

Определить, как изменился неизменный объект, значительно проще. Если объект, на который делается ссылка, отличается от предыдущего, тогда объект изменился. Вот и все.

Определение необходимости повторной ренты в действии

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

Чтобы узнать больше о shouldComponentUpdate() и о том, как вы можете создавать чистые компоненты, взгляните на Оптимизацию производительности .

Функциональные компоненты

Мы удалили конструктор, и на самом деле React поддерживает более простой синтаксис, называемый функциональными компонентами для таких типов компонентов, как Square, которые состоят только из метода render . Вместо того, чтобы определять класс, расширяющий React.Component , просто напишите функцию, которая берет реквизит и возвращает то, что должно быть визуализировано.

Замените весь класс Square этой функцией:

function Square(props) {

return (

<button className=»square» onClick={props.onClick}>

{props.value}

</button>

);

}

Вам нужно будет изменить this.props на props оба раза. Многие компоненты в ваших приложениях могут быть написаны как функциональные компоненты: эти компоненты, как правило, легче писать, а React оптимизирует их в будущем.

Пока мы onClick={() => props.onClick()} код, мы также изменили onClick={() => props.onClick()} на просто onClick={props.onClick} , так как для этого onClick={props.onClick} функцию down down. Обратите внимание, что onClick={props.onClick()} не будет работать, потому что он будет вызывать props.onClick немедленно, а не передавать его.

Просмотр текущего кода.

Взятие поворотов

Очевидным недостатком нашей игры является то, что только X может играть. Давайте это исправим.

Предположим, что первый шаг будет равен ‘X’. Измените начальное состояние в нашем конструкторе Board:

class Board extends React.Component {

constructor(props) {

super(props);

this.state = {

squares: Array(9).fill(null),

xIsNext: true,

};

}

Каждый раз, когда мы переходим, мы переключаем xIsNext , переворачивая булевское значение и сохраняя состояние. Теперь обновите функцию handleClick Board, чтобы перевернуть значение xIsNext :

handleClick(i) {

const squares = this.state.squares.slice();

squares[i] = this.state.xIsNext ? ‘X’ : ‘O’;

this.setState({

squares: squares,

xIsNext: !this.state.xIsNext,

});

}

Теперь X и O по очереди. Затем измените текст «status» в рендере Board, чтобы он также отображал, кто следующий:

render() {

const status = ‘Next player: ‘ + (this.state.xIsNext ? ‘X’ : ‘O’);

 

return (

// the rest has not changed

После этих изменений у вас должен быть этот компонент Board:

class Board extends React.Component {

constructor(props) {

super(props);

this.state = {

squares: Array(9).fill(null),

xIsNext: true,

};

}

 

handleClick(i) {

const squares = this.state.squares.slice();

squares[i] = this.state.xIsNext ? ‘X’ : ‘O’;

this.setState({

squares: squares,

xIsNext: !this.state.xIsNext,

});

}

 

renderSquare(i) {

return (

<Square

value={this.state.squares[i]}

onClick={() => this.handleClick(i)}

/>

);

}

 

render() {

const status = ‘Next player: ‘ + (this.state.xIsNext ? ‘X’ : ‘O’);

 

return (

<div>

<div className=»status»>{status}</div>

<div className=»board-row»>

{this.renderSquare(0)}

{this.renderSquare(1)}

{this.renderSquare(2)}

</div>

<div className=»board-row»>

{this.renderSquare(3)}

{this.renderSquare(4)}

{this.renderSquare(5)}

</div>

<div className=»board-row»>

{this.renderSquare(6)}

{this.renderSquare(7)}

{this.renderSquare(8)}

</div>

</div>

);

}

}

Просмотр текущего кода.

Объявление победителя

Покажем, когда выиграна игра. Добавьте эту вспомогательную функцию в конец файла:

function calculateWinner(squares) {

const lines = [

[0, 1, 2],

[3, 4, 5],

[6, 7, 8],

[0, 3, 6],

[1, 4, 7],

[2, 5, 8],

[0, 4, 8],

[2, 4, 6],

];

for (let i = 0; i < lines.length; i++) {

const [a, b, c] = lines[i];

if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {

return squares[a];

}

}

return null;

}

Вы можете вызвать его в функции render Board, чтобы проверить, выиграл ли кто-нибудь игру, и покажите статус-шоу «Победитель: [X / O]», когда кто-то победит.

Заменить декларацию status редакторе Board следующим кодом:

render() {

const winner = calculateWinner(this.state.squares);

let status;

if (winner) {

status = ‘Winner: ‘ + winner;

} else {

status = ‘Next player: ‘ + (this.state.xIsNext ? ‘X’ : ‘O’);

}

 

return (

// the rest has not changed

Теперь вы можете изменить handleClick в Board, чтобы вернуться раньше и проигнорировать щелчок, если кто-то уже выиграл игру или квадрат уже заполнен:

handleClick(i) {

const squares = this.state.squares.slice();

if (calculateWinner(squares) || squares[i]) {

return;

}

squares[i] = this.state.xIsNext ? ‘X’ : ‘O’;

this.setState({

squares: squares,

xIsNext: !this.state.xIsNext,

});

}

Поздравления! Теперь у вас есть рабочая игра с тик-таком. И теперь вы знаете основы Реакта. Таким образом, вы, вероятно, настоящий победитель здесь.

Просмотр текущего кода.

Сохранение истории

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

Давайте планируем хранить такой объект в состоянии:

history = [

{

squares: [

null, null, null,

null, null, null,

null, null, null,

]

},

{

squares: [

null, null, null,

null, ‘X’, null,

null, null, null,

]

},

// …

]

Нам нужно, чтобы игровой компонент верхнего уровня отвечал за отображение списка ходов. Так что, как только мы вытащили штат до Квадрата в Совет, давайте теперь вытаскиваем его снова из Board into Game, чтобы у нас была вся информация, которая нам нужна на верхнем уровне.

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

class Game extends React.Component {

constructor(props) {

super(props);

this.state = {

history: [{

squares: Array(9).fill(null),

}],

xIsNext: true,

};

}

 

render() {

return (

<div className=»game»>

<div className=»game-board»>

<Board />

</div>

<div className=»game-info»>

<div>{/* status */}</div>

<ol>{/* TODO */}</ol>

</div>

</div>

);

}

}

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

  • Удалите constructor в Board.
  • Замените this.state.squares[i] на this.props.squares[i] в renderSquare .
  • Замените this.handleClick(i) на this.props.onClick(i) в renderSquare .

Теперь весь компонент Board выглядит следующим образом:

class Board extends React.Component {

handleClick(i) {

const squares = this.state.squares.slice();

if (calculateWinner(squares) || squares[i]) {

return;

}

squares[i] = this.state.xIsNext ? ‘X’ : ‘O’;

this.setState({

squares: squares,

xIsNext: !this.state.xIsNext,

});

}

 

renderSquare(i) {

return (

<Square

value={this.props.squares[i]}

onClick={() => this.props.onClick(i)}

/>

);

}

 

render() {

const winner = calculateWinner(this.state.squares);

let status;

if (winner) {

status = ‘Winner: ‘ + winner;

} else {

status = ‘Next player: ‘ + (this.state.xIsNext ? ‘X’ : ‘O’);

}

 

return (

<div>

<div className=»status»>{status}</div>

<div className=»board-row»>

{this.renderSquare(0)}

{this.renderSquare(1)}

{this.renderSquare(2)}

</div>

<div className=»board-row»>

{this.renderSquare(3)}

{this.renderSquare(4)}

{this.renderSquare(5)}

</div>

<div className=»board-row»>

{this.renderSquare(6)}

{this.renderSquare(7)}

{this.renderSquare(8)}

</div>

</div>

);

}

}

В render игры должен быть рассмотрен самый последний элемент истории и может взять на себя вычисление статуса игры:

render() {

const history = this.state.history;

const current = history[history.length — 1];

const winner = calculateWinner(current.squares);

 

let status;

if (winner) {

status = ‘Winner: ‘ + winner;

} else {

status = ‘Next player: ‘ + (this.state.xIsNext ? ‘X’ : ‘O’);

}

 

return (

<div className=»game»>

<div className=»game-board»>

<Board

squares={current.squares}

onClick={(i) => this.handleClick(i)}

/>

</div>

<div className=»game-info»>

<div>{status}</div>

<ol>{/* TODO */}</ol>

</div>

</div>

);

}

Поскольку Game теперь <div className=»status»>{status}</div> статус, мы можем удалить <div className=»status»>{status}</div> и код, вычисляющий статус из функции render Board:

render() {

return (

<div>

<div className=»board-row»>

{this.renderSquare(0)}

{this.renderSquare(1)}

{this.renderSquare(2)}

</div>

<div className=»board-row»>

{this.renderSquare(3)}

{this.renderSquare(4)}

{this.renderSquare(5)}

</div>

<div className=»board-row»>

{this.renderSquare(6)}

{this.renderSquare(7)}

{this.renderSquare(8)}

</div>

</div>

);

}

Затем нам нужно переместить handleClick метода handleClick из Board to Game. Вы можете вырезать его из класса Board и вставить его в класс Game.

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

handleClick(i) {

const history = this.state.history;

const current = history[history.length — 1];

const squares = current.squares.slice();

if (calculateWinner(squares) || squares[i]) {

return;

}

squares[i] = this.state.xIsNext ? ‘X’ : ‘O’;

this.setState({

history: history.concat([{

squares: squares,

}]),

xIsNext: !this.state.xIsNext,

});

}

На данный момент Board требуется только renderSquare и render; инициализация состояния и обработчик клика должны жить в игре.

Просмотр текущего кода.

Отображение движений

Покажем предыдущие шаги, сделанные в игре до сих пор. Ранее мы узнали, что элементы React являются первоклассными объектами JS, и мы можем их хранить или передавать. Чтобы отобразить несколько элементов в React, мы передаем массив элементов React. Самый распространенный способ построения этого массива — сопоставить массив данных. Давайте сделаем это в методе render игры:

render() {

const history = this.state.history;

const current = history[history.length — 1];

const winner = calculateWinner(current.squares);

 

const moves = history.map((step, move) => {

const desc = move ?

‘Go to move #’ + move :

‘Go to game start’;

return (

<li>

<button onClick={() => this.jumpTo(move)}>{desc}</button>

</li>

);

});

 

let status;

if (winner) {

status = ‘Winner: ‘ + winner;

} else {

status = ‘Next player: ‘ + (this.state.xIsNext ? ‘X’ : ‘O’);

}

 

return (

<div className=»game»>

<div className=»game-board»>

<Board

squares={current.squares}

onClick={(i) => this.handleClick(i)}

/>

</div>

<div className=»game-info»>

<div>{status}</div>

<ol>{moves}</ol>

</div>

</div>

);

}

Просмотр текущего кода.

Для каждого шага в истории мы создаем элемент списка <li> с кнопкой <button> внутри него, у которого есть обработчик кликов, который мы реализуем в ближайшее время. С помощью этого кода вы должны увидеть список ходов, которые были сделаны в игре, а также предупреждение, в котором говорится:

Предупреждение: каждый дочерний элемент в массиве или итераторе должен иметь уникальную «ключевую» опору. Проверьте метод рендеринга «Игра».

Давайте поговорим о том, что означает это предупреждение.

Ключи

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

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

Представьте, что переход от

<li>Alexa: 7 tasks left</li>

<li>Ben: 5 tasks left</li>

в

<li>Ben: 9 tasks left</li>

<li>Claudia: 8 tasks left</li>

<li>Alexa: 5 tasks left</li>

Человеческому глазу кажется, что Alexa и Ben поменялись местами, и Claudia была добавлена, но React — это просто компьютерная программа и не знает, что вы намеревались сделать. В результате React попросит вас указать свойство ключа для каждого элемента в списке, строку для дифференциации каждого компонента от своих братьев и сестер. В этом случае alexa , ben , claudia могут быть разумными ключами; если элементы соответствуют объектам в базе данных, идентификатор базы данных обычно является хорошим выбором:

<li key={user.id}>{user.name}: {user.taskCount} tasks left</li>

key — это специальное свойство, которое зарезервировано React (наряду с ref , более продвинутая функция). Когда элемент создается, React вытаскивает свойство key и сохраняет ключ непосредственно на возвращаемом элементе. Несмотря на то, что может показаться, что это часть реквизита, на нее нельзя ссылаться на this.props.key. React использует ключ автоматически при выборе детей для обновления; нет возможности для компонента узнать о его собственном ключе.

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

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

Если вы не укажете какой-либо ключ, React предупредит вас и вернется к использованию индекса массива в качестве ключа — это не правильный выбор, если вы когда-либо переупорядочиваете элементы в списке или добавляете / удаляете элементы в любом месте, кроме нижней части список. Явно передавая key={i} замалчивает предупреждение, но имеет такую ​​же проблему, поэтому в большинстве случаев не рекомендуется.

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

Внедрение путешествия во времени

Для нашего списка переходов у нас уже есть уникальный идентификатор для каждого шага: число шагов, когда это произошло. В методе render игры добавьте ключ как <li key={move}> и предупреждение ключа должно исчезнуть:

const moves = history.map((step, move) => {

const desc = move ?

‘Go to move #’ + move :

‘Go to game start’;

return (

<li key={move}>

<button onClick={() => this.jumpTo(move)}>{desc}</button>

</li>

);

});

Просмотр текущего кода.

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

Во-первых, добавить stepNumber: 0 в исходное состояние в игре constructor:

class Game extends React.Component {

constructor(props) {

super(props);

this.state = {

history: [{

squares: Array(9).fill(null),

}],

stepNumber: 0,

xIsNext: true,

};

}

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

Добавьте метод, называемый jumpTo к классу игры:

handleClick(i) {

// this method has not changed

}

 

jumpTo(step) {

this.setState({

stepNumber: step,

xIsNext: (step % 2) === 0,

});

}

 

render() {

// this method has not changed

}

Затем обновление, stepNumber когда новый ход делается путем добавления stepNumber: history.length к обновлению состояния в игре handleClick. Мы также обновлять, handleClick чтобы быть в курсе stepNumber при чтении текущего состояния доски, так что вы можете вернуться назад во времени, то нажмите на доске, чтобы создать новую запись :

handleClick(i) {

const history = this.state.history.slice(0, this.state.stepNumber + 1);

const current = history[history.length — 1];

const squares = current.squares.slice();

if (calculateWinner(squares) || squares[i]) {

return;

}

squares[i] = this.state.xIsNext ? ‘X’ : ‘O’;

this.setState({

history: history.concat([{

squares: squares

}]),

stepNumber: history.length,

xIsNext: !this.state.xIsNext,

});

}

Теперь вы можете изменить игры render для чтения из этого шага в истории:

render() {

const history = this.state.history;

const current = history[this.state.stepNumber];

const winner = calculateWinner(current.squares);

 

// the rest has not changed

Просмотр текущего кода.

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

Заключение

Теперь вы сделали крестики-нолики игры, что:

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

Хорошая работа! Мы надеемся, что теперь вы чувствуете, как у вас есть достойное понимание о том, как React работает.

Проверьте конечный результат здесь: Конечный результат.

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

  1. Отображение местоположения для каждого шага в формате (Col, строка) в списке истории двигаться.
  2. Жирный выбранный элемент в списке перемещения.
  3. Перепишите Board использовать две петли, чтобы сделать квадраты вместо жесткого кодирования их.
  4. Добавление кнопки переключения, что позволяет сортировать ходы в порядке возрастания или убывания.
  5. Когда кто-то выигрывает, выделить три квадрата, которые вызвали победу.

На протяжении всего этого урока мы коснулись ряда React понятий, включая элементы, компоненты, реквизита и государства. Для более подробного описания работы по каждой из этих тем, проверить остальную часть документации. Чтобы узнать больше об определении компонентов, проверьте React.Component ссылки API.

 

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *