Продвинутые концепции JavaScript

Сидя за компьютером, я думал о создании небольшого приложения, которое бы использовало некоторые продвинутые концепции JavaScript. В частности, я думал о реальных реализациях throttle и debounce.

Я создал приложение, которое использует логику throttle, но также продемонстрирует некоторые сложные для маневрирования веб-приложения, такие как setTimeout и setInterval.

Если это вызвало у вас интерес… пожалуйста, читайте дальше.

TLDR

Для тех из вас, кто не любит читать, вот логика работы приложения

const GetScores = () => {
  const [homeScore, setHomeScore] = useState(0);
  const [awayScore, setAwayScore] = useState(0);

  const updateScore = () => {
    const scoreOptions = [0, 2, 3];
    const randomNum = () => Math.floor(Math.random() * 3);

    setHomeScore(homeScore + scoreOptions[randomNum()]);
    setAwayScore(awayScore + scoreOptions[randomNum()]);
  };

  return {
    homeScore,
    awayScore,
    updateScore,
  };
};



const usePlayGame = ({ 
  team1 = "https://cdn.mos.cms.futurecdn.net/8227s5kMEx84YEHWLbxCb4-1200-80.jpg",
  team2 = "https://www.dailynews.com/wp-content/uploads/migration/2015/201506/SPORTS_150619439_AR_0_FHMRLLUSDJPX.jpg?w=1024",
  timeLapse,
  delay,
}) => {
  const [time, setTime] = useState(15);
  const [pause, setPause] = useState(false);
  const [quarter, setQuarter] = useState(0);
  const [winner, setWinner] = useState("");

  const { updateScore, homeScore, awayScore, homeTeam, awayTeam } = GetScores(
    team1,
    team2
  );

  useEffect(() => {
    // if we have finished the last quarter
    if (quarter >= 3) {
      // we decide the winner by checking the scores
      const winner = homeScore > awayScore ? homeTeam : awayTeam;
      //   we set the winner
      setWinner(winner);
      //   we pause the game indefinitely
      return setPause(true);
    }
    // Let's unpause after a break (setTimeout callback)
    if (pause) {
      setPause(false);
    }

    // on every interval increase time by the timelapse set (default 1000 ms)
    const interval = setInterval(() => {
      updateScore();
      setTime(time - 1);
    }, timeLapse);

    // if the time is run out and the game is not yet over
    if (time === 0 && quarter !== 3) {
      // We pause the match
      setPause(true);
      //   we move to the next quarter
      setQuarter(quarter + 1);
      //   we reset the time after a short break (delay default 5000 ms)
      setTimeout(() => {
        setTime(time + 15);
      }, delay);
      return clearInterval(interval);
    }
    return () => clearInterval(interval);
  }, [time]);

  return {
    time,
    pause,
    quarter,
    winner,
    homeScore,
    awayScore,
    homeTeam,
    awayTeam,
  };
};

Войти в полноэкранный режим Выйти из полноэкранного режима

Фэнтези-баскетбол

Для реализации логики я хотел создать что-то веселое. Что-то, что мне стало очень нравиться… Баскетбол. Вперед, Воины!!! я прав.

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

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

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

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

Мы установим еще одну зависимость — стилизованные компоненты.

npm install --save-dev styled-components

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

Начало работы

Чтобы начать работу с любым проектом на реакте для домашнего использования, мы можем просто использовать Create React App (CRA). Create React app — это мощный инструмент для создания проекта React и идеальное решение для создания простых приложений, подобных этому.

Для начала работы выполните следующую команду.

npx create-react-app ${your app name here}

Затем введите следующую команду в CLI cd ${имя вашего приложения здесь} и вуаля. у нас есть проект.

Структура

Ниже вы можете увидеть структуру игры.
Я буду рассматривать каждый компонент по мере продвижения.

Пересоздайте структуру по своему усмотрению, если вы хотите следовать за мной, то создайте папки, как показано на рисунке выше.

API

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

Эта логика хранится в src/Services/api.js

Сначала давайте начнем с того, что мы хотим, чтобы этот API возвращал.
Мы хотим получить следующее:

  • счет домашней команды
  • счет команды гостей
  • функция для обновления счета
import { useState } from "react";

