Максимизируйте свои навыки React: создайте приложение списка дел от начала до конца (с помощью TypeScript + Vite)

Максимизируйте свои навыки React: создайте приложение списка дел от начала до конца (с помощью TypeScript + Vite)

3 февраля 2023 г.

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

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

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

Итак, приступим!

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

Краткое введение

Мы будем использовать TypeScript для написания кода и Vite для разработки и сборки приложения.

TypeScript

TypeScript – это строго типизированный язык программирования, основанный на JavaScript. С практической точки зрения, если вы уже знаете JavaScript, все, что вам нужно научиться использовать TypeScript, — это как использовать типы и интерфейсы.

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

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

const someFunc = (parameter: number) => {...};

someFunc('1') // Argument of type 'string' is not assignable to parameter of type 'number'.

Если бы мы использовали JavaScript, то, скорее всего, обнаружили бы ошибку позже.

Нам не всегда нужно указывать тип, так как TypeScript чаще всего может определить их автоматически.

Вы можете изучить основы TypeScript здесь. (Или просто игнорируйте типы.)

Посетить

Самый распространенный способ запуска приложения React — это, вероятно, использование create-react-app. Вместо этого мы будем использовать Vite (произносится как «veet»). Но не волнуйтесь, это так же просто, но более эффективно.

С такими инструментами, как webpack (используется create-react-app под капотом), все ваше приложение должно быть объединено в один файл, прежде чем он может быть передан в браузер. Vite, с другой стороны, использует собственные модули ES в браузере, чтобы сделать объединение более эффективным с помощью Rollup, предоставляя части исходного кода как нужно.

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

Кроме того, Vite предлагает встроенную поддержку Typescript, JSX и TSX, CSS и многого другого.

Как и приложение create-react-app, Vite предлагает инструмент под названием create-vite, который позволяет нам быстро начать новый проект, используя базовые шаблоны, включая варианты для Vanilla JS, или используя библиотеки, такие как React.

Чтобы было ясно, нам не нужен такой инструмент, как Vite или create-react-app для создания приложений React, но они облегчают нашу жизнь, заботясь о настройке проекта, связывании кода, использование транспиляторов и многое другое.

Погружение в React

JSX/TSX

React позволяет добавлять разметку непосредственно в код, который впоследствии будет скомпилирован в обычный JavaScript. Это называется JSX. Когда мы используем JSX, мы можем сохранять наши файлы как .jsx для JavaScript или .tsx для TypeScript.

Это выглядит так:

const element = <h1>Hello, world!</h1>;

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

Например, если у нас есть массив текста, который мы хотим отобразить как разные элементы абзаца, мы могли бы сделать это:

const paragraphs = ["First", "Second", "Third"];

paragraphs.map((paragraph) => <p>{paragraph}</p>);

И это будет скомпилировано примерно так:

<p>First</p>
<p>Second</p>
<p>Third</p>

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

Компоненты React

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

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

// Define the component
const Component = () => {
  const paragraphs = ["First", "Second", "Third"];
  return (
    <>
      {paragraphs.map((paragraph) => (
        <p>{paragraph}</p>
      ))}
    </>
  );
};

// Use the component in the same way you use an HTML element in the JSX
const OtherComponent = () => {
  return <Component />;
};

Реквизит

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

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

Если мы используем TypeScript, нам нужно указать типы данных внутри объекта реквизита (контекста для них нет, поэтому TypeScript не может их вывести), который в данном случае представляет собой массив строк (строка[]).

const Component = (props: { paragraphs: string[] }) => {
  <>
    {props.paragraphs.map((paragraph) => (
      <p>{paragraph}</p>
    ))}
  </>;
};

const OtherComponent = () => {
  const paragraphs = ["First", "Second", "Third"];
  return <Component paragraphs={paragraphs} />;
};

Состояние

Если мы хотим создать интерактивный компонент, нам потребуется хранить информацию в состоянии компонента, чтобы он мог ее «запомнить».

Например, если мы хотим определить простой счетчик, который показывает количество нажатий кнопки, нам нужен способ хранения и обновления этого значения. React позволяет нам сделать это с помощью хука useState (hook — это функция, которая позволяет вам «подцепиться» к React функции состояния и жизненного цикла).

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

import { useState } from "react";

const Counter = () => {
  const [count, setCount] = useState(0);
  return (
    <>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>Increment count</button>
    </>
  );
};

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

Создание проекта

Зависимости

Для использования Vite нам понадобится **узел **и менеджер пакетов.

Чтобы установить узел, просто выберите один из вариантов здесь в зависимости от вашей системы и конфигурации. Если вы используете Linux или Mac, вы также можете установить его с помощью Homebrew.

Менеджер пакетов может быть npm или пряжа. В этом посте мы будем использовать npm.

Создание проекта

Настало время создать проект. В терминале мы переходим в каталог, в котором будет создан проект, затем запускаем команду create-vite.

$ npm create vite@latest

Нам может быть предложено установить дополнительные пакеты (например, create-vite). Введите y и нажмите Enter, чтобы продолжить.

Need to install the following packages:
  create-vite@4.0.0
Ok to proceed? (y)

Далее нам будет предложено ввести информацию о проекте.

Введите название проекта. Я выбрал my-react-project.

? Project name: › my-react-project

Выберите React в качестве «фреймворка».

React технически библиотека, а не framework, но не беспокойтесь об этом.

? Select a framework: › - Use arrow-keys. Return to submit.
    Vanilla
    Vue
❯   React
    Preact
    Lit
    Svelte
    Others

Выберите TypeScript + SWC в качестве варианта.

SWC (расшифровывается как Speedy Web Compiler) — сверхбыстрый компилятор TypeScript/JavaScript, написанный на Rust. Они утверждают, что «в 20 раз быстрее, чем Babel на одном потоке и в 70 раз быстрее на четырех ядрах».

? Select a variant: › - Use arrow-keys. Return to submit.
    JavaScript
    TypeScript
    JavaScript + SWC
❯   TypeScript + SWC

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

cd my-react-project
npm install
npm run dev

Через несколько секунд мы увидим что-то похожее на это:

  VITE v4.0.4  ready in 486 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help

Если мы откроем наш браузер и перейдем к http://localhost:5173/, мы увидим страницу Vite + React по умолчанию:

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

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

Структура файла и начальная настройка

Если мы откроем проект в редакторе кода или в выбранной IDE, мы должны увидеть такую ​​файловую структуру:

Мы можем удалить некоторые шаблонные файлы, поскольку они не будут использоваться (все файлы .svg и .css).

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

function App() {
  return (

  )
}

export default App

Мы вернемся к этому файлу позже.

Стили

Стиль здесь не в центре внимания, но мы будем использовать Tailwind CSS, библиотеку, которая позволяет нам стилизовать HTML-элементы, добавляя к ним классы. Следуйте этим инструкциям, чтобы увидеть стили, отраженные в вашем собственном проекте.

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

Думаем о дизайне: расположение компонентов

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

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

Начнем с макета базового пользовательского интерфейса и наметим иерархию задействованных компонентов.

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

В идеале наши компоненты должны отвечать только за одну вещь в соответствии с принципом единой ответственности.

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

Реквизит: создание статической версии

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

Вы можете найти код статической версии в этом репозитории GitHub, в ветке «static-version» . Код полностью работающего приложения находится в основной ветке.

Контейнер

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

// src/components/Container.tsx
const Container = ({
  children,
  title,
}: {
  children: JSX.Element | JSX.Element[];
  title?: string;
}) => {
  return (
    <div className="bg-green-600 p-4 border shadow rounded-md">
      {title && <h2 className="text-xl pb-2 text-white">{title}</h2>}
      <div>{children}</div>
    </div>
  );
};

export default Container;

Он принимает объект реквизита с параметром children типа JSX.Element | JSX.Элемент[]. Это означает, что мы можем скомпоновать его с любым другим элементом HTML или любыми другими компонентами, которые мы создаем. Он может отображаться в любом месте внутри контейнера — в данном случае во втором div.

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

Контейнер также принимает необязательную опору string с именем title, которая будет отображаться внутри h2 всякий раз, когда она существует.

// src/App.tsx
import Container from "./components/Container";
import Input from "./components/Input";
import Summary from "./components/Summary/Summary";
import Tasks from "./components/Tasks/Tasks";

function App() {
  return (
    <div className="flex justify-center m-5">
      <div className="flex flex-col items-center">
        <div className="sm:w-[640px] border shadow p-10 flex flex-col gap-10">
          <Container title={"Summary"}>
            <Summary />
          </Container>
          <Container>
            <Input />
          </Container>
          <Container title={"Tasks"}>
            <Tasks />
          </Container>
        </div>
      </div>
    </div>
  );
}

export default App;

Сводка

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

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

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

// src/components/Summary/SummaryItem.tsx
const SummaryItem = ({
  itemName,
  itemValue,
}: {
  itemName: string;
  itemValue: number;
}) => {
  return (
    <article className="bg-green-50 w-36 rounded-sm flex justify-between p-2">
      <h3 className="font-bold">{itemName}</h3>
      <span className="bg-green-900 text-white px-2 rounded-sm">
        {itemValue}
      </span>
    </article>
  );
};

