Remix เป็นแนวคิดใหม่ในความหมายของเฟรมเวิร์ก React แบบฟูลสแต็ค โดยเน้นที่มาตรฐานเว็บที่มีอยู่และเชื่อมโยงส่วนหน้าอย่างใกล้ชิดกับแบ็กเอนด์ การมีเพศสัมพันธ์ที่แน่นแฟ้นนี้เป็นการสูดอากาศบริสุทธิ์เมื่อคุณเห็นว่าการโหลดข้อมูลลงในส่วนประกอบ React ของคุณนั้นง่ายเพียงใด หรือวิธีประมวลผลข้อมูลที่ส่งจากแบบฟอร์ม
ในบทความนี้ เราจะเห็นพลังของ Remix โดยการสร้างระบบการจัดการฟีเจอร์แฟล็กอย่างง่ายโดยใช้ Upstash Redis เป็นฐานข้อมูล
สามารถดูซอร์สโค้ดแบบเต็มได้ที่นี่
ตั้งค่า
คุณจะได้รับแอป Remix ใหม่ล่าสุดโดยเรียกใช้ npx create-remix@latest
และเลือกสภาพแวดล้อมการปรับใช้ที่คุณต้องการ ฉันใช้ Vercel แต่ไม่ควรสร้างความแตกต่างให้กับบทช่วยสอนนี้
มีสองวิธีที่คุณสามารถเชื่อมต่อกับ Upstash Redis:วิธีแรกคือผ่านการเชื่อมต่อ TCP ซึ่งคุณสามารถใช้ไลบรารีไคลเอ็นต์ Redis มาตรฐานที่คุณคุ้นเคยได้ ประการที่สองคือผ่าน REST API ของ Upstash เราจะใช้ตัวเลือกที่สองเพราะสามารถใช้ได้ในทุกสภาพแวดล้อมแบบไร้เซิร์ฟเวอร์ เช่น สภาพแวดล้อม Cloudflare Workers ที่ Remix สามารถใช้งานได้ Upstash มีแพ็คเกจที่เลียนแบบคำสั่ง Redis จริง ทำให้ง่ายต่อการทราบว่าควรเรียกใช้ฟังก์ชันใด
ตอนนี้เราต้องการวิธีจัดเก็บตัวแปรสภาพแวดล้อมสองแบบที่จำเป็นในการเชื่อมต่อกับฐานข้อมูล Upstash Redis ของเรา Remix ไม่ได้มาพร้อมกับการรองรับ env var แบบสำเร็จรูป แต่สามารถทำได้โดยการเพิ่ม dotenv เป็นการพึ่งพาการพัฒนา
npm add --save-dev dotenv
ใน .env
. ของเรา ไฟล์ (ซึ่งควรเพิ่มใน .gitignore
) เราสามารถตั้งค่า env vars สองรายการที่จำเป็นในการเชื่อมต่อกับ Upstash Redis @upstash/redis
แพ็คเกจจะตรวจจับสิ่งเหล่านี้โดยอัตโนมัติ ดังนั้นจึงไม่จำเป็นต้องเชื่อมต่อภายในรหัสของเรา ค่าเหล่านี้สามารถพบได้ในแดชบอร์ด Upstash หลังจากสร้างฐานข้อมูล Redis ใหม่
UPSTASH_REDIS_REST_URL="https://..."
UPSTASH_REDIS_REST_TOKEN="..."
เราจำเป็นต้องอัปเดต dev
. ของเรา สคริปต์เพื่อให้ dotenv เลือก env vars สคริปต์อื่นๆ ยังคงเหมือนเดิม
{
"scripts": {
"dev": "node -r dotenv/config node_modules/.bin/remix dev"
}
}
การจัดเก็บข้อมูลคุณลักษณะ
แฟล็กคุณลักษณะอาจซับซ้อนอย่างไม่น่าเชื่อ โดยมีแผนเปิดตัวเป็นเปอร์เซ็นต์ของฐานผู้ใช้ของคุณ ซึ่งเปิดใช้งานสำหรับกลุ่มผู้ใช้บางกลุ่ม แต่ก็สามารถทำได้ง่ายๆ เช่น "เปิด" และ "ปิด" เราจะจัดเก็บแฟล็กคุณลักษณะของเราโดยใช้ประเภทข้อมูลแฮชที่ Redis มีให้ ข้อมูลของเราจะมีลักษณะเหมือน JSON ด้านล่าง โดยที่ "1" ถูกเปิดใช้งาน/เปิด และ "0" ถูกปิดใช้งาน/ปิด"
{
"chart": "1",
"graph": "0"
}
ในการเข้าถึงและจัดการข้อมูลนี้ เราจะใช้คำสั่ง/ฟังก์ชันสี่คำสั่งจาก Redis:
- hgetall เพื่อดึงคีย์ทั้งหมด (คุณสมบัติ) และค่า (เปิดใช้งาน/ปิดใช้งาน)
- hset เพื่อเปิดใช้งานหรือปิดใช้งานการตั้งค่าสถานะเฉพาะ
- hdel เพื่อลบการตั้งค่าสถานะเฉพาะ
- hmget เพื่อรับค่าแฟล็กคุณลักษณะหลายค่าพร้อมกัน
การจัดการคุณลักษณะ
เราจะสร้างหน้าที่อยู่ที่ /features
ซึ่งรับผิดชอบในการสร้างและจัดการ (เปิด/ปิด/ลบ) คุณลักษณะที่มีอยู่ เราจะลงรายละเอียดเกี่ยวกับสิ่งที่ AddFeature
และ FeatureList
ทำเมื่อเราพูดถึงวิธีการโหลดข้อมูลและวิธีเขียนข้อมูล
// app/routes/features.tsx
export default function Features() {
return (
<div>
<h1>Features</h1>
<AddFeature />
<FeatureList />
</div>
);
}
ตัวโหลดข้อมูล
ตัวโหลดข้อมูลเป็นฟังก์ชันที่ส่งออกใน Remix ชื่อ loader
ซึ่งทำงานบนเซิร์ฟเวอร์และส่งคืนข้อมูลที่มีให้สำหรับองค์ประกอบ React ของเราผ่านตะขอ
เรากำลังเริ่มต้นด้วยหน้าเพื่อสร้างและจัดการการตั้งค่าสถานะคุณลักษณะ และในกรณีนี้ เราต้องการส่งคืนคุณลักษณะทั้งหมด โดยจะส่งกลับเป็นอาร์เรย์ของคู่:
[
["graph", true],
["chart", false]
]
เริ่มต้นด้วยคำจำกัดความประเภท TypeScript จากนั้นเราจะเห็นฟังก์ชันชื่อ loadAllFeatures
ที่ใช้ hgetall
ฟังก์ชันจาก @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;
});
};
loader
.ที่ส่งออก ฟังก์ชั่นจะเรียก loadAllFeatures
ฟังก์ชั่นส่งคืนคุณสมบัติที่จะส่งผ่านไปยังองค์ประกอบ React
export const loader: LoaderFunction = async (): Promise<LoaderData> => {
// You would want to add authentication/authorization here
const features = await loadAllFeatures();
return { features };
};
เราจะพูดถึงรายละเอียดขององค์ประกอบ React นี้ในภายหลัง แต่เพื่อแสดงวิธีที่คุณเข้าถึงข้อมูลที่ส่งคืนจากฟังก์ชันตัวโหลด คุณใช้ Remix hook ชื่อ useLoaderData
.
const FeatureList = () => {
const { features } = useLoaderData<LoaderData>();
return (
<ul>
{features.map(([feature, active]) => (
<li key={feature}>{/* coming soon */}</li>
))}
</ul>
);
};
การทำงานของแบบฟอร์ม
เราได้เห็นแล้วว่าข้อมูลถูกโหลดอย่างไร แต่ในขั้นตอนนี้ เราไม่มีคุณสมบัติใดๆ ในฐานข้อมูลแฟล็กคุณลักษณะของเรา! นี่คือจุดที่การกระทำของฟอร์มเข้ามาเล่น ข้อมูลถูกประมวลผลใน Remix โดยการส่งออกฟังก์ชันชื่อ action
. เหมือน loader
, สิ่งนี้ทำงานบนเซิร์ฟเวอร์และโดยทั่วไปจะส่งคืน json
ข้อมูลที่องค์ประกอบ React สามารถเข้าถึงได้ผ่าน hook อื่น หรือสามารถบอกให้เบราว์เซอร์ redirect
ไปหน้าอื่น
action
ฟังก์ชันด้านล่างจะจัดการกับการกระทำต่างๆ สี่ประเภทอย่างแท้จริง การสร้างคุณลักษณะ การเปิด/ปิดคุณลักษณะ และการลบคุณลักษณะ เราจัดการสิ่งนี้ด้วย switch
คำสั่งที่เรียกใช้ฟังก์ชัน/คำสั่ง Redis ที่เหมาะสม
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");
};
คุณจะสังเกตเห็นว่าเมื่อประสบความสำเร็จ ฉันเปลี่ยนเส้นทางไปยังหน้าเดียวกับที่ผู้ใช้กำลังเปิดอยู่ สิ่งนี้จะทริกเกอร์การโหลดหน้าซ้ำ โดยเรียก loader
ฟังก์ชันและอัปเดตสิ่งที่แสดงต่อผู้ใช้
ในการเพิ่มการตั้งค่าสถานะคุณลักษณะใหม่ AddFeature
คอมโพเนนต์จะใช้ Remix Form
องค์ประกอบที่จะส่งข้อมูลไปยังฟังก์ชันการทำงานที่เราเห็นด้านบน ฉันระบุว่าควรส่งผ่าน post
วิธีการและยังให้ replace
เพื่อไม่ให้เพิ่มหน้าใหม่ในประวัติของเบราว์เซอร์ทุกครั้งที่เราสร้างการตั้งค่าสถานะคุณลักษณะ
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>
);
};
เมื่อสร้างคุณลักษณะแล้ว เราจะต้องการแสดงการตั้งค่าสถานะปัจจุบันทั้งหมดเพื่อให้สามารถจัดการได้ อันที่จริงแล้ว แฟล็กของฟีเจอร์จะแสดงสองรูปแบบ:อันแรกสำหรับเปิด/ปิดแฟล็กของฟีเจอร์ และอีกอันสำหรับลบออก
โปรดทราบว่ามีสองฟิลด์ที่ซ่อนอยู่:_action
เพื่อให้ action
. ของเรา ฟังก์ชั่นรู้ว่าเรากำลังพยายามทำอะไรกับคุณลักษณะนี้และ feature
ซึ่งส่งชื่อแฟล็กที่เราต้องการแก้ไข
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>
);
};
การใช้คุณลักษณะ
เรามีการตั้งค่าสถานะคุณลักษณะในฐานข้อมูล Upstash Redis ของเรา แต่สิ่งที่ดีคือถ้าเราไม่เปิดหรือปิดฟังก์ชันการทำงานในแอปของเราตามการตั้งค่าสถานะเหล่านี้ เราจะใช้ฟังก์ชันตัวโหลดเพื่อโหลดคุณสมบัติเฉพาะจากฐานข้อมูลโดยใช้ hmget
แล้วจัดการข้อมูลเล็กน้อยเพื่อให้มีโครงสร้างที่ถูกต้อง
หากเราต้องการโหลด ["chart", "graph", "fake"]
ตั้งค่าสถานะ Redis จะส่งคืน ["1", "0", null]
... โปรดทราบว่าหากไม่มีแฟล็ก ค่าจะเป็น null
ซึ่งฉันต้องการแสดงโดยใส่ fake
ธง.
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 };
};
ขณะนี้ เราสามารถเข้าถึงข้อมูลที่โหลดในคอมโพเนนต์ของเราได้อีกครั้งโดยใช้ useLoaderData
. ของ Remix ตะขอ. จากนั้นเลือกว่าหน้าที่การทำงานของเว็บไซต์ของเราควรเปลี่ยนแปลงอย่างไรไม่ว่าจะเปิดหรือปิดการตั้งค่าสถานะ
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>
);
}
บทสรุป
ในบทความนี้ เราได้เห็นวิธีการใช้ Upstash Redis เพื่อสร้างระบบแฟล็กฟีเจอร์อย่างง่ายใน Remix โดยใช้ประโยชน์จากตัวโหลดข้อมูลและฟอร์มฟังก์ชันฝั่งเซิร์ฟเวอร์ สิ่งเหล่านี้ช่วยให้เรารักษาแบ็กเอนด์และฟรอนท์เอนด์ของหน้าเฉพาะไว้อย่างแนบแน่น ทำซ้ำได้อย่างรวดเร็วโดยไม่จำเป็นต้องตั้งค่า GraphQL API แยกต่างหากและแทนที่เหตุการณ์การส่งแบบฟอร์มมาตรฐานในส่วนหน้า รีมิกซ์ในขณะที่เราเห็นว่าใช้มาตรฐานเว็บเกี่ยวกับวิธีที่แบบฟอร์มส่งข้อมูลของพวกเขา