export const GetScores = (
) => {
  const [homeScore, setHomeScore] = useState(0);
  const [awayScore, setAwayScore] = useState(0);

const updateScore = () => {
    const scoreOptions = [0, 2, 3];
    const randomNum = () => Math.floor(Math.random() * 3);

    setHomeScore(homeScore + scoreOptions[randomNum()]);
    setAwayScore(awayScore + scoreOptions[randomNum()]);
  };

  return {
    homeScore,
    awayScore,
    updateScore,
  };
};

Вход в полноэкранный режим Выход из полноэкранного режима

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

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

Я создал массив с возможными счетами в баскетбольном матче: 0, 2 и 3. Чтобы получить 2 очка, вы можете сделать данк, бросок или бросок с линии, чтобы получить 3 очка, и единственный вариант — промах.

Затем мне нужно было как-то определить, какой из трех вариантов будет выбран. Я создал функцию, которая возвращает Math.floor(Math.random()*3). Давайте разложим это по полочкам.

Math.floor извлекает десятичные значения и «выравнивает» их так, что, по сути, удаляет десятичные знаки (например, 10.8 равно 10).

Math.random дает нам случайное числовое значение от 0 до 1 (например, 0.245 или 0.678), мы умножаем его на количество вариантов (3) и получаем случайное число от 0 до 2, которое соответствует индексам в нашем массиве.

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

Крючки

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

Эта логика хранится в src/Services/hooks.js


import { useState, useEffect } from "react";
import { GetScores } from "./api";

const usePlayGame = ({ team1, team2, timeLapse = 1000, delay = 5000 }) => {
  const [time, setTime] = useState(15);
  const [pause, setPause] = useState(false);
  const [quarter, setQuarter] = useState(0);
  const [winner, setWinner] = useState("");

  const { updateScore, homeScore, awayScore } = GetScores();

  useEffect(() => {
    // if we have finished the last quarter
    if (quarter >= 3) {
      // we decide the winner by checking the scores
      const winner = homeScore > awayScore ? team1 : team2;
      //   we set the winner
      setWinner(winner);
      //   we pause the game indefinitely
      return setPause(true);
    }
    // Let's unpause after a break (setTimeout callback)
    if (pause) {
      setPause(false);
    }

    // on every interval increase time by the timelapse set (default 1000 ms)
    const interval = setInterval(() => {
      updateScore();
      setTime(time - 1);
    }, timeLapse);

    // if the time is run out and the game is not yet over
    if (time === 0 && quarter !== 3) {
      // We pause the match
      setPause(true);
      //   we move to the next quarter
      setQuarter(quarter + 1);
      //   we reset the time after a short break (delay default 5000 ms)
      setTimeout(() => {
        setTime(time + 15);
      }, delay);
      return clearInterval(interval);
    }
    return () => clearInterval(interval);
  }, [time]);

  return {
    time,
    pause,
    quarter,
    winner,
    homeScore,
    awayScore,
  };
};

export default usePlayGame;
Вход в полноэкранный режим Выйти из полноэкранного режима

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



    // if we have finished the last quarter
if (quarter >= 3) {
      // we decide the winner by checking the scores
      const winner = homeScore > awayScore ? team1 : team2;
      //   we set the winner
      setWinner(winner);
      //   we pause the game indefinitely
      return setPause(true);
    }

Вход в полноэкранный режим Выйти из полноэкранного режима

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

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

let count = 0;

const addTo10 = () => {
if(count === 10) return

console.log(count)

addTo10(count++)

}
Вход в полноэкранный режим Выход из полноэкранного режима

Как вы можете видеть на этом простом примере, у нас есть счетчик, который начинается с 0. Функция addTo10 вызывает сама себя с count++, прибавляя счетчик каждый раз, когда она выполняется. Оператор if в начале проверяет, что когда счетчик равен 10, мы возвращаемся и функция прекращает работу.

Вы можете скопировать и вставить это в браузер, чтобы посмотреть, как это работает.

Следующий блок кода, который очень важен, следующий

// on every interval increase time by the timelapse set (default 1000 ms)
    const interval = setInterval(() => {
      updateScore();
      setTime(time - 1);
    }, timeLapse);

Вход в полноэкранный режим Выйти из полноэкранного режима

мы создаем таймер с помощью setInterval, который перезапускает функцию через каждый интервал. В примере выше переменная timeLapse установлена на 1000 мс, что составляет одну секунду. Таким образом, мы уменьшаем значение числа на 1 при каждом запуске. Это и есть наши часы.