export default SummaryItem;

// src/components/Summary/Summary.tsx
import SummaryItem from "./SummaryItem";

const Summary = () => {
  return (
    <>
      <div className="flex justify-between">
        <SummaryItem itemName={"Total"} itemValue={3} />
        <SummaryItem itemName={"To do"} itemValue={2} />
        <SummaryItem itemName={"Done"} itemValue={1} />
      </div>
    </>
  );
};

export default Summary;

Вы заметите, что SummaryItem принимает два реквизита: itemName, типа string, и itemValue, типа number. Эти реквизиты передаются, когда компонент SummaryItem используется внутри компонента SummaryItem, а затем визуализируются в SummaryItem JSX.

Задачи

Точно так же для раздела задач (последний) у нас есть компонент Tasks, который отображает компоненты TaskItem.

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

// src/components/Tasks/TaskItem.tsx
const TaskItem = () => {
  return (
    <div className="flex justify-between bg-white p-1 px-3 rounded-sm">
      <div className="flex gap-2 items-center">
        <input type="checkbox" />
        Task name
      </div>
      <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3">
        Delete
      </button>
    </div>
  );
};

export default TaskItem;

// src/components/Tasks/Tasks.tsx
import TaskItem from "./TaskItem";

const Tasks = () => {
  return (
    <div className="flex flex-col gap-2">
      <TaskItem />
    </div>
  );
};

export default Tasks;

Ввод

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

// src/components/Input.tsx
const InputContainer = () => {
  return (
    <form action="" className="flex flex-col gap-4">
      <div className="flex flex-col">
        <label className="text-white">Enter your next task:</label>
        <input className="p-1 rounded-sm" />
      </div>
      <button
        type="button"
        className="bg-green-100 rounded-lg hover:bg-green-200 p-1"
      >
        Add task
      </button>
    </form>
  );
};

export default InputContainer;

Состояние: добавление интерактивности

Чтобы добавить интерактивности в React, нам нужно хранить информацию в состоянии компонента.

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

Минимальное представление состояния

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

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

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

const tasks = [
  {
    name: "task one",
    done: false,
  },
  {
    name: "task two",
    done: true,
  },
];

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

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

Где должно жить государство

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

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

// src/components/Input.tsx
import { useState } from "react";

const InputContainer = () => {
  const [newTask, setNewTask] = useState(""); // Initialize newTask and setNewTask
  return (
    <form action="" className="flex flex-col gap-4">
      <div className="flex flex-col">
        <label className="text-white">Enter your next task:</label>
        <input
          className="p-1 rounded-sm"
          type="text"
          value={newTask} // Set the input value to newTask
          onChange={(e) => setNewTask(e.target.value)} // Set newTask to the input value whenever the user types something
        />
      </div>
      <button
        type="submit"
        className="bg-green-100 rounded-lg hover:bg-green-200 p-1"
      >
        Add task
      </button>
    </form>
  );
};

export default InputContainer;

Что это делает, так это отображает наше значение newTask во входных данных и вызывает функцию setNewTask при каждом изменении входных данных (т. е. когда пользователь что-то вводит).

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

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

Давайте взглянем на наше дерево компонентов (вы можете использовать инструменты разработки React для этого).

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

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

(Мы пока не беспокоимся о том, как вносить и сохранять какие-либо изменения в родительском состоянии, это следующая часть.)

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

Помимо свойств name и done, мы также добавляем идентификатор к нашим объектам задач, так как это скоро понадобится.

Мы определяем interface с типами значения в объектах задачи и передаем его в функцию useState. Это необходимо в данном случае, так как TypeScript не сможет вывести его, когда мы изменим начальное значение tasks на пустой массив или когда мы передаем его в качестве реквизита.

Наконец, обратите внимание, что мы передаем задачи как свойства компонентам Summary и Tasks. Эти компоненты должны быть изменены, чтобы приспособиться к этому.

Мы обновили компонент «Сводка», чтобы теперь он принимал tasks в качестве реквизита. Мы также определили значения total, pending и done, которые будут передаваться в качестве свойств компонентам SummaryItem вместо статического itemValue, которые у нас были раньше.

Для компонента Tasks мы также берем task в качестве реквизита и сопоставляем его свойство name с компонентами TaskItem. В результате мы получаем компонент TaskItem для каждого объекта внутри массива tasks. Мы также обновляем компонент TaskItem, чтобы он принимал name в качестве реквизита.

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

Результат на данный момент таков:

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

Добавление обратного потока данных

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

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

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

Ниже представлен наш последний компонент приложения с объявленными обработчиками, которые передаются в качестве реквизитов компонентам ввода и задач.

handleSubmit возвращает новый массив со старыми задачами и новой. toggleDoneTask возвращает новый массив с противоположным свойством done для указанного id. handleDeleteTask возвращает новый массив без задачи с указанным id.

// src/App.tsx
import { FormEvent, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import Container from "./components/Container";
import Input from "./components/Input";
import Summary from "./components/Summary/Summary";
import Tasks from "./components/Tasks/Tasks";

export interface Task {
  name: string;
  done: boolean;
  id: string;
}

function App() {
  const [tasks, setTasks] = useState<Task[]>([]);

  const handleSubmit = (e: FormEvent<HTMLFormElement>, value: string) => {
    e.preventDefault();
    const newTask = {
      name: value,
      done: false,
      id: uuidv4(),
    };
    setTasks((tasks) => [...tasks, newTask]);
  };

  const toggleDoneTask = (id: string, done: boolean) => {
    setTasks((tasks) =>
      tasks.map((t) => {
        if (t.id === id) {
          t.done = done;
        }
        return t;
      })
    );
  };

  const handleDeleteTask = (id: string) => {
    setTasks((tasks) => tasks.filter((t) => t.id !== id));
  };

  return (
    <div className="flex justify-center m-5">
      <div className="flex flex-col items-center">
        <div className="border shadow p-10 flex flex-col gap-10 sm:w-[640px]">
          <Container title={"Summary"}>
            <Summary tasks={tasks} />
          </Container>
          <Container>
            <Input handleSubmit={handleSubmit} />
          </Container>
          <Container title={"Tasks"}>
            <Tasks
              tasks={tasks}
              toggleDone={toggleDoneTask}
              handleDelete={handleDeleteTask}
            />
          </Container>
        </div>
      </div>
    </div>
  );
}

export default App;

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

// src/components/Input.tsx
import { FormEvent, useState } from "react";

const InputContainer = ({
  handleSubmit,
}: {
  handleSubmit: (e: FormEvent<HTMLFormElement>, value: string) => void;
}) => {
  const [newTaskName, setNewTaskName] = useState("");
  return (
    <form
      action=""
      className="flex flex-col gap-4"
      onSubmit={(e) => {
        handleSubmit(e, newTaskName);
        setNewTaskName("");
      }}
    >
      <div className="flex flex-col">
        <label className="text-white">Enter your next task:</label>
        <input
          className="p-1 rounded-sm"
          type="text"
          value={newTaskName}
          onChange={(e) => setNewTaskName(e.target.value)}
        />
      </div>
      <button
        type="submit"
        className="bg-green-100 rounded-lg hover:bg-green-200 p-1"
      >
        Add task
      </button>
    </form>
  );
};

export default InputContainer;

Это последний компонент Tasks, который мы обновили, чтобы передавать реквизиты из приложения в TaskItem. Мы также добавили тернарный оператор для возврата «Задач еще нет!» когда нет задач.

// src/components/Tasks/Tasks.tsx
import { Task } from "../../App";
import TaskItem from "./TaskItem";

const Tasks = ({
  tasks,
  toggleDone,
  handleDelete,
}: {
  tasks: Task[];
  toggleDone: (id: string, done: boolean) => void;
  handleDelete: (id: string) => void;
}) => {
  return (
    <div className="flex flex-col gap-2">
      {tasks.length ? (
        tasks.map((t) => (
          <TaskItem
            key={t.id}
            name={t.name}
            done={t.done}
            id={t.id}
            toggleDone={toggleDone}
            handleDelete={handleDelete}
          />
        ))
      ) : (
        <span className="text-green-100">No tasks yet!</span>
      )}
    </div>
  );
};

export default Tasks;

И это последний компонент TaskItem, использующий toggleDone и handleDelete для обновления состояния компонента приложения.

// src/components/Tasks/TaskItem.tsx
const TaskItem = ({
  name,
  done,
  id,
  toggleDone,
  handleDelete,
}: {
  name: string;
  done: boolean;
  id: string;
  toggleDone: (id: string, done: boolean) => void;
  handleDelete: (id: string) => void;
}) => {
  return (
    <div className="flex justify-between bg-white p-1 px-3 rounded-sm gap-4">
      <div className="flex gap-2 items-center">
        <input
          type="checkbox"
          checked={done}
          onChange={() => toggleDone(id, !done)}
        />
        {name}
      </div>
      <button
        className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3"
        type="button"
        onClick={() => handleDelete(id)}
      >
        Delete
      </button>
    </div>
  );
};

export default TaskItem;

И вот наше финальное приложение после того, как мы добавим несколько задач!

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

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

Заключительные слова

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

Мы рассмотрели:

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

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

Удачного кодирования!


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE