ساخت برنامه های بلادرنگ با رویدادهای ارسالی از سرور 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