На интервале мы обновляем счет, который является функцией, которую мы получаем из нашего API, созданного ранее:


  const { updateScore, homeScore, awayScore } = GetScores();
Войти в полноэкранный режим Выйти из полноэкранного режима

это обновит счет и экспортирует homeScore и awayScore для соответствующих команд.

 // if the time is run out and the game is not yet over
    if (time === 0 && quarter !== 3) {
      // We pause the match
      setPause(true);
      //   we move to the next quarter
      setQuarter(quarter + 1);
      //   we reset the time after a short break (delay default 5000 ms)
      setTimeout(() => {
        setTime(time + 15);
      }, delay);
      return clearInterval(interval);
    }
    return () => clearInterval(interval);
  }, [time]);

Войти в полноэкранный режим Выход из полноэкранного режима

Это основной код. Давайте пройдемся по нему шаг за шагом. Если время достигает нуля и это не последняя четверть, мы хотим установить паузу на true, увеличить четверть и использовать обратный вызов setTimeout для сброса таймера обратно на 15 и использовать переменную delay для установки миллисекунд, которые мы хотим подождать (по умолчанию 5000 мс или 5 секунд). внутри useEffect мы должны вернуть clearInterval, чтобы остановить запуск интервалов.

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

if(pause){
setPause(false)
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Дроссель

Если вы посмотрите принцип работы дросселя в JavaScript, то обычно он имеет код, похожий на приведенный выше. Мы хотим установить переменную или состояние в true, чтобы функция могла работать, внутри функции мы устанавливаем состояние в false, чтобы она перестала работать, и используем setTimeout, чтобы запустить функцию снова. В данном случае setInterval заботится о сбросе состояния.

Ниже показан пример дросселирования в JavaScript

//initialize throttlePause variable outside throttle function
let throttlePause;

const throttle = (callback, time) => {
  //don't run the function if throttlePause is true
  if (throttlePause) return;

  //set throttlePause to true after the if condition. This allows the function to be run once
  throttlePause = true;

  //setTimeout runs the callback within the specified time
  setTimeout(() => {
    callback();

    //throttlePause is set to false once the function has been called, allowing the throttle function to loop
    throttlePause = false;
  }, time);
};

Вход в полноэкранный режим Выход из полноэкранного режима

UI

Теперь самое интересное. Мы можем создать пользовательский интерфейс. Пользовательский интерфейс состоит из нескольких компонентов, основным из которых является ScoreCard.

import React from "react";
import Winner from "../Winner";
import { CardWrapper, Score, Teams, TeamLogo } from "./styles";
import usePlayGame from "../../Services/hooks";
import Time from "../Time";
import Break from "../Break";
import { game } from "./utils";

const ScoreCard = ({
  team1 = "https://cdn.mos.cms.futurecdn.net/8227s5kMEx84YEHWLbxCb4-1200-80.jpg",
  team2 = "https://www.dailynews.com/wp-content/uploads/migration/2015/201506/SPORTS_150619439_AR_0_FHMRLLUSDJPX.jpg?w=1024",
  timeLapse,
  delay,
}) => {
  const { quarter, time, homeScore, awayScore, pause, winner } = usePlayGame({
    team1,
    team2,
    timeLapse,
    delay,
  });

  return (
    <CardWrapper>
      <Teams>
        <TeamLogo src={team1} /> vs
        <TeamLogo src={team2} />
      </Teams>
      {game[quarter]}
      <Score>
        {homeScore} - {awayScore}
      </Score>
      {pause ? <Break quarter={quarter} /> : <Time time={time} />}
      <br />
      {winner && <Winner img={winner} />}
    </CardWrapper>
  );
};

export default ScoreCard;


Вход в полноэкранный режим Выход из полноэкранного режима

В карточке результатов есть команды

<Teams>
 <TeamLogo src={team1} /> vs
 <TeamLogo src={team2} />
</Teams>
Войти в полноэкранный режим Выход из полноэкранного режима

квартал, в котором мы находимся, который использует файл utils.js. Я объединил оба варианта в разделе ниже

{game[quarter]}

export const game = ["First Half", "Second Half", "Third Half", "Full Time"];
Войти в полноэкранный режим Выход из полноэкранного режима

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

следующая часть — счет

<Score>
  {homeScore} - {awayScore}
</Score>
Войти в полноэкранный режим Выход из полноэкранного режима

который получает homeScore и awayScore из нашего файла hooks.js.

затем у нас есть пара операторов, которые используют компоненты Break, Time и Winner.

{pause ? <Break quarter={quarter} /> : <Time time={time} />}
<br />
{winner && <Winner img={winner} />}
Войти в полноэкранный режим Выйти из полноэкранного режима

если мы находимся на паузе, мы хотим отобразить слово «перерыв», кроме случаев, когда мы достигаем полного времени. в противном случае мы хотим отобразить время, но поскольку время идет от 15 до 0, мы хотим отобразить его как 15, 14, 13, 12, 11, 10, 09, 08, 07, 06, 05, 04, 03, 02, 01, 00.

По этим причинам я создал следующие компоненты в папке Break и папке Time соответственно.


//Break
import React from "react";

const Break = ({ quarter }) => {
  console.log(quarter);
  return <div>{quarter < 3 ? "Break" : ""}</div>;
};

export default Break;

//Time
import React from "react";

const Time = ({ time }) => <div>{time < 10 ? `0${time}` : time}</div>;

export default Time;

Вход в полноэкранный режим Выход из полноэкранного режима

Последним кусочком головоломки является компонент Winner


import React from "react";
import { WinnerCard } from "./styles";

const Winner = ({ img }) => {
  return (
    <div>
      <h1>!! Winner !!</h1>
      <WinnerCard src={img} />
    </div>
  );
};

export default Winner;

Войти в полноэкранный режим Выход из полноэкранного режима

Стилизованные компоненты

Для компонента ScoreCard и компонента Winner я создал следующие стили, используя стилизованные компоненты:

//ScoreCard
import styled from "styled-components";

export const CardWrapper = styled.div`
  display: flex;
  flex-direction: column;
  text-align: center;
`;

export const Score = styled.div`
  display: flex;
  font-size: 8rem;
  justify-content: center;
`;

export const Teams = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 3rem;
  font-weight: bold;
  gap: 1rem;
  padding: 15px;
  border-bottom: 10px double black;
`;

export const TeamLogo = styled.img`
  width: 200px;
  height: 115px;
  border-radius: 15px;
  padding: 10px;
  border: 10px double black;
`;

export const Winner = styled.div`
  display: flex;
  flex-direction: column;
`;

Вход в полноэкранный режим Выход из полноэкранного режима
//Winner

import styled from "styled-components";

export const WinnerCard = styled.img`
  width: 200px;
  height: 115px;
  padding: 10px;
  border: 5px double black;
  border-image: linear-gradient(to top right, #a67c00 10%, #fabf00 90%) 1 round;
`;

Войти в полноэкранный режим Выход из полноэкранного режима

Эти стили можно импортировать в ваш файл с помощью именованных экспортов из ‘./styles’ следующим образом

//ScoreCard
import { CardWrapper, Score, Teams, TeamLogo } from "./styles";

//Winner
import { WinnerCard } from "./styles";
Войти в полноэкранный режим Выйти из полноэкранного режима

Дополнительное обучение

Для тех, кто интересуется возможностями стилизованных компонентов, я добавил стилизованный компонент, который я использовал в своем App.js для установки макета страницы


import styled from "styled-components";

export const Direction = styled.div`
  ${({ direction }) => {
    return direction === "row"
      ? `{
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem;
  margin: auto 0;
  overflow: scroll;
  max-width: 100vw;
}`
      : `{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  grid-gap: 1rem;
  overflow: scroll;
}`;
  }}
`;


Вход в полноэкранный режим Выйти из полноэкранного режима

Этот компонент принимает направление, если это направление — ‘row’, то первая часть троичного числа используется в нашем компоненте-обертке, чтобы установить все наши дочерние компоненты в один ряд. Если вы решите не использовать это направление, а вместо него выбрать ‘column’ или любое другое, поток будет выполнен в сетчатом макете, который является отзывчивым.

App.js

Ниже вы найдете мою реализацию ScoreCard в нашем файле App.js.

import { Direction } from "./styles";
import ScoreCard from "./Components/ScoreCard";

function App() {
  return (
    <Direction direction="row">
      <ScoreCard />
    </Direction>
  );
}

export default App;

Вход в полноэкранный режим Выход из полноэкранного режима

Спасибо

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

Пожалуйста, поставьте лайк ❤️, оставьте комментарий 📃 или подключитесь 👋 :

Github
Linkedin
Discord

Вот ссылка на Github Repo: Sports App

Оцените статью
devanswers.ru
Добавить комментарий