Русский
Русский
English
Статистика
Реклама

YAHW на React

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

Создание первого приложения

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

node --version v10.24.0 npm --version6.14.11npx --version10.2.2

Собственно создание приложения выполняется простой командой

npx create-react-app hw-app

где hw-app (helloworld-application) -- имя приложения.

В текущей папке будет создана папка с именем hw-app, содержащая все необходимое для запуска React приложения. Чтобы проверить его работу нужно зайти внутрь (cd hw-app) и запустить приложение.

npm start

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

You can now view hw-app in the browser.http://localhost:3000Note that the development build is not optimized.To create a production build, use npm run build.

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

Что бы остановить приложение (если захотим) нажмем Ctr-C. Посмотрим на содержимое папки приложения-проекта.

lsREADME.md  node_modules  package-lock.json  package.json  public  src

На данном этапе нас будут интересовать папки public и src.

ls publicfavicon.ico  index.html  logo192.png  logo512.png  manifest.json  robots.txtls srcApp.css  App.js  App.test.js  index.css  index.js  logo.svg  reportWebVitals.js  setupTests.js</pre></code>

В папке public лежит index.html, который и будет отдаваться dev-сервером в ответ на запрос браузера. В свою очередь в index.html есть div элемент с id='root', в который React "отрисует" приложение, как это указано в файле src/index.js

ReactDOM.render(
<React.StrictMode>
<App/>
</React.StrictMode>,
document.getElementById('root')
);

и отрисует он там компонент App. Чтобы рассмотреть немного ближе, как это работает изменим файлы index.html и App.js. Пусть браузeр в заголовке показывает название именно нашего приложения. Изменим соответствующую строку было

<title>ReactApp</title>

стало

<title>MyFirstReactApp</title>

А компонент App пусть покажет наш контент (содержимое файла App.js) было:

importlogofrom'./logo.svg';
import'./App.css';

functionApp(){
return(
<divclassName="App">
<headerclassName="App-header">
<imgsrc={logo}className="App-logo"alt="logo"/>
<p>
Edit<code>src/App.js</code>andsavetoreload.
</p>
<a
className="App-link"
href="http://personeltest.ru/aways/reactjs.org"
target="_blank"
rel="noopenernoreferrer"
>
LearnReact
</a>
</header>
</div>);}

стало:

functionApp(){
return(
<div>
<h1>HelloReact!</h1>
</div>
);
}
exportdefaultApp;

также из папки src можно удалить все неиспользуемые файлы в данный момент файлы. Содержимое папки должно быть таким

ls src/App.js index.js

По необходимости мы будем добавлять нужные файлы сами и узнаем зачем мы это делаем. А пока посмотрим на результат.

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

В React мы работаем с компонентами. В данном и самом простом случае компонент - это функция написанная на JavaScript и возвращающая код, похожий на разметку html. Похожий, но являющийся на деле кодом JSX из которого html получается в результате компиляции. Мы не будем вносить изменения в файл App.js пусть он остается корневым компонентом нашего в будущем интерактивного приложения, в котором мы расположим написанные нами компоненты.

Не забудем убрать лишние строки из src/index.js, он примет вид

importReactfrom'react';
importReactDOMfrom'react-dom';
importAppfrom'./App';

ReactDOM.render(
<React.StrictMode>
<App/>
</React.StrictMode>,
document.getElementById('root')
);

Следует сказать, что компоненты могут быть функциональными -- просто функция, возвращающая представление компонента и "классовыми" -- это компонент описываемый JS классом, имеющий внутреннее состояние и дополнительный функционал. Сказанное не означает, что функциональные компоненты не могут иметь внутреннего состояния, но об этом позже. Добавим в наше приложение два компонента, один функциональный и один в виде класса. Оба будут иметь состояния, что-то о себе помнить и взаимодействовать с пользователем.

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

ClickCounter - функция

Первая итерация. Это содержимое файла ClickCounter.js

