Computer >> Máy Tính >  >> Lập trình >> Redis

Phối lại ứng dụng TODO với Redis

Trong bài đăng này, chúng tôi sẽ viết một ứng dụng TODO đơn giản bằng Remix và Serverless Redis (Upstash).

Remix là một khung công tác web đầy đủ cho phép bạn tập trung vào giao diện người dùng và làm việc trở lại thông qua các nguyên tắc cơ bản về web để mang lại trải nghiệm người dùng nhanh chóng, mượt mà và linh hoạt.

Tạo dự án phối lại

Chạy lệnh dưới đây:

npx create-remix@latest

Phối lại ứng dụng TODO với Redis

Dự án đã sẵn sàng. Bây giờ hãy cài đặt các phụ thuộc và chạy:

npm install
npm run dev

Phối lại ứng dụng TODO với Redis

Giao diện Người dùng

Chúng tôi sẽ xây dựng một biểu mẫu đơn giản và một danh sách cho các mục cần làm:

// app/routes/index.tsx

import type { ActionFunction, LoaderFunction } from "remix";
import { Form, useLoaderData, useTransition, redirect } from "remix";
import { useEffect, useRef } from "react";
import type { Todo } from "~/components/todo-item";
import TodoItem from "~/components/todo-item";

export const loader: LoaderFunction = async () => {
  // example data
  return [
    { id: 1, text: "Task 1", status: false },
    { id: 2, text: "Task 2", status: true },
  ];
};

export const action: ActionFunction = async ({ request }) => {
  // this will be used for create, update and delete operations
};

export default function Index() {
  // for loading and form actions
  const transition = useTransition();

  // to use the loaded data in the page
  const todos: Todo[] = useLoaderData();

  const isCreating = transition.submission?.method === "POST";
  const isAdding = transition.state === "submitting" && isCreating;

  // split the finished and unfinished items
  const uncheckedTodos = todos.filter((todo) => !todo.status);
  const checkedTodos = todos.filter((todo) => todo.status);

  const formRef = useRef<HTMLFormElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    // reset the form after the create
    if (isAdding) return;
    formRef.current?.reset();
    inputRef.current?.focus();
  }, [isAdding]);

  return (
    <main className="container">
      {/* crete form */}
      <Form ref={formRef} method="post">
        <input
          ref={inputRef}
          type="text"
          name="text"
          autoComplete="off"
          className="input"
          placeholder="What needs to be done?"
          disabled={isCreating}
        />
      </Form>

      {/* uncompleted tasks */}
      <div className="todos">
        {uncheckedTodos.map((todo) => (
          <TodoItem key={todo.id} {...todo} />
        ))}
      </div>

      {/* completed tasks */}
      {checkedTodos.length > 0 && (
        <div className="todos todos-done">
          {checkedTodos.map((todo) => (
            <TodoItem key={todo.id} {...todo} />
          ))}
        </div>
      )}
    </main>
  );
}

Đây là thành phần CẦN LÀM của chúng tôi:

// app/components/todo-item.tsx

import { Form } from "remix";

export type Todo = { id: string; text: string; status: boolean };

export default function TodoItem({ id, text, status }: Todo) {
  return (
    <div className="todo">
      <Form method="put">
        {/* this hidden input will keep the data for our todo item */}
        <input
          type="hidden"
          name="todo"
          defaultValue={JSON.stringify({ id, text, status })}
        />
        {/* Remix forms are just like traditional web forms. I like this. */}
        <button type="submit" className="checkbox">
          {status && "✓"}
        </button>
      </Form>

      <span className="text">{text}</span>
    </div>
  );
}

Bây giờ là lúc để thêm tệp CSS của chúng tôi. Tạo tệp css app/styles/app.css :

:root {
  --rounded: 0.25rem;
  --rounded-md: 0.375rem;
  --gray-50: rgb(249, 250, 251);
  --gray-100: rgb(243, 244, 246);
  --gray-200: rgb(229, 231, 235);
  --gray-300: rgb(209, 213, 219);
  --gray-400: rgb(156, 163, 175);
  --gray-500: rgb(107, 114, 128);
  --gray-600: rgb(75, 85, 99);
  --gray-700: rgb(55, 65, 81);
  --gray-800: rgb(31, 41, 55);
  --gray-900: rgb(17, 24, 39);
}

*,
::before,
::after {
  box-sizing: border-box;
  border: 0;
  padding: 0;
}

button,
input,
optgroup,
select,
textarea {
  font-family: inherit;
  font-size: 100%;
  line-height: inherit;
  color: inherit;
  margin: 0;
  padding: 0;
}

button {
  cursor: pointer;
  background-color: white;
}

