برنامه نویسی

ساخت برنامه های بلادرنگ با رویدادهای ارسالی از سرور Remix.js و صف های شغلی

آنچه خواهید آموخت

امیدوارم پس از خواندن این مقاله، پیشینه کافی برای ایجاد برنامه‌های پیچیده‌تر رویداد محور در Remix.js، با استفاده از SSE و Job Queues داشته باشید.

برنامه نهایی

این مقاله چه چیزی را پوشش می دهد

ما چندین جنبه را پوشش خواهیم داد، از جمله:

  • مسیریابی Remix.js
  • صف های شغلی کویرل

  • رویدادهای ارسال شده توسط سرور Remix.js

پیش نیازها

قبل از شروع مقاله، توصیه می شود که از React، Remix آگاهی داشته باشید و مفاهیمی از صف ها و رویدادهای ارسال شده توسط سرور داشته باشید.

ایجاد پروژه

برای مقداردهی اولیه یک پروژه در Remix دستور زیر را اجرا می کنیم:

npx create-remix@latest nixy
cd nixy
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سرور توسعه دهنده را با دستور زیر راه اندازی می کنیم:

npm run dev
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما قصد داریم از Just the basics نوع، با هدف استقرار Remix App Server و قرار است استفاده کنیم TypeScript در برنامه

وابستگی های لازم را نصب کنید:

npm install quirrel remix-utils superjson dayjs cuid
npm install -D concurrently
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در package.json فایل را تغییر می دهیم dev اسکریپت به موارد زیر:

{
  "dev": "concurrently 'remix dev' 'quirrel'",
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به این ترتیب، در طول توسعه برنامه، سرور Quirrel به طور همزمان با سرور Next اجرا می شود. برای دسترسی به رابط کاربری Quirrell کافیست دستور زیر را اجرا کنید:

npm run quirrel ui
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Event Emitter را تنظیم کنید

در مقاله امروز ما یک مثال ساده خواهیم داشت که در آن تنها یک فرآیند در حال اجرا است که به این معنی است که ما یک نمونه واحد در دسترس داریم و برای این مورد استفاده از EventEmitter ایده آل است، اما سایرین باید انجام شوند. اگر نمونه های زیادی دارید گزینه های در نظر گرفته شده را در نظر بگیرید.

// @/app/common/emitter.ts
import { EventEmitter } from "events";

let emitter: EventEmitter;

declare global {
  var __emitter: EventEmitter | undefined;
}

if (process.env.NODE_ENV === "production") {
  emitter = new EventEmitter();
} else {
  if (!global.__emitter) {
    global.__emitter = new EventEmitter();
  }
  emitter = global.__emitter;
}

export { emitter };
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با EventEmitter نمونه ایجاد شده، می توانیم به مرحله بعدی برویم.

Job Queue را تنظیم کنید

صفی که می خواهیم ایجاد کنیم کاملاً قابل درک است، ابتدا انواع داده های آن را تعریف می کنیم Queue payload که در این صورت باید شناسه را اضافه کنیم. سپس، در داخل callback، کاری که ما انجام می دهیم این است که یک رویداد جدید با در نظر گرفتن نام صف و داده های مربوطه منتشر می کنیم. Job که می خواهیم ارسال کنیم، که در این مورد نیاز به سریال سازی دارد.

// @/app/queues/add.server.ts
import { Queue } from "quirrel/remix";
import superjson from "superjson";

import { emitter } from "~/common/emitter";

export const addQueueEvtName = "addJobQueue";

export default Queue<{ identifier: string }>("queue/add", async (job) => {
  emitter.emit(
    addQueueEvtName,
    superjson.stringify({ identifier: job.identifier })
  );
});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با ایجاد صف می توانیم به مرحله بعدی برویم.

مسیرها را تنظیم کنید

اکنون که همه چیزهایی را که باید استفاده کنیم آماده داریم، می‌توانیم مسیرهای برنامه خود را تعریف کنیم. مسیرهایی که در برنامه خواهیم داشت به شرح زیر است:

  • _index.tsx – مسیر اصلی برنامه، که در آن تمام بخش زمان واقعی قابل مشاهده خواهد بود.
  • queue.add.ts – این مسیر را در معرض دید قرار می دهد Queue که به عنوان یک عمل ایجاد شد تا بتوان آن را در معرض دید و مصرف قرار داد.
  • sse.add.ts – این مسیر یک جریان رویداد ایجاد می کند که هر یک از رویدادها را به رابط کاربری هدایت می کند.

با مسیرهای فوق ایجاد شده در داخل routes/ پوشه، ما می توانیم روی مسیری که مسئول جریان رویداد است کار کنیم:

// @/app/routes/sse.add.ts
import type { LoaderFunction } from "@remix-run/node";
import { eventStream } from "remix-utils";

import { emitter } from "~/common/emitter";
import { addQueueEvtName } from "~/queues/add.server";

export const loader: LoaderFunction = ({ request }) => {
  // event stream setup
  return eventStream(request.signal, (send) => {
    // listener handler
    const listener = (data: string) => {
      // data should be serialized
      send({ data });
    };

    // event listener itself
    emitter.on(addQueueEvtName, listener);

    // cleanup
    return () => {
      emitter.off(addQueueEvtName, listener);
    };
  });
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در قطعه کد بالا، از نمونه Emitter رویداد برای گوش دادن به هر یک از رویدادهایی که منتشر می شوند و با استفاده از eventStream تابع وابستگی remix-utils می‌توانیم راه‌اندازی به‌روزرسانی‌های زنده را از باطن به مشتری ساده کنیم.

حرکت در حال حاضر به Queue ثبت مسیر همانطور که قبلا ذکر شد به این صورت خواهد بود:

// @/app/routes/queue.add.ts
import addQueue from "~/queues/add.server";

export const action = addQueue;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در قطعه کد بالا، صفی را وارد کردیم که در گذشته ایجاد شده بود و با استفاده از action ابتدایی ریمیکس

آخرین اما نه کم‌اهمیت، اکنون می‌توانیم روی سمت کلاینت برنامه کار کنیم، جایی که می‌توانیم از همه چیزهایی که تاکنون ایجاد شده است استفاده کنیم. صفحه دارای دکمه ای است که یک عمل سمت سرور را برای اضافه کردن یک مورد جدید فراخوانی می کند Job به Queue.

این Job بسیار ساده است، کافی است یک شناسه منحصر به فرد را ارسال کنید، به طوری که به صورت بصری بتوانیم تشخیص دهیم که هر رویداد منتشر شده واقعا منحصر به فرد است و در صف یک تاخیر پنج ثانیه اضافه می کنیم تا تضمین کنیم که Job واقعا وارد صف شده است و هر یک از آنها پردازش می شود.

سپس، در سمت UI، از آن استفاده خواهیم کرد useEventSource برای اتصال کامپوننت/صفحه به جریان رویدادی که قبلا ایجاد شده و با استفاده از a useEffect ما پیام های هر رویداد منتشر شده را به وضعیت محلی مولفه اضافه می کنیم. این به منظور به روز رسانی لیست نامرتب که در آن داریم JSX از صفحه بدین ترتیب:

// @/app/routes/_index.tsx
import type { V2_MetaFunction } from "@remix-run/node";
import { Form } from "@remix-run/react";
import { useEffect, useState } from "react";
import { useEventSource } from "remix-utils";
import cuid from "cuid";
import superjson from "superjson";
import dayjs from "dayjs";

import addQueue from "~/queues/add.server";

export const meta: V2_MetaFunction = () => {
  return [{ title: "SSE and Quirrel" }];
};

export const action = async () => {
  const currentTime = dayjs();
  const newTime = currentTime.add(5, "second");

  await addQueue.enqueue({ identifier: cuid() }, { runAt: newTime.toDate() });

  return null;
};

export default function Index() {
  const [messages, setMessages] = useState<{ identifier: string }[]>([]);
  const lastMessage = useEventSource("/sse/add");

  useEffect(() => {
    setMessages((datums) => {
      if (lastMessage !== null) {
        return datums.concat(superjson.parse(lastMessage));
      }
      return datums;
    });
  }, [lastMessage]);

  return (
    <div>
      <h2>Server-sent events and Quirrel</h2>

      <ul>
        {messages.map((message, messageIdx) => (
          <li key={messageIdx}>{message.identifier}</li>
        ))}
      </ul>

      <Form method="POST">
        <button type="submit">Add New Job</button>
      </Form>
    </div>
  );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و با آن آخرین مرحله این مقاله را به پایان می برم. شایان ذکر است که اگرچه مثال ساده است، اما در نهایت به عنوان مبنایی برای ایجاد سیستم‌های پیچیده‌تر عمل می‌کند که به عنوان مثال، می‌توانیم از صف‌ها برای محدود کردن تعداد درج‌ها و به‌روزرسانی‌هایی که در پایگاه داده انجام می‌شود استفاده کنیم تا کاهش یابد. فشار برگشتی یا زمان‌بندی ایجاد کنید تا مجموعه‌ای از کارها مانند یادآوری‌ها، پیام‌های خودکار، اعلان‌ها و غیره اجرا شوند. از اینجا استفاده‌های آن متفاوت است.

نتیجه

امیدوارم این مقاله برای شما مفید بوده باشد، چه در حال استفاده از اطلاعات در یک پروژه موجود یا صرفاً برای سرگرمی آن را امتحان کنید.

لطفا در صورت مشاهده هر گونه اشتباه در مقاله با ارسال نظر به من اطلاع دهید. و اگر می‌خواهید کد منبع این مقاله را ببینید، می‌توانید آن را در مخزن github لینک زیر پیدا کنید.

Github Repo

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا