Привет, друзья ?, добро пожаловать в статью этой недели, в которой мы будем делать кое-что интересное. Делаем приложение Remix ?! Я решил взять несколько своих старых учебных проектов на Github и немного подправить их, а что может быть лучше, чем переписать их в Remix? Я надеюсь поделиться с вами в этой статье своим путешествием о том, как я этого добился, включая мои трудности, неприязни и подходы к решению проблемы. Сюда также войдут все мои фрагменты кода, так что если вы заинтересованы в создании приложения для отслеживания задач в Remix, давайте начнем!
Предварительные условия: Я предполагаю, что у вас есть хорошее понимание React, CSS и Javascript и некоторые основы Remix, прежде чем вы попытаетесь следовать этому руководству ?, также у вас установлен node и присутствует npm. В этом посте мы пропустим много не относящихся к делу концепций.
Вы также можете ознакомиться с моими постами Intro to Remix ? и Why Remix ?? для получения некоторой справочной информации о Remix.
? Начало работы:
Чтобы начать, откройте командную строку (shell) и введите команду:
npx create-remix@latest
После этого начнется загрузка, которая может занять некоторое время, в зависимости от вашей сети.
После этого появится анкета с запросом имени вашего проекта. Нажмите «Enter», чтобы использовать имя по умолчанию, или введите свое собственное имя проекта и нажмите «Enter».
Далее вам будет предложено ввести ваш выбор развертывания. Для простоты мы выбрали Vercel, мы будем использовать «Vercel».
Для этого вам необходимо создать учетную запись vercel, если вы этого не сделали, перейдите и сделайте это, прежде чем продолжить.
Затем нас спрашивают, «TypeScript» (TS) или «JavaScript». Я бы выбрал TypeScript (по причинам превосходства и простоты разработки, вы можете выбрать TypeScript, даже если вы его еще не изучали). Если вы не хотите использовать TS, смело удаляйте его и используйте только Javascript). После этого мы запускаем npm install
и ждем, пока наше remix-приложение установит свои зависимости. Запустите
cd <your-app-name>
затем
npm run postinstall
.
Затем мы можем использовать code .
, чтобы открыть его в VS Code (если вы используете vs code).
Немного информации о том, что делают основные папки & файлы:
-
app/ — Сюда помещается весь код вашего приложения Remix
-
app/entry.client.tsx — Это первый фрагмент вашего JavaScript, который будет запущен при загрузке приложения в браузере. Мы используем этот файл для гидратации наших компонентов React.
-
app/entry.server.tsx — Это первый бит вашего JavaScript, который будет выполняться, когда запрос попадет на ваш сервер. Remix обрабатывает загрузку всех необходимых данных, а вы отвечаете за отправку ответа. Мы будем использовать этот файл для рендеринга нашего приложения React в строку/поток и отправим его в качестве ответа клиенту.
-
app/root.tsx — Сюда мы помещаем корневой компонент нашего приложения. Здесь вы рендерите элемент.
-
app/routes/ — Здесь будут располагаться все ваши «модули маршрутов». Remix использует файлы в этом каталоге для создания URL-маршрутов для вашего приложения на основе имен файлов.
-
public/ — Здесь размещаются ваши статические активы (изображения/шрифты/и т.д.)
-
remix.config.js — Remix имеет несколько параметров конфигурации, которые вы можете задать в этом файле.
Взято из документации Remix. Не волнуйтесь, я расскажу о них подробнее позже.
Откройте новый терминал (Ctrl + Shift + '
по умолчанию). Запустите npm run dev
, затем откройте localhost:3000 и тада! у нас есть приложение Remix.
? Первые шаги:
С этого момента я буду часто использовать местоимения от первого лица, поскольку перехожу от описания того, как установить приложение Remix, к созданию собственного приложения Remix.
Шаги, предпринятые при создании этого приложения, являются моим выбором, не стесняйтесь смешивать их или полностью менять ?.
Самое важное в проекте сейчас — это папка app
, именно в ней хранится наша магия, и именно с нее мы начнем редактирование.
Первое, что я делаю, это иду в app/routes/index
и очищаю код от шаблонов. Весь. Но пока не сохраняйте, нам нужно написать кое-что, чтобы TypeScript не выдал ошибку.
Если вы уже немного изучили Typescript, то можете пропустить этот раздел, в противном случае вам стоит продолжить чтение ?.
По сути, TS (Typescript) — это сильно типизированный язык, построенный на JavaScript. Валидный JS также является валидным TS. Разница заключается в типизированном формате, короче говоря, это помогает вам проверять и избегать множества ошибок при программировании в вашем редакторе без необходимости покидать редактор. Он определяет, когда не должно быть ошибок, а также может помочь определить возможные ошибки, с которыми вы можете столкнуться позже. И как я уже сказал, TS — это просто JS (он компилируется в JS во время выполнения), но более строгая версия, которая не допускает сомнений во время программирования. По мере продвижения вперед вы поймете, почему TS может избавить от многих головных болей.
Давайте наполним наш index.tsx
(да, tsx) некоторым кодом
export default function Index () {
return (
<div>
<h1>Hello World</h1>
</div>
)
}
Простое сообщение Hello World
.
Следующее, что я сделал, это спланировал маршруты, которые мы будем иметь в нашем приложении для отслеживания задач, и то, как это приложение будет функционировать.
Во-первых, пользователь должен быть авторизован, прежде чем он сможет получить доступ к приложению, поэтому если пользователь не прошел аутентификацию, перенаправьте его на страницу входа. Вариантами аутентификации будут Google и Email (я не уверен, что стоит добавлять Twitter), для этого пригодится Supabase или Firebase. Я выбрал Supabase, потому что это сообщество, а я большой поклонник проектов с открытым исходным кодом. Далее я решил, что в приложении не будет слишком много маршрутов и оно будет длинным и цельным. У меня уже был макет и дизайн, давайте начнем прокладывать маршруты ?♂️!
Маршруты routes
будут иметь такой вид, пока только 2 маршрута, с индексным маршрутом, index.tsx
, служащим в качестве маршрута входа и task-tracker.tsx
, служащим в качестве фактической домашней страницы.
routes
|---index.tsx
|---task-tracker.tsx
Давайте сначала начнем с маршрута трекера задач.
Опять же, мы делаем простое сообщение «Task Tracker» и переходим по адресу localhost:3000/task tracker
, чтобы увидеть, что наше приложение работает просто отлично.
.
Хорошо, я удаляю все между тегами div
и оставляю пустой тег. Я прикрепляю к нему className
(назовите его как хотите)
export default function TaskTracker () {
return (
<div className="container">
</div>
)
}
Пора добавить немного стилизации ?. Я использую только CSS (модульный CSS), без tailwind или библиотечных компонентов. Создайте папку styles
в папке app
и в ней я создаю файл task-tracker.css
и ввожу свои стили.
Вы можете использовать Tailwind, но в этой статье я не буду об этом рассказывать.
Файл task-tracker.css
:
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400&display=swap');
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
}
body {
font-family: 'Poppins', sans-serif;
}
.container {
margin: 3rem auto 2rem auto;
overflow: auto;
min-height: 25rem;
border: 0;
padding: 2rem;
width: 80%;
border-radius: 0.325rem;
}
Не забывайте использовать те стили, которые вам нравятся и которые вы предпочитаете
Наши стили не отображаются! Давайте соединим их. Remix использует функцию links
в отдельных маршрутах, чтобы придать стиль этому конкретному маршруту. В нашем случае мы бы использовали task-tracker.css
в нашем task-tracker.tsx
task-tracker.tsx
import type { LinksFunction } from "remix";
import styles from "../styles/task-tracker.css";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
};
// Rest of our code
Теперь добавим немного контента.
Мы разделим наши компоненты, чтобы облегчить их создание. В Remix есть несколько способов создания компонентов. Первый — создать папку components
(я всегда использую название components, это может быть что угодно) в папке app
и импортировать оттуда наши компоненты в наши маршруты. Во-вторых, можно поступить по-родному и просто написать все компоненты в том же файле. В-третьих, вы можете просто написать все компоненты в вашем экспорте default
и не разбивать ваши компоненты на части. Именно так я бы и поступил (Плохая идея).
Давайте создадим подзаголовок для нашего контейнера todo:
// something up here
export default function TaskTracker() {
return (
<div className="container">
<header className="header">
<h1>Task Tracker</h1>
</header>
</div>
);
}
и наш css-файл:
/* CSS styles */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.2rem;
}
Наше приложение все еще выглядит безвкусным ?. Давайте это исправим.
Скоро начнется скоростное кодирование ?.
Давайте добавим кнопку Add
в наш хедер. Только помните, что не стоит тратить слишком много времени на стилизацию. Для этого всегда найдется время ?.
Далее мы хотим добавить «задачи».
Файл tsx
:
export default function TaskTracker() {
return (
<div className="container">
<header className="header">
<h1>Task Tracker</h1>
<button className="btn">Add</button>
</header>
<div className="task-section">
<div className="task">
<h3>Eat Breakfast</h3>
<p className="description">Eat Breakfast in the dining-room, I guess.</p>
<p className="time">2:00pm</p>
</div>
</div>
</div>
);
}
и нашу таблицу стилей:
/* Css styles here */
.task {
background: #dbdbdb;
border-radius: 3px;
margin: 0.255rem;
padding: 0.625rem 1.2rem;
cursor: pointer;
}
.task h3 {
display: flex;
align-items: center;
justify-content: space-between;
}
.time {
color: #4e4e4e
}
В настоящее время наше приложение выглядит следующим образом: функциональности нет, но макет уже собирается.
Давайте поработаем над логикой компонента задач. Мы должны иметь возможность добавлять задачи и удалять задачи. Давайте еще раз пробежимся по коду:
//Our Task type (do not include if you're using js)
type Task = {
id: string,
title: string,
description?: string,
time: string
}
// Our default export
export default function TaskTracker() {
const [tasks, setTasks] = useState<Task[]>([
{ id: "1", title: "Task 1", description: "Description 1", time: "1:00" },
{ id: "2", title: "Task 2", description: "Description 2", time: "2:00" },
{ id: "3", title: "Task 3", description: "Description 3", time: "3:00" },
]);
return (
<div className="container">
<header className="header">
<h1>Task Tracker</h1>
<button className="btn">Add</button>
</header>
<div className="task-section">
{
tasks.map(task => (
<div className="task" key={task.id}>
<h3 className="task-title">{task.title}</h3>
<p className="task-description">{task.description}</p>
<p className="task-time">{task.time}</p>
</div>
))
}
</div>
</div>
);
}
Наши обновленные стили:
/* Updated styles */
.task-title {
display: flex;
align-items: center;
justify-content: space-between;
}
.task-time {
color: #4e4e4e
}
Я подытожу то, что я сделал здесь. Я добавил состояние для хранения наших задач, теперь задачи будут объектами, хранящимися в массиве, потому что их очень легко мутировать и деструктурировать таким образом. В нашем компоненте задач (принципы remix jsx похожи на принципы react, только один экспорт по умолчанию для каждого маршрута) мы отображаем массив, а затем упорядочиваем все наши задачи по убыванию.
Теперь наш трекер задач начинает собираться вместе (каким-то образом).
? Построение логики:
Давайте добавим возможность удалять задачи из нашего списка задач. Но сначала я бы установил модуль react-icons
. Это то, что я использовал в начальной версии этого приложения, и это библиотека с большим количеством иконок.
npm i react-icons
// Importing our icon
import { RiDeleteBin5Line } from "react-icons/ri";
//Our default export
export default function TaskTracker() {
const [tasks, setTasks] = useState<Task[]>([
{ id: "1", title: "Task 1", description: "Description 1", time: "1:00" },
{ id: "2", title: "Task 2", description: "Description 2", time: "2:00" },
{ id: "3", title: "Task 3", description: "Description 3", time: "3:00" },
]);
//Our delete Task function
const deleteTask = (id: string) => {
setTasks(tasks.filter((tasks) => tasks.id !== id));
};
return (
<div className="container">
<header className="header">
<h1>Task Tracker</h1>
<button className="btn">Add</button>
</header>
<div className="task-section">
{tasks.map((task) => (
<div className="task" key={task.id}>
<h3 className="task-title">{task.title}</h3>
<p className="task-description">{task.description}</p>
<p className="task-time">{task.time}</p>
<RiDeleteBin5Line
className="delete-icon"
onClick={() => deleteTask(task.id)}
/>
</div>
))}
</div>
</div>
);
}
И обновленная таблица стилей:
.delete-icon {
color: #4e4e4e;
cursor: pointer;
position: absolute;
right: 1rem;
font-size: 1.625rem;
top: 35%;
}
.delete-icon:hover {
color: #f11;
}
Не забудьте настроить все по своему вкусу, возможно, вы даже захотите изменить иконку или смягчить цвета.
Если вы перезагрузите приложение, теперь вы сможете удалять задачи! ?
Теперь давайте добавим возможность добавлять задачи, для этого мы будем использовать формы. Чтобы избежать беспорядочного маршрута, давайте разделим его на другой компонент. В верхней части экспорта по умолчанию сделайте другой экспорт (помните, просто export function...
, никакого дефолта)
// app/components/Add.tsx (or whatever you like)
import { Form } from "remix";
export default function Add() {
return (
<div>
<h1>Add Task</h1>
<Form className="add-form">
<div className="form-control">
<label>Task</label>
<input
type="text"
placeholder="Add Task"
/>
</div>
<div className="form-control">
<label>Description</label>
<input
type="text"
placeholder="Task description...(optional)"
/>
</div>
<div className="form-control">
<label>Set day & Time</label>
<input
type="text"
placeholder="Day & Time"
/>
</div>
<input type="submit" value="Save Task" className="btn btn-block" />
</Form>
</div>
);
}
Обновим наш CSS (не нужно создавать новую таблицу стилей или заново импортировать файл CSS в наш файл components
).
/* CSS Styling (Same task-tracker.css file) */
.add-form {
margin-bottom: 2.325rem;
}
.form-control {
margin: 1.2rem 0;
}
.form-control label {
display: block;
}
.form-control input {
width: 100%;
height: 2.325rem;
margin: 0.25rem;
padding: 0.2rem 0.35rem;
font-size: 1.125rem;
}
.form-control-check {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.form-control-check label {
flex: 1;
}
.form-control-check input {
flex: 2;
height: 1.25rem;
}
Импортируем в наш файл task-tracker.tsx
:
// Import our component
import Add from "~/components/AddTask";
Теперь я не хочу, чтобы панель «Добавить задачу» была видна все время. Поэтому кнопка «Добавить» находится там. Давайте добавим логику переключения в наш маршрут трекера задач:
// The default export in app/task-tracker.tsx
/* Other code up here */
// should the AddTask show or not?
const [showAddSection, setShowAddSection] = useState<boolean>(false);
// function to toggle the view of AddTask
const toggleShow = (): void => {
setShowAddSection(!showAddSection);
}
return (
<div className="container">
<header className="header">
<h1>Task Tracker</h1>
<button className="btn" style={showAddSection ? {backgroundColor: '#f11'} : {backgroundColor: '#000'}} onClick={toggleShow}>{showAddSection ? 'Close' : 'Add'}</button>
</header>
{showAddSection && <Add />}
/* More code down here */
Сначала я создал булево состояние showAddSection (длинное название ?), чтобы определить, должно ли приложение показывать раздел AddTask
. Затем функция для определения позволяет пользователю вручную переключить это состояние. Затем мы прикрепляем функцию к кнопке и добавляем некоторые изменения. Во-первых, если раздел AddTask открыт, мы используем оператор if..else (в некотором смысле), чтобы переопределить цвет фона, который мы установили в файле CSS. Также мы меняем текст с «Добавить» на «Закрыть» и наоборот.
Затем мы просто используем простой оператор if для отображения AddTask
в зависимости от того, истинно или ложно состояние.
? Очистка:
Заключительный раздел, давайте добавим возможность реального добавления задач.
Для этого вернемся к файлу компонента AddTask и, наконец, сделаем что-нибудь с нашей формой при отправке:
// app/component/<your-file>.tsx
import { useState } from "react";
import { Form } from "remix";
type Task = {
task: string;
description?: string;
time: string;
};
export default function Add(props: { addTask: (task: Task) => void }) {
const [task, setTask] = useState<string>("");
const [description, setDescription] = useState<string>("");
const [time, setTime] = useState<string>("");
const submit = () => {
if (!task || !time) {
alert("You can't submit an empty task!");
} else {
props.addTask({ task, description, time });
}
setTask("");
setDescription("");
setTime("");
};
return (
<div>
<h2>Add Task</h2>
<Form className="add-form" onSubmit={submit}>
<div className="form-control">
<label>Task</label>
<input type="text" value={task} placeholder="Add Task" onChange={(e) => setTask(e.target.value)}/>
</div>
<div className="form-control">
<label>Description</label>
<input
type="text"
value={description}
placeholder="Task description...(optional)"
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<div className="form-control">
<label>Set Day & Time</label>
<input type="text" value={time} placeholder="Day & Time" onChange={(e) => setTime(e.target.value)}/>
</div>
<input type="submit" value="Save Task" className="btn btn-block" />
</Form>
</div>
);
}
Вот и весь наш код. Первое, что я сделал, это использовал состояния для хранения значений ввода, чтобы мы могли получить к ним доступ позже. Затем мы использовали функцию для передачи значений обратно в маршрут трекера задач (я покажу код через секунду).
Кстати, нам не понадобилось
e.preventDefault()
, потому что, Remix ?!
У нас также был оператор if, в котором говорилось, что поле «Задача» и поле «Время» являются обязательными для отправки и предупреждали, если они были пустыми.
Вот и все! Не стесняйтесь настраивать, но если вы хотите, чтобы TS не выдавал ошибок, лучше двигайтесь дальше. Наш полный маршрут task-tracker
:
import { useState } from "react";
import { RiDeleteBin5Line } from "react-icons/ri";
import Add from "~/components/AddTask";
import type { LinksFunction } from "remix";
import styles from "../styles/task-tracker.css";
type Task = {
id?: number;
task: string;
description?: string;
time: string;
};
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
};
export default function TaskTracker() {
const [showAddSection, setShowAddSection] = useState<boolean>(false);
const [tasks, setTasks] = useState<Task[]>([
{ id: 1, task: "Task 1", description: "Description 1", time: "1:00" },
{ id: 2, task: "Task 2", description: "Description 2", time: "2:00" },
{ id: 3, task: "Task 3", description: "Description 3", time: "3:00" },
]);
const addTask = (task: Task) => {
const id = Math.floor(Math.random() * 999999) + 1;
const newTask = { id, ...task };
setTasks([...tasks, newTask]);
};
const deleteTask = (id: number | undefined): void => {
setTasks(tasks.filter((tasks) => tasks.id !== id));
};
const toggleShow = (): void => {
setShowAddSection(!showAddSection);
};
return (
<div className="container">
<header className="header">
<h1>Task Tracker</h1>
<button
className="btn"
style={
showAddSection
? { backgroundColor: "#f11" }
: { backgroundColor: "#000" }
}
onClick={toggleShow}
>
{showAddSection ? "Close" : "Add"}
</button>
</header>
{showAddSection && <Add addTask={addTask}/>}
<div className="task-section">
{tasks.map((task) => (
<div className="task" key={task.id}>
<h3 className="task-title">{task.task}</h3>
<p className="task-description">{task.description}</p>
<p className="task-time">{task.time}</p>
<RiDeleteBin5Line
className="delete-icon"
onClick={() => deleteTask(task.id)}
/>
</div>
))}
</div>
</div>
);
}
Мы создали функцию под названием addTask
, и задали ее id случайным числом, а затем, присвоив ей ID, объединили ее с исходным состоянием задачи. Мы также передали эту функцию вниз компоненту AddTask (так мы смогли передать значения вверх).
Таким образом, сейчас у меня есть приложение, из которого я могу успешно удалять и добавлять задачи.
? Заключительное замечание:
Вот и все для этой статьи. Забавный факт, НО. Я решил разделить эту статью на 2 (может стать 3 ?) отдельных статьи из-за ее длины. Чтобы прояснить кое-что, вы, ребята, можете быть немного смущены тем, что я поспешил с приложением и не дал подробного объяснения по ходу дела, ну, этот пост не является учебником как таковым. Предполагается, что это статья, в которой я документирую свой прогресс (Пока я писал этот пост, я впервые кодировал это приложение. Это позволило мне получить баллы в конце, так как я все документировал).
Я присоединился к #buildinpublic в Twitter, а это просто публичное строительство и рассказ другим о своих достижениях и прочем. А также получать обратную связь о том, как стать лучше.
И в конце всего, напишите мои мысли, мои трудности во время создания этого приложения, а также то, что Remix упростил для меня и чего, по моему мнению, ему сейчас не хватает. Но из-за большого объема, надеюсь, это будет в следующей статье.
На этом пока все, друзья мои, если хотите, можете заглянуть ко мне в Twitter и проследить за моим путешествием, следя за моими твитами ?. Если у вас есть желание помогать дальше, вы можете купить мне мой любимый кофе, цифровой кофе! Заранее спасибо ?.
Если вы разочарованы тем, что по Remix пока нет учебника, не унывайте! Я создаю мега написанный учебник, который надеюсь выпустить, надеюсь, к это секрет ?. Вы можете ускорить процесс или узнать, кто знает?
Как всегда, веселитесь и получайте удовольствие. Изучайте. Кодируйте. Отлаживайте. Повторяйте. Желаю вам счастливого обучения и потрясающего времени кодинга ?.