html {
  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe
      UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif,
    Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: var(--gray-800);
}

.container {
  padding: 8rem 1rem 0;
  margin: 0 auto;
  max-width: 28rem;
}

.input {
  width: 100%;
  padding: 0.75rem 1rem;
  background-color: var(--gray-100);
  border-radius: var(--rounded-md);
}

.input::placeholder {
  color: var(--gray-400);
}

.input:disabled {
  color: var(--gray-600);
  background-color: var(--gray-200);
}

.todos {
  margin-top: 1.5rem;
}

.todos.todos-done {
  background-color: var(--gray-100);
  color: var(--gray-500);
  border-radius: var(--rounded-md);
}

.todo {
  display: flex;
  align-items: center;
  padding: 0.75rem;
  border-radius: var(--rounded-md);
}

.todo + .todo {
  border-top: 1px solid var(--gray-100);
}

.todo .checkbox {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: var(--rounded);
  border: 1px solid var(--gray-300);
  box-shadow: 0 1px 1px 0 rgb(0 0 0 / 10%);
}

.todo .text {
  margin-left: 0.75rem;
}

Nhập css dưới root.tsx :

import {
  Links,
  LiveReload,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from "remix";
import type { MetaFunction } from "remix";
import styles from "./styles/app.css";

export function links() {
  return [{ rel: "stylesheet", href: styles }];
}

export const meta: MetaFunction = () => {
  return { title: "Remix Todo App with Redis" };
};

export default function App() {
  // ...
}

Bây giờ bạn sẽ thấy:

Phối lại ứng dụng TODO với Redis

Chuẩn bị cơ sở dữ liệu

Chúng tôi sẽ giữ dữ liệu của mình trong Upstash Redis. Vì vậy, hãy tạo một cơ sở dữ liệu Upstash. Chúng tôi sẽ sử dụng ứng dụng khách Upstash dựa trên HTTP. Hãy cài đặt:

npm install @upstash/redis

:::noteUpstash tương thích với API Redis, vì vậy bạn có thể sử dụng bất kỳ ứng dụng Redis nào nhưng bạn cần thay đổi mã bên dưới. :::

Chúng tôi có thể thêm các mục CẦN LÀM mới chỉ bằng cách gửi biểu mẫu. Chúng tôi lưu các mục mới vào Redis Hash.

Sao chép / dán UPSTASH_REDIS_REST_URL ve UPSTASH_REDIS_REST_TOKEN từ bảng điều khiển Upstash.

// app/routes/index.tsx

// ...
import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: "UPSTASH_REDIS_REST_URL",
  token: "UPSTASH_REDIS_REST_TOKEN",
});

export const action: ActionFunction = async ({ request }) => {
  const form = await request.formData();

  if (request.method === "POST") {
    const text = form.get("text");
    if (!text) return redirect("/");

    await redis.hset("remix-todo-example", {
      [Date.now().toString()]: {
        text,
        status: false,
      },
    });
  }

  // to fetch the list after each operation
  return redirect("/");
};

// ...

Bây giờ hãy liệt kê các mục:

// app/routes/index.tsx

export const loader: LoaderFunction = async () => {
  const res = await redis.hgetall<Record<string, object>>(DATABASE_KEY);
  const todos = Object.entries(res ?? {}).map(([key, value]) => ({
    id: key,
    ...value,
  }));
  // sort by date (id=timestamp)
  return todos.sort((a, b) => parseInt(b.id) - parseInt(a.id));
};

Chúng tôi có chức năng 'tạo' và 'danh sách'. Bây giờ chúng ta sẽ triển khai phần mà người dùng có thể đánh dấu một việc cần làm là xong.

// app/routes/index.tsx

export const action: ActionFunction = async ({ request }) => {
  const form = await request.formData();

  // create
  if (request.method === "POST") {
    // ...
  }

  // update
  if (request.method === "PUT") {
    const todo = form.get("todo");
    const { id, text, status } = JSON.parse(todo as string);

    await redis.hset("remix-todo-example", {
      [id]: {
        text,
        status: !status,
      },
    });
  }

  return redirect("/");
};

Bây giờ mọi thứ đã sẵn sàng! Tôi đang lên kế hoạch triển khai ứng dụng TODO tương tự với Next.js và SvelteKit. Sau đó, tôi sẽ so sánh kinh nghiệm của mình trong các khuôn khổ này.

Hãy theo dõi và theo dõi chúng tôi tại Twitter và Discord.

Mã nguồn dự án

https://github.com/upstash/redis-examples/tree/master/remix-todo-app-with-redis

Trang Demo Dự án

https://remix-todo-app-with-redis.vercel.app/