constclickCounter=()=>{
constclickTimes=0;
return(
<div>
<p>Ясчетчиккликов</p>
<p>Кликнуто{clickTimes}раз</p>
</div>
);
}
exportdefaultclickCounter;

А вот так изменился App.js.

importClickCounterfrom'./ClickCounter';

functionApp(){
return(
<div>
<h1>HelloReact!</h1>
<ClickCounter/>
</div>
);
}
exportdefaultApp;

В первой строке импортируем наш ClickCounter и добавляем его в отрисовку после тега "Hello React!". И да, новый компонент будет выглядеть в коде как еще один новый тэг. Тут наверное может возникнуть вопрос об именовании компонентов, в файле ClickCounter.js экспортируем clickCounter, в App.js импортируем ClickCounter, что за дела? Мы можем импортировать хоть MySupperPupperClickCounter из './ClickCounter' и использовать его как <MySupperPupperClickCounter /> , но получим все равно вывод функции clickCounter(), которая экспортируется по дефолту. (попробуйте)

Итого: после слова import стоит имя компонента которое мы будем использовать далее в файле (в данном случае в файле App.js) после слова from стоит имя файла с относительным путем, но без расширения '.js'.

Что же написано в СlickCounter.js? Определена константа с именем clickCounter, которой присваивается '=' функция без параметров '()' выполняющая код написанный в теле '{}', там же определяется переменная clickTimes. Значение этой переменной будет появляться в строке

<p>Кликнуто{clickTimes}раз</p>

где имя переменной обернуто в фигурные скобки. Помним, что это JSX и после компиляции мы увидим "Кликнуто 0 раз" как на рисунке.

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

const [clickTimes, clickIncrement] = useState(0)

useState(0) - принимает в качестве параметра инициирующее значение для переменной состояния clickTimes его же и возвращает как первый элемент массива.
clickUpdater - функция, которая будет обновлять значение переменной состояния своим параметром.

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

Таким образом файл ClickCounter.js становится таким:

importReact,{useState}from'react';

constClickCounter=()=>{
const[clickTimes,clickUpdater]=useState(0);

constclickIncrementer=()=>{
returnclickTimes+1;
}

return(
<divonClick={()=>clickUpdater(clickIncrementer)}>
<p>Ясчетчиккликов</p>
<p>Кликнуто{clickTimes}раз</p>
</div>
);
}
exportdefaultClickCounter;

Обратите внимание, ClickCounter мы теперь пишем с большой буквы -- это требование к именованию функциональных компонентов. Теперь, кликая по тексту элемента будем наблюдать возрастающее число кликов.

Machine - class

Поехали дальше. На очереди компонент класс. Назовем этот класс Machine и опишем в файле Machine.js. Класс компонента обязан реализовать функцию render(), которая будет вызываться для отображения компонента в браузере. Сначала просто нарисуем нужные нам элементы с нужными значениями. Вот полный текст файла.

importReactfrom'react';

classMachineextendsReact.Component{

state={
machineState:'STOPPED',
machineStarted:0
}

render(){
return(
<div>
<p>Яинтерфейсмашины.</p>
<p>Состояниемашины:{this.state.machineState}.
<br/>
<buttononClick={this.clickButtonHandler}>{this.state.buttonLabel}</button>
</p>
Машинузапускали{this.state.machineStartCount}раз.
</div>
);
}
}

exportdefaultMachine;

Как видим, render() возвращает не что иное, как JS код, и весь этот код должен быть обрамлен одним <div>(<span> или даже <>), для функционального компонента требование то же. В файл App.js добавим строку для импорта

import Machine from './Machine';

и строку для отображения

<Machine />

Полный текст файла

importClickCounterfrom'./ClickCounter';
importMachinefrom'./Machine';

functionApp(){
return(
<div>
<h1>HelloReact!</h1>
<ClickCounter/>
<Machine/>
</div>
);
}
exportdefaultApp;

Что увидим в браузере показано на рисунке. Пока, конечно -- каша. Но совсем скоро мы ее поправим.

А пока обсудим содержимое файла Machine.js. В первой строке import React. Обратите внимание, мы не использовали никаких выражений в фигурных скобках. Именно поэтому мы пишем extends React.Component.

Если бы написалиimport React, { Component } from 'react' , то можно было бы сказать extends Component.

Далее, появилось объявление и инициализация объекта state. Именно в нем будет храниться изменяемая информация (состояние), связанная с объектом класса. Обращаться к этому объекту нужно с использованием указателя this. Понятное дело, что объект state может быть объемнее и сложнее, чем в нашем случае.

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

Состояние машины -- STOPPED, на кнопке написано START.Состояние машины -- STARTED, на кнопке написано STOP.Счетчик стартов будет считать количество нажатий на кнопку с надписью START.

При работе с состоянием state надо знать, изменять состояние нужно только специальной функцией setState(), при этом фактически будет создано и сохранено новое состояние с новыми значениями. Попробуем на практике, при нажатии на кнопку поменяем соответствующие надписи. Для этого напишем функцию clickButtonHandler.

clickButtonHandler=()=>{
switch(this.state.machineState){
case'STOPPED':
constcnt=this.state.machineStartCount;
this.setState({machineState:'STARTED',
buttonLabel:'STOP',
machineStartCount:cnt+1});
break;
case'STARTED':
this.setState({machineState:'STOPPED',
buttonLabel:'START'});
break;
default:
break;
}
}

Код простой и пояснений не требует, кроме наверное одного нюанса (я на этом застрял). Речь идет о наличии константы const cnt = this.state.machineStartCount. Почему бы не написать просто machineStartCount: this.state.machineStartCount + 1? Нельзя. Нельзя использовать непосредственно состояние компонента для создания нового состояния. Просто. Также нужно обратить внимание, что в одном случае мы создаем полностью новое состояние, а в другом, только только его части, не обновляем значение счетчика. setState() правильно обработает ситуацию, она обновит указанные поля и оставит не тронутыми те, о которых умолчали.

Теперь добавим эту функцию в обработчик клика для кнопки.

<buttononClick={this.clickButtonHandler}>{this.state.buttonLabel}</button>

Все работает, но выглядит все еще так себе.

Добавим стиля

Громко сказано, просто отделим компоненты друг от друга и покажем их границы. Все описание запишем в файл src/App.css и импортируем его в src/App.js строкой

import'./App.css';

А это содержимое файла App.css :

.Component {      margin: 20px;      border: 1px solid #eee;      box-shadow: 5px 5px 10px #ccc;      padding: 10px;    }

Чтобы применить стиль к компоненту надо в тэге div добавить className вот так:

<div className='Component'>

Посмотрим, что получилось.

"Пропсы"

Не могу понять, почему не использовать 'свойства' или 'параметры', ведь очень похоже на передачу параметров при создании компонента. Может быть, при создании нового сообщества React-истов(еров) потребовался новый слэнг? В общем, следуем официальному сайту.

Мы рассмотрели state - структуру, которая хранит информацию о компоненте и которую мы можем изменять в жизненном цикле компонента, изменения инициируются компонентом. Теперь мы рассмотрим props.

Это данные, которые родитель компонента может передать дочернему компоненту. "Пропсы" (вроде как устоявшийся термин) не изменяются в течение срока жизни компонента самим компонентом, для него это "константы", но которые компонент может прочитать. Поcмотрим как это работает.

Представим себе, что нам нужно создать две машины, два компонента Machine, которые будут отличаться именем-обозначением, в остальном экземпляры компонента будут одинаковы. Добавим дополнительную информацию с использованием props. Создадим в App две машины с именами "Первая машина" и "Вторая машина" следующим образом

<Machinename='Перваямашина'/>
<Machinename='Втораямашина'/>

name и будет тем самым пропсом. Теперь изменим функцию render() компонента.

render(){
return(
<divclassName='Component'>
{this.props.name}
<p>Яинтерфейсмашины.</p>
<p>Состояниемашины:{this.state.machineState}.
<br/>
<buttononClick={this.clickButtonHandler}>{this.state.buttonLabel}</button>
</p>
Машинузапускали{this.state.machineStartCount}раз.
</div>
);
}

Обратим внимание на строку после div. Теперь мы имеем на странице два независимых однотипных компонента с разными состояниями. Смотрим на рисунок.

Это самый простой случай использования пропса. В игре "Сапер" рассмотрим еще. Промежуточный итог таков:

  • мы научились создавать простое приложение React;

  • мы научились создавать компоненты React;

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

  • узнали что такое state и props

На очереди -- создание игры "Сапер". Рассмотрим создание более сложных компонентов, посмотрим как можно устроить взаимодействие между компонентами. Для реализации логики игры сначала поработаем с состояниями компонентов, во второй итерации посмотрим как хранить состояние всего приложения и работать с ним с использованием redux. Приступим.

Игра "Сапер"

Все знают игру "Сапер", нужно на поле размера M x N найти X мин. Сделаем такую. Интерфейс игры показан на рисунке.

Здесь есть два "больших" компонента:

  • компонент с "измерительными приборами" (счетчик мин и секундомер) ControlPanel

  • компонент "игровое поле" с множеством кликабельных компонентов, квадратов с минами или безMineField

Оговорим правила, может быть еще раз.

  • перед игроком поле m-строк, n-колонок

  • игрок кликает по любой ячейке левой кнопкой мыши

    • если это первый клик за игру, то запускается секундомер

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

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

    • если игрок кликнет по ячейке с миной, таймер останавливается, показываются все мины (их места расположения), игра окончена

  • игрок кликает по закрытой ячейке правой кнопкой мыши - ячейка помечается как заминированная, повторный клик снимает метку, при этом увеличивается или уменьшается счетчик мин

  • если откроются все свободные ячейки - секундомер останавливается, игра окончена

Родительский компонент Game

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

  • конечно создание тех двух

  • запуск счетчика секунд после первого клика пользователя по ячейке

  • остановка его после "нахождения" мины (проигрыш) или после открытия всех свободных от мин ячеек (выигрыш)

  • оповещение пользователя о завершении игры.

Game хранит свое состояние вот в таком объекте:

state={
flagCnt:0,
seconds:0
};

Здесь: flagCnt -- счетчик флажков на минном поле, seconds -- прошло секунд с начала игры. Но ведь счетчик флажков и прошедших секунд отображается на компоненте ControlPnael , как же происходит передача данных? Посмотрим.

Компонент ControlPnael

Вот так Game создает ControlPanel

<ControlPanel
flagCnt={this.state.flagCnt}
seconds={this.state.seconds}
/>

А вот так будет выглядеть код компонента ControlPanel. Он совсем короткий и я привожу его весь.

constzeroPad=(num,places)=>String(num).padStart(places,'0');

constcontrolPanel=(props)=>{
constmin=Math.floor(props.seconds/60);
constsecs=props.seconds%60;

return(
<divclassName='Control'
style={{color:'#adadad'}}
>
Flagcount:{zeroPad(props.flagCnt)}Time:{zeroPad(min,2)}:{zeroPad(secs,2)}
</div>
);
}

exportdefaultcontrolPanel;

Функциональный компонент создается с параметром props, Game в пропсах указывает имена переменных (своих переменных) this.state.flagCnt и this.state.seconds, а ControlPanel использует при рендеринге имена пропсов flagCnt и seconds. Просто? Сам компонент не изменяет значения пропсов, за него это делает родительский компонент, причем делает это с использованием setState() (помним, что эта функция используется для изменения state компонента). А так как setState инициирует перерисовку самого компонента и его дочек, то мы увидим изменяющиеся значения на ControlPanel.

Как данные спускаются от родительского компонента в дочерний мы увидели, теперь посмотрим как данные "поднимаются" от дочернего компонента к родительскому. Такой фокус происходит при взаимодействии Game и MineField .

Взаимодействие Game и MineField

При создании компонента MineField используется следующий код в Game :

<MineField
rows='8'
cols='8'
mines='10'
gameStarted={this.startGame}
gameOver={this.stopGame}
changeFlagCount={this.setFlag}
/>

Имена пропсов, наверное, говорящие: rows -- количество строк ячеек, cols -- количество колонок, mines -- количество мин, с этими значения MineField построит минное поле. Теперь о передаче данных "наверх". Как Game узнАет о том, что игра началась, что был уже клик по ячейке? Просто, MineField вызовет в своем коде функцию gameSarted -- это имя пропса, но выполнится код функции startGame в пространстве класса Game -- это его функция. Это приведет по цепочке к ежесекундному запуску функции tick(), изменяющей значение seconds, до тех пор пока MineField не вызовет функцию gameOver(true|false), при этом вызовется функция stopGame в классе Game. Game покажет alert с сообщением о выигрыше или проигрыше в зависимости от переданного (поднятого?)из MineField параметра. Вот часть кода Game (собственно, почти весь код):

start=()=>{
this.timerID=setInterval(()=>this.tick(),1000);
}

stop=()=>{
clearInterval(this.timerID)
}

tick(){
constoldsec=this.state.seconds;
this.setState({seconds:oldsec+1});
}

startGame=()=>{
this.start();
}

stopGame=(isGameWon)=>{
if(isGameWon){
alert("Youwin");
}else{
alert("Youlose");
}
this.stop();
}

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

Функционал ячеек прост и описывается он в классе с оригинальным названием Cell. Первое и основное, что должна сделать ячейка -- это отрисовать себя в соответствии со своим состоянием: закрыта, открыта, помечена флагом. Тут нужно добавить, что состояние ячейки не является хранимым в ячейке состоянием, а опять же передается ей через пропс от MineField.

render(){
constcellSize=40;//px
varwidth=cellSize+'px';
varheight=cellSize+'px';
varleft=this.props.col(cellSize+4)+'px'; vartop=this.props.row(cellSize+4)+'px';
letbackgroundColor=this.props.opened?'#adadad':'#501b1d';

varrendstate=()=>{
if(this.props.checked){
return(
<imgclassName='flag'src={flag}alt=''/>
)
}
if(this.props.opened){
return(
this.props.hasBomb?<imgclassName='bomb'src={bomb}alt=''/>:(this.props.bombNbr>0?this.props.bombNbr:''));
}
}

return(
<divclassName='Cell'
style={{width,height,left,top,backgroundColor}}
onClick={this.leftClickHandler}
onContextMenu={this.rightClickHandler}
>
{rendstate()}
</div>
);
}

Да, в этой функции много "магических" чисел (40, 4, 4), наверное код можно было бы сделать чище. Но по именам переменных наверное все понятно: cellSize -- длина стороны ячейки, чтобы хоть как-то уменьшить количество безымянных чисел. width, height -- высота ширина top, left -- координаты верхнего левого угла ячейки на поле, вычисляются в зависимости от номера строки и колонки, передаваемых от MineField в пропсах. bomb, flag -- импортированные из файлов рисунки. Итого, чтобы ячейка себя правильно отрисовала MineField передает ей следующие пропсы:

  • col -- колонка

  • row -- строка

  • checked -- помечена флагом

  • opened -- открыта

  • bombNbr -- сколько мин (бомб) в соседних ячейках

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

leftClickHandler=()=>{
this.props.clickLeft(this.props.row,this.props.col);
}

rightClickHandler=(e)=>{
e.preventDefault();
this.props.clickRight(this.props.row,this.props.col);
}

Как видите, вся работа этих функций заключается только в том, чтобы передать "наверх" координаты ячейки, про которой кликнули. После клика, вызываются переданные в пропсах функции, работа которых произойдет в классе MineField и после обработки результат спустится вниз, назад в ячейку в виде измененного пропса opened или checked.

Класс MineField

Это самый "функционально насыщенный и сложный" класс, весь код составляет немногим более 200 строк. Класс имеет конструктор, в котором создается массив размера rows x cols содержащий элементы типа cellData . Вот описание cellData.

classcellData{
constructor(row,col){
this.row=row;
this.col=col;
this.hasBomb=false;
this.checked=false;
this.opened=false;
this.bombNbr=0;
this.nbrs=[...Array(0)];
}
}

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

constructor(props){
super(props);
this.closedCells=props.rows*props.cols-props.mines;
this.flagCount=0;
this.state={
field:this.createMap(this.props.rows,this.props.cols,this.props.mines),
gameState:'waiting',
}
}

В поле состояния state.field мы сохраняем карту минного поля, создаваемого функцией createMap(this.props.rows, this.props.cols, this.props.mines) . Если посмотреть на код (ссылка на полный код игры в конце статьи) создания минного поля, то можно увидеть может быть не совсем оптимальное наполнение поля минами и расчет соседей с минами и заполнение списка соседей, в общем, несколько проходов по одному и тому же массиву, но читается легко (наверное). Что же, вот и код отрисовки компонента MineField и создания при этом компонентов ячеек с нужными пропсами, считаем при этом что все данные для ячеек лежат в соответствующем массиве:

render(){
return(
<divclassName='MineField'>
{
this.state.field.map(function(row){
returnrow.map(function(cell){
return(
<Cell
row={cell.row}
col={cell.col}
hasBomb={cell.hasBomb}
bombNbr={cell.bombNbr}
key={cell.row+"-"+cell.col}
checked={cell.checked}
opened={cell.opened}
clickLeft={this.cellLeftClicked}
clickRight={this.cellRightClicked}
/>
)
},this);
},this)
}
</div>
);
}

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

cellLeftClicked=(row,col)=>{
switch(this.state.gameState){
case'waiting':
this.props.gameStarted();
this.setState({gameState:'started'});
/*fallsthrough*/
case'started':
letnewField=[...this.state.field];
this.openCell(newField,row,col);
this.setState({field:newField});
break;
case'finished':
break;
default:
break;
}
}

cellRightClicked=(row,col)=>{
switch(this.state.gameState){
case'waiting':
break;
case'started':
if(this.state.field[row][col].opened){
break;
}
letnewField=[...this.state.field];
letflagCntDiff=newField[row][col].checked?-1:1;
if((this.flagCount+flagCntDiff)<0||(this.flagCount+flagCntDiff)>this.props.mines){
break;
}
this.flagCount+=flagCntDiff;
this.props.changeFlagCount(flagCntDiff);
newField[row][col].checked=!newField[row][col].checked;
this.setState({field:newField});
break;
case'finished':
break;
default:
break;
}
}

Как видно, игра перемещается из состояния в состояние waiting -> started -> finished . waiting -- это состояние сразу после загрузки страницы. В состояние started мы перемещаемся после открытия первой ячейки, и в состояние finished после открытия ячейки с миной или открытия всех свободных ячеек. За открытие ячеек отвечает функция openCell() , она рекурсивно вызывает себя для открытия соседних ячеек, которые не граничат заминированными ячейками. В состоянии finished мы перестаем реагировать на действия пользователя, игру (страницу) нужно перезагрузить.

Еще раз хотелось бы обратить внимание на то, как происходит работа с минным полем -- state.field . В обработчике клика мышкой мы создаём копию поля. Производим с ним необходимые манипуляции и потом, с помощью setState() устанавливаем новое состояние с новым, обновленным полем.

Вот и все

Полный код приложений можно найти по ссылке.

В этой статье я хотел показать способы взаимодействия, обмена данными
между компонентами в приложении React, надеюсь получилось. Хотя могло получиться и так, что какие-то вещи уже очевидные для себя сейчас не рассказал. Я также писал здесь в тексте, что расскажу на примере этой же игры про применение redux , но наверное она (игра) того не стоит. Если статья вызовет интерес, сделаем какую-нибудь инфографику с биржи, поучимся вместе использовать графические библиотеки и вот тут дойдет время для redux , будет к месту, наверное. А теперь, всем всего хорошего!

Источник: habr.com
К списку статей
Опубликовано: 30.03.2021 20:09:12
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Программирование

Java

Reactjs

React

Component

Properties

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru