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

Sử dụng Upstash Redis với Remix

Remix là một bước mới về ý nghĩa của việc trở thành một khung React fullstack, tập trung vào các tiêu chuẩn web hiện có và liên kết chặt chẽ giao diện người dùng với phần phụ trợ. Sự kết hợp chặt chẽ này là một luồng gió mới khi bạn thấy việc tải dữ liệu vào các thành phần React của mình đơn giản như thế nào hoặc cách xử lý dữ liệu được gửi từ một biểu mẫu.

Trong bài viết này, chúng ta sẽ thấy sức mạnh của Remix bằng cách tạo một hệ thống quản lý Cờ nổi bật đơn giản bằng cách sử dụng Upstash Redis làm cơ sở dữ liệu.

Mã nguồn đầy đủ có thể được tìm thấy tại đây.

Thiết lập

Bạn sẽ nhận được một ứng dụng Remix hoàn toàn mới bằng cách chạy npx create-remix@latest và chọn môi trường triển khai ưa thích của bạn. Tôi đã sử dụng Vercel nhưng nó sẽ không tạo ra sự khác biệt cho hướng dẫn này.

Có hai cách bạn có thể kết nối với Upstash Redis:Cách thứ nhất là thông qua kết nối TCP, trong đó bạn có thể sử dụng bất kỳ thư viện máy khách Redis tiêu chuẩn nào mà bạn đã quen. Thứ hai là thông qua API REST của Upstash. Chúng tôi sẽ sử dụng tùy chọn thứ hai vì nó có sẵn trong tất cả các môi trường không máy chủ, chẳng hạn như môi trường Cloudflare worker mà Remix có thể được triển khai. Upstash có một gói bắt chước các lệnh Redis thực tế, giúp bạn dễ dàng biết hàm nào cần gọi.

Bây giờ chúng tôi cần một cách để lưu trữ hai biến môi trường cần thiết để kết nối với cơ sở dữ liệu Upstash Redis của chúng tôi. Phối lại không đi kèm với sự hỗ trợ của env var phát triển, nhưng nó có thể được thực hiện bằng cách thêm dotenv làm phụ thuộc phát triển.

npm add --save-dev dotenv

Trong .env của chúng tôi tệp (cần được thêm vào .gitignore ) chúng ta có thể thiết lập hai env vars cần thiết để kết nối với Upstash Redis. @upstash/redis gói tự động phát hiện những điều này nên không cần kết nối trong mã của chúng tôi. Bạn có thể tìm thấy các giá trị này trong bảng điều khiển Upstash sau khi tạo cơ sở dữ liệu Redis mới.

UPSTASH_REDIS_REST_URL="https://..."
UPSTASH_REDIS_REST_TOKEN="..."

Chúng tôi cần cập nhật dev của mình script để dotenv nhận các env vars. Các tập lệnh khác có thể giữ nguyên.

{
  "scripts": {
    "dev": "node -r dotenv/config node_modules/.bin/remix dev"
  }
}

Lưu trữ dữ liệu tính năng

Cờ tính năng có thể trở nên vô cùng phức tạp, với các kế hoạch triển khai đến tỷ lệ phần trăm cơ sở người dùng của bạn, được kích hoạt cho các nhóm người dùng cụ thể, nhưng chúng cũng có thể đơn giản như "bật" và "tắt". Chúng tôi sẽ lưu trữ các cờ đặc trưng của mình bằng cách sử dụng kiểu dữ liệu Hash mà Redis cung cấp. Dữ liệu của chúng tôi sẽ trông giống như JSON bên dưới, trong đó "1" được bật / bật và "0" bị vô hiệu hóa / tắt ".

{
  "chart": "1",
  "graph": "0"
}

Để truy cập và thao tác dữ liệu này, chúng tôi sẽ sử dụng bốn lệnh / chức năng do Redis cung cấp:

  • hgetall để truy xuất tất cả các khóa (tính năng) và giá trị (đã bật / tắt).
  • hset để bật hoặc tắt một cờ tính năng cụ thể.
  • hướng dẫn để xóa một cờ tính năng cụ thể.
  • hmget để nhận nhiều giá trị cờ tính năng nhưng cụ thể cùng một lúc.

Quản lý các tính năng

Chúng tôi sẽ xây dựng một trang có địa chỉ tại /features chịu trách nhiệm tạo và quản lý (bật / tắt / xóa) các tính năng hiện có. Chúng ta sẽ đi vào chi tiết về những gì AddFeatureFeatureList thực hiện khi chúng ta thảo luận về cách tải dữ liệu và sau đó là cách ghi dữ liệu.

// app/routes/features.tsx
export default function Features() {
  return (
    <div>
      <h1>Features</h1>
      <AddFeature />
      <FeatureList />
    </div>
  );
}

Bộ tải dữ liệu

Trình tải dữ liệu là một chức năng được xuất trong Phối lại có tên là loader được chạy trên máy chủ và trả về dữ liệu có sẵn cho thành phần React của chúng tôi thông qua một hook.

Chúng tôi đang bắt đầu với một trang để tạo và quản lý cờ tính năng và trong trường hợp này, chúng tôi muốn trả lại tất cả các tính năng. Chúng sẽ được trả về dưới dạng một mảng các cặp:

[
  ["graph", true],
  ["chart", false]
]

Bắt đầu với định nghĩa kiểu TypeScript, sau đó chúng ta sẽ thấy một hàm có tên là loadAllFeatures sử dụng hgetall chức năng từ @upstash/redis .

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

type LoaderData = {
  features: Array<[string, boolean]>;
};

const loadAllFeatures = async () => {
  const redis = Redis.fromEnv();
  const data = await redis.hgetall("features");
  const features: Array<[string, boolean]> = [];

  for (let i = 0; i < data.length; i += 2) {
    features.push([data[i], data[i + 1] === "1"]);
  }

  return features.sort((a, b) => {
    if (a[0] > b[0]) return 1;
    if (a[0] < b[0]) return -1;
    return 0;
  });
};

Trình tải loader đã xuất chính hàm sẽ gọi loadAllFeatures , trả về các tính năng sẽ được chuyển vào thành phần React.

export const loader: LoaderFunction = async (): Promise<LoaderData> => {
  // You would want to add authentication/authorization here
  const features = await loadAllFeatures();
  return { features };
};

Chúng tôi sẽ trình bày chi tiết về thành phần React này sau, nhưng để hiển thị cách bạn truy cập dữ liệu được trả về từ chức năng trình tải, bạn sử dụng móc Phối lại có tên là useLoaderData .

const FeatureList = () => {
  const { features } = useLoaderData<LoaderData>();

  return (
    <ul>
      {features.map(([feature, active]) => (
        <li key={feature}>{/* coming soon */}</li>
      ))}
    </ul>
  );
};

Hành động trên biểu mẫu

Chúng tôi đã thấy cách dữ liệu được tải, nhưng ở giai đoạn này, chúng tôi thực sự không có bất kỳ tính năng nào trong cơ sở dữ liệu cờ tính năng của mình! Đây là nơi mà các hành động biểu mẫu phát huy tác dụng. Dữ liệu được xử lý trong Remix bằng cách xuất một hàm có tên là action . Giống như loader , điều này được chạy trên máy chủ và nó thường trả về json dữ liệu mà thành phần React có thể truy cập thông qua một hook khác hoặc nó có thể yêu cầu trình duyệt redirect sang một trang khác.

Hành động loader hàm bên dưới thực sự xử lý bốn loại hành động khác nhau, tạo một tính năng, bật / tắt một tính năng và xóa một tính năng. Chúng tôi xử lý điều này bằng một switch câu lệnh sau đó gọi hàm / lệnh Redis thích hợp.

export const action: ActionFunction = async ({ request }) => {
  // You would want to add authentication/authorization here
  const formData = await request.formData();
  const feature = formData.get("feature") as string;
  const action = formData.get("_action") as string;

  if (!feature || feature.length === 0) {
    // This isn't currently displayed in our component
    return json({ error: "Please provide a feature" });
  }

  switch (action) {
    case "create":
    case "enable":
      await redis.hset("features", { [feature]: 1 });
      break;
    case "disable":
      await redis.hset("features", { [feature]: 0 });
      break;
    case "delete":
      await redis.hdel("features", feature);
      break;
  }

  return redirect("/features");
};

Bạn sẽ nhận thấy rằng khi thành công, tôi chuyển hướng đến cùng một trang mà người dùng hiện đang truy cập. Về cơ bản, điều này sẽ kích hoạt tải lại trang, gọi loader chức năng và cập nhật những gì được hiển thị cho người dùng.

Để thêm cờ tính năng mới, AddFeature thành phần sẽ sử dụng Remix Form thành phần sẽ gửi dữ liệu đến hàm hành động mà chúng ta đã thấy ở trên. Tôi đã chỉ định rằng nó nên gửi qua post và cũng cung cấp replace chống đỡ để nó không thêm trang mới vào lịch sử của trình duyệt mỗi khi chúng tôi tạo cờ tính năng.

const AddFeature = () => {
  return (
    <Form method="post" replace>
      <input type="hidden" name="_action" value="create" />
      <input type="text" name="feature" required placeholder="name" />
      <button type="submit">Add</button>
    </Form>
  );
};

Khi một đối tượng địa lý đã được tạo, chúng tôi sẽ muốn hiển thị tất cả các cờ đối tượng địa lý hiện tại để chúng có thể được quản lý. Mỗi cờ tính năng thực sự hiển thị hai dạng:Một để bật / tắt cờ tính năng và một để xóa nó.

Lưu ý rằng có hai trường ẩn:_action để action của chúng tôi hàm biết những gì chúng tôi đang cố gắng thực hiện đối với tính năng và feature sẽ gửi tên cờ mà chúng tôi muốn sửa đổi.

const FeatureList = () => {
  const { features } = useLoaderData<LoaderData>();

  return (
    <ul>
      {features.map(([feature, active]) => (
        <li key={feature}>
          <Form method="post" replace>
            <input
              type="hidden"
              name="_action"
              value={active ? "disable" : "enable"}
            />
            <input type="hidden" name="feature" value={feature} />
            <button type="submit" className="btn-naked">
              {active ? "💪" : "🦾"}
            </button>
          </Form>

          <span>{feature}</span>

          <Form method="post" replace>
            <input type="hidden" name="_action" value="delete" />
            <input type="hidden" name="feature" value={feature} />
            <button type="submit">Delete</button>
          </Form>
        </li>
      ))}
    </ul>
  );
};

Sử dụng Tính năng

Chúng tôi có các cờ tính năng trong cơ sở dữ liệu Upstash Redis của mình, nhưng điều tốt là gì nếu chúng tôi không bật hoặc tắt chức năng trong ứng dụng của mình dựa trên các cờ này. Chúng tôi sẽ sử dụng chức năng trình tải để tải các tính năng cụ thể từ cơ sở dữ liệu bằng cách sử dụng hmget và sau đó là một chút thao tác dữ liệu để đưa nó vào đúng cấu trúc.

Nếu chúng ta muốn tải ["chart", "graph", "fake"] cờ, Redis sẽ trả về cho chúng tôi ["1", "0", null] ... hãy nhớ rằng nếu cờ không tồn tại, giá trị của nó sẽ là null , mà tôi muốn hiển thị bằng cách bao gồm fake cờ.

type LoaderData = {
  features: Record<string, boolean>;
};

const loadFeatures = async (keys: Array<string>) => {
  const data = await redis.hmget("features", ...keys);

  const features = keys.reduce<Record<string, boolean>>((acc, key, index) => {
    acc[key] = data[index] === "1";
    return acc;
  }, {});

  return features;
};

export const loader: LoaderFunction = async (): Promise<LoaderData> => {
  const features = await loadFeatures(["chart", "graph"]);
  return { features };
};

Bây giờ chúng tôi có thể truy cập dữ liệu đã tải trong thành phần của mình, một lần nữa bằng cách sử dụng useLoaderData của Remix cái móc. Sau đó, chọn cách chức năng của trang web của chúng tôi sẽ thay đổi cho dù cờ hiện đang được bật hay bị vô hiệu hóa.

export default function Index() {
  const { features } = useLoaderData<LoaderData>();

  return (
    <div>
      <h1>Dashboard</h1>
      {features.chart ? <h2>Chart</h2> : <h2>No Chart</h2>}
      {features.graph ? <h2>Graph</h2> : <h2>No Graph</h2>}
    </div>
  );
}

Kết luận

Trong bài viết này, chúng tôi đã giới thiệu cho các bạn cách sử dụng Upstash Redis để tạo hệ thống cờ tính năng đơn giản trong Remix, tận dụng các chức năng phía máy chủ hành động và trình tải dữ liệu của nó. Những điều này cho phép chúng tôi giữ cho phần phụ trợ và giao diện người dùng của một trang cụ thể được liên kết chặt chẽ, lặp lại nhanh chóng mà không cần thiết lập API GraphQL riêng biệt và ghi đè các sự kiện gửi biểu mẫu tiêu chuẩn trên giao diện người dùng. Phối lại như chúng ta đã thấy dựa trên các tiêu chuẩn web về cách các biểu mẫu gửi dữ liệu của chúng.