Рубрики
Без рубрики

Хотите узнать больше о реагировании? Давайте построим – а потом играть – игра.

Обновление: Эта статья теперь является частью моей книги «Rect.js за пределы основы». Ударение обновленной версии этого контента и более о реагировании на jscomplete.com/react-beeyond-basics. Когда я преподаю реагировать на новичков, я начинаю Представляя их в API React. Тогда у меня есть их построить

Автор оригинала: FreeCodeCamp Community Member.

Когда я преподаю реагировать на новичков, я начинаю, введя их в API React. Тогда у меня есть их построить простую игру браузера после этого. Я думаю, что это хорошее введение стратегии, потому что простая игра обычно имеет небольшое состояние и, в большинстве случаев, в большинстве случаев зависимости данных вообще нет. Учащиеся полностью сосредоточены на самом AGIT API. Официальный оригинальный учебник React – это простой Игра Tic-Tac-Toe , который является отличным выбором.

Строительство простых игровых приложений Beats Apples Abstract (и Todo) на многих уровнях. Я всегда был против использования абстрактных видов примеров FOO-BAR, потому что им не хватает контекста и взаимодействия.

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

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

Я назвал игру, которую мы собираемся построить в этой статье Целевая сумма Отказ Это простой: вы начинаете с случайного числа в заголовке, цель (42 на скриншоте выше), а список случайных Вызов номера ниже этой цели (шесть чисел на скриншоте выше).

Четыре из шести случайных чисел, используемых выше (8, 5, 13, 16), добавляют точно к целевой сумме 42. Выбор правильного подмножества чисел – это то, как вы выигрываете игру.

Хотите играть в несколько раундов? Нажмите на Начать Кнопка ниже:

Вы могли выиграть? Я так плохо в этой игре.

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

Шаг № 1: Начальная разметка и стили

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

Чтобы сохранить эту статью максимально короткой и сосредоточенной на реакции, я начну с какой-то начальной готовной разметки и CSS. Вот сеанс кода JsComplete, который вы можете использовать для начала: jsdrops.com/rg-0

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

.game {  display: inline-flex; flex-direction: column;  align-items: center; width: 100%;}.target {  border: thin solid #999; width: 40%; height: 75px;  font-size: 45px; text-align: center; display: inline-block;  background-color: #ccc;}.challenge-numbers {  width: 85%; margin: 1rem auto;}.number {  border: thin solid lightgray; background-color: #eee;  width: 40%; text-align: center; font-size: 36px;  border-radius: 5px; margin: 1rem 5%; display: inline-block;}.footer {  display: flex; width: 90%; justify-content: space-between;  }.timer-value { color: darkgreen; font-size: 2rem; }

Я не очень хорош с CSS, и некоторые из моих выборов выше, вероятно, сомнительны. Не отвлекайтесь на это. У нас есть игра, чтобы построить.

Шаг № 2: Извлечение компонентов

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

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

Новые изменения, представленные в каждом фрагменте кода ниже, подчеркиваются в смелый Отказ

// Step #2
class Number extends React.Component {  render() {    return 
{this.props.value}
; }}
class Game extends React.Component {  render() {    return (      
42
10
); }}
ReactDOM.render(, document.getElementById('mountNode'));

Вы можете извлечь больше компонентов, таких как Цель или Таймер компонент. При добавлении таких компонентов могут повысить читаемость кода, я собираюсь сохранить пример простой и использовать только два компонента: Игра и Номер Отказ

Шаг № 3: Делать вещи динамически

Каждый раз, когда мы оказываем новую игру, нам нужно создать новый случайный целевой номер. Это просто. Мы можем использовать Math.random () Чтобы получить случайное число внутри Мин ... Макс Диапазон с использованием этой функции:

// Top-level function
const randomNumberBetween = (min, max) =>  Math.floor(Math.random() * (max - min + 1)) + min;

Если нам нужен целевой номер между 30 и 50 мы можем просто использовать RandomNumberbetween (30, 50) Отказ

Затем нам нужно создать шесть случаев случайных проблем. Я собираюсь исключить номер 1 Из этих чисел и, вероятно, не пойдет выше 9 для первого уровня. Это позволяет нам просто использовать RandomNumberBetween (2, 9) в цикле для создания всех номеров вызова. Легко, верно? ВЕРНО?

Этот набор чисел случайных проблем должен иметь подмножество, которое на самом деле суммы на случайное целевое число, которое мы создали. Мы не можем просто выбрать любое случайное число. Мы должны выбрать немного Факторы целевого номера (с некоторыми из результатов их факторизации), а затем еще несколько отвлекающих случайных чисел. Это трудно!

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

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

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

Это легче. Мы можем использовать Array.rom Чтобы создать массив случайных чисел с помощью RandomNumberBetween ни функция. Затем мы можем использовать Lodash Sampleize Метод для выбора случайного подмножества, а затем просто сумма, на котором подмножество и вызовите ее целью.

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

Вот модификации, которые нам нужны до сих пор:

// In the Game class
  challengeNumbers = Array    .from({ length: this.props.challengeSize })    .map(() => randomNumberBetween(...this.props.challengeRange));
  target = _.sampleSize(    this.challengeNumbers,    this.props.challengeSize - 2  ).reduce((acc, curr) => acc + curr, 0);
  render() {    return (      
{this.target}
{this.challengeNumbers.map((value, index) => )}
10
) }

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

Вы можете увидеть полный код, который мы имеем до сих пор здесь Отказ

Шаг № 4: решить, что происходит в состоянии

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

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

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

Все эти идентифицированные элементы состояния могут быть определены в состоянии « » Игра компонент. Номер Компонент не нуждается в состоянии.

Вот что нам нужно размещать на Игра Состояние компонента до сих пор:

// In the Game component
state = {  gameStatus: 'new' // new, playing, won, lost  remainingSeconds: this.props.initialSeconds,  selectedIds: [],};

Обратите внимание, как я сделал начальное значение для количества ОСТАЛЬНЫЕ ИЗМЕНЕНИЯ настраиваемый также. Я использовал новую опору на уровне игрового уровня ( Itationalseconds ) для этого:

Честно говоря, нам не нужны Гейместат быть в состоянии вообще. Это в основном вычисляется. Тем не менее, я намеренно делаю исключение, поместив его в состояние как упрощенную форму кэширование этот вычислений.

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

Как насчет цвета фона, используемые для целевого номера, когда игрок выигрывает или теряет игру? Нужно ли пойти на государство?

Не совсем. Так как у нас есть Гейместат Элемент, мы можем использовать это, чтобы найти правильный цвет фона. Словарь фоновых цветов может быть простым статическим Игра Свойство (или вы можете передать его, если вы хотите сделать его настраиваемым):

// In the Game component
  static bgColors = {    playing: '#ccc',    won: 'green',    lost: 'red',  };

Вы можете увидеть полный код, который мы имеем до сих пор здесь Отказ

Шаг № 5: Проектирование просмотров как функции данных и состояния

Это действительно ядро реагирования. Теперь, когда мы определили все данные и укажите эти игровые потребности, мы можем разработать весь интерфейс на основе них.

Поскольку состояние обычно начинается с пустых значений (например, пустой Выполненные файлы Array), трудно разработать UI без тестирования фактических значений. Тем не менее, значения издевательства могут быть использованы для облегчения тестирования:

// Mock states:
state = {  gameStatus: 'playing',  remainingSeconds: 7,  selectedIds: [0, 3, 4],};
// Also test with  gameStatus: 'lost'
// And  gameStatus: 'won'

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

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

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

  • Делает Номер компонент должен знать о ВыполнителиДидс Массив, чтобы выяснить, является ли это выбранное число?
  • Делает Номер Компонент должен знать о текущем Гейместат стоимость?

Я признаю, что ответы на эти вопросы не так просто, как вы можете подумать. Хотя вы можете быть соблазнены ответить «да» для обоих Номер Компонент не должен знать оба ВыполнителиДидс и Гейместат Отказ Это нужно только быть в курсе того, можно ли его нажать. Если его нельзя нажать, ему нужно будет отображать себя по-другому.

Проезжая что-нибудь еще для Номер Компонент заставит его повторно рендерировать излишне, что то, что мы должны избегать.

Мы можем использовать более низкую непрозрачность для представления неточный номер. Давайте сделаем Номер Компонент получают кликабельный пропры

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

  • Если мы пройдем Гейместат ценность для Номер Компонент, потом каждый раз Гейместат Изменения (например, из играют на Won ), Rect Ref Re-Render все шесть номеров вызова. Но в этом случае на самом деле не нужно было повторно сделать любой из них.
  • Номер компонента должен повторно рендер, когда Гейместат изменения с Новый к играть Из-за маскирующих вопросительных знаков функция в начале. Чтобы не пройти от Гейместат к Номер Мы можем вычислить значение, отображаемое в Номер Компонент внутри карта Функция обратного вызова в Игра компонент.
  • Если мы пройдем ВыполнителиДидс массив до Номер Компонент, затем на каждом щелчке RECT RECT RE-READE все шесть номеров вызовов, когда это необходимо только для повторного визуализации одного номера. Вот почему кликабельный Логический флаг гораздо лучший выбор здесь.

С каждым предприметм вы переходите к ребенку React Component, приходит большая ответственность.

Это важнее, чем вы думаете. Однако реагирование не будет оптимизировать переработки компонента автоматически. Нам придется решить, хочу ли мы это сделать это. Это обсуждается на шаге № 8 ниже.

Помимо кликабельный опоры, что еще делает Номер Компонент нужен? Поскольку он будет нажат, и нам нужно разместить идентификатор номера нажатия на Игра Состояние, обработчик кликов каждого Номер Компонент должен знать свой собственный идентификатор. И мы не можем использовать React’s ключ Опытное значение в этом случае. Давайте сделаем Номер Компонент получают ID оперировать.

// In the Number component
render() {    return (      
console.log(this.props.id)} > {this.props.value}
); }

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

// In the Game classisNumberAvailable = (numberIndex) =>    this.state.selectedIds.indexOf(numberIndex) === -1;

Одним из поведений, которые вы, вероятно, заметили, когда играете в игру выше, состоит в том, что цифровые квадраты начинают отображать знак вопроса до нажатия кнопки «Пуск». Мы можем использовать тройной оператор для контроля значения каждого Номер Компонент на основе Гейместат стоимость. Вот что нам нужно изменить, чтобы сделать Номер Компонент внутри карта вызов:

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

{this.state.gameStatus === 'new' ? '?' : this.target}

Наконец, мы должны показать Начать кнопка только когда Гейместат это Новый Отказ В противном случае мы должны просто показать ОСТАЛЬНЫЕ ИЗМЕНЕНИЯ прилавок. Когда игра – выиграл или потерял Давайте покажем Играть снова кнопка. Вот модификации, которые нам нужны для всего этого:

{this.state.gameStatus === 'new' ? ( ) : (
{this.state.remainingSeconds}
)} {['won', 'lost'].includes(this.state.gameStatus) && ( <;button>Play Again )}

Вы можете увидеть полный код, который мы имеем до сих пор здесь Отказ

Шаг № 6: Проектирование поведения для изменения состояния

Первое поведение, которое нам нужно выяснить, – это как начать игру. Здесь нам нужны две основные действия: 1) Изменить Гейместат к играть и 2) начать таймер, чтобы уменьшить ОСТАЛЬНЫЕ ИЗМЕНЕНИЯ стоимость.

Если ОСТАЛЬНЫЕ ИЗМЕНЕНИЯ уменьшается весь путь к нулю, нам нужно заставить игру в потерял Состояние и остановите таймер. В противном случае он будет уменьшаться за ноль.

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

// In the Game class
startGame = () => {  this.setState({ gameStatus: 'playing' }, () => {    this.intervalId = setInterval(() => {      this.setState((prevState) => {        const newRemainingSeconds = prevState.remainingSeconds - 1;        if (newRemainingSeconds === 0) {          clearInterval(this.intervalId);          return { gameStatus: 'lost', remainingSeconds: 0 };        }        return { remainingSeconds: newRemainingSeconds };      });    }, 1000);  });};

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

Далее давайте выясним, что должно произойти, когда ряд нажат во время игры на игровой сессии. Давайте создадим Выборщик . функция для этого. Эта функция должна получить идентификатор номера нажатия и должен работать только тогда, когда Гейместат это играть Отказ Каждый раз, когда ряд нажат, нам нужно добавить свой идентификатор на ВыполнителиДидс множество.

Нам также нужно вычислить новый Гейместат Потому что каждый клик может привести к выиграл / потерял статус. Давайте создадим CalcGamestatus функция сделать это.

Вот один из способов реализации этих двух новых функций:

// In the Game class
selectNumber = (numberIndex) => {  if (this.state.gameStatus !== 'playing') {    return;  }  this.setState(    (prevState) => ({      selectedIds: [...prevState.selectedIds, numberIndex],      gameStatus: this.calcGameStatus([        ...prevState.selectedIds,        numberIndex,      ]),    }),    () => {      if (this.state.gameStatus !== 'playing') {        clearInterval(this.intervalId);      }    }  );};
calcGameStatus = (selectedIds) => {  const sumSelected = selectedIds.reduce(    (acc, curr) => acc + this.challengeNumbers[curr],    0  );  if (sumSelected < this.target) {    return 'playing';  }  return sumSelected === this.target ? 'won' : 'lost';};

Обратите внимание на несколько вещей о функциях выше:

  • Мы использовали массив Распространение оператора добавить Numberindex к ВыполнителиДидс Отказ Это удобный трюк, чтобы избежать мутации оригинального массива.
  • С новым Гейместат должен быть вычислен в то время как Мы обновляем государство, я передал новый ВыполнителиДидс ценность для CalcGamestatus функция, а не использовать текущий ВыполнителиДидс стоимость. Он еще не обновлен, чтобы включить новый Numberindex на этой точке.
  • В CalcGamestatus Я использовал Уменьшить вызов. Это вычисляет текущую сумму после щелчка с использованием комбинации того, что выбрано и оригинал CallengeNumbers Массив, который содержит фактические значения чисел. Затем несколько условных условий могут сделать трюк определения текущего состояния игры.
  • Так как таймер должен быть остановлен, если новый Гейместат не играть Я использовал второй аргумент обратного вызова для SetState реализовать эту логику. Это гарантирует, что это будет использовать новый Гейместат После асинхронизации SetState звонок сделан.

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

Теперь, как именно мы собираемся реализовать это Играть снова действие? Можем ли мы просто просто сбросить состояние Игра компонент?

Неа. Подумайте о том, почему.

Шаг № 7: Сброс реактивный компонент

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

Мы, безусловно, можем улучшить startgame Функция делать все это. Но RECT предлагает более простым способом сбросить компонент: Размонтируйте этот компонент и просто вернутся. Это будет вызвать весь код инициализации и позаботиться о любых таймерах.

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

// In the Game class
  componentWillUnmount() {    clearInterval(this.intervalId);  }

Теперь, если Игра Компонент не размонтируется и восстановлен, он начнет совершенно свежий экземпляр с новыми случайными числами и пустым состоянием. Однако для восстановления компонента на основе поведения нам нужно будет представить новый родительский компонент для Игра Отказ Мы назовем это Приложение Отказ Затем мы поставим что-то в состоянии этого нового родительского компонента, который будет вызвать изменение интерфейса.

React имеет еще один полезный трюк, который мы можем использовать для достижения этой задачи. Если какой-либо реактивный компонент отображается с определенным ключ А позже повторно оказано с другим ключ Реакция видит совершенно новый экземпляр. Затем он автоматически размонтирует и повторно устанавливает этот компонент!

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

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

Вот модификации, которые нам нужны:

// Create new App component
class App extends React.Component {  state = {    gameId: 1,  };
resetGame = () =>    this.setState((prevState) => ({      gameId: prevState.gameId + 1,    }));
  render() {    return (       1}        challengeSize={6}        challengeRange={[2, 9]}        initialSeconds={10}        onPlayAgain={this.resetGame}      />    );  }}
// In the Game class: respect the value of the new autoPlay prop  componentDidMount() {    if (this.props.autoPlay) {      this.startGame();    }  }
// In the Game render call// Wire the Play Again action using the parent prop
// Render the new App component instead of GameReactDOM.render(, document.getElementById('mountNode'));

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

Шаг № 8: Оптимизировать, если вы можете измерить

Одним из сложных аспектов приложения React Application позволяет избежать расточительного рендеринга компонентов, которые не должны быть восстановлены. Мы пошли на большую длину на шаге # 5, чтобы не пройти от опоры, которые приведут Номер компонент для восстановления без необходимости.

Тем не менее, код, как это сейчас, все еще расточительно повторно рендеринг большинства Номер составные части. Чтобы увидеть это в действии, используйте ComponentWillUpdate Метод в Номер Компонент и просто console.log что-то там:

// In the Number componentcomponentWillUpdate() {  console.log('Number Updated');}

Тогда иди и играй. На каждом государстве изменение в Игра Компонент, вы увидите, что мы повторно рендерируем все 6 Номер составные части. Это происходит, когда мы нажимаем на Начать Кнопка и каждую секунду после этого!

Дело в том, что Номер Компонент не должен перенаправлять себя, если только плеер нажимает на него. 60 Повторные рендеры, которые были вызваны изменением таймера были расточительными. Кроме того, когда игрок нажимает номер, только этот номер должен быть переназначен. Прямо сейчас React также переиздается все шесть номеров, когда игрок выбирает любой номер.

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

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

Рейкт PURECOMPONENT Класс сделает именно это. Идите вперед и измените Номер Компонент для расширения Rect.Purecomponent вместо Ract.component И посмотрите, как проблема волшебным образом уходит.

class Number extends React.PureComponent

Тем не менее, эта оптимизация стоит того? Мы не можем ответить на этот вопрос без измерения. По сути, вам необходимо измерить, какой код использует меньше ресурсов: компонент рендеринга вызова или Если Заявление в Rect.Purecomponent что сравнивает предыдущее и следующее состояние/реквизиты. Это полностью зависит от размеров деревьев состояния/реквизит и сложности того, что повторно оказывается. Не просто предположим, что один путь лучше другого.

Вы можете увидеть последний код здесь Отказ MVP завершен. Теперь, для любви к CSS, может кто-то, пожалуйста, стиль эту игру, чтобы она обратилась к детям?:)

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

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

Игра целевой суммы была представлена в моем Реагируйте на родном важном учебном курсе, который доступен на Линда и LinkedIn Обучение Отказ

Спасибо за чтение.

Изучение реагировать или узел? Оформить заказ моих книг: