Remix는 기존 웹 표준에 초점을 맞추고 프론트엔드를 백엔드에 밀접하게 묶는 풀스택 React 프레임워크의 의미에 대한 새로운 해석입니다. 이 긴밀한 결합은 데이터를 React 구성 요소에 로드하는 것이 얼마나 간단한지 또는 양식에서 제출된 데이터를 처리하는 방법을 볼 때 신선한 공기를 불어넣습니다.
이 기사에서는 Upstash Redis를 데이터베이스로 사용하여 간단한 기능 플래그 관리 시스템을 만들어 Remix의 성능을 확인할 수 있습니다.
전체 소스 코드는 여기에서 찾을 수 있습니다.
설정
npx create-remix@latest
를 실행하면 새로운 Remix 앱을 얻을 수 있습니다. 선호하는 배포 환경을 선택합니다. Vercel을 사용했지만 이 튜토리얼에서는 차이가 없어야 합니다.
Upstash Redis에 연결할 수 있는 두 가지 방법이 있습니다. 첫 번째는 익숙한 표준 Redis 클라이언트 라이브러리를 사용할 수 있는 TCP 연결을 통한 것입니다. 두 번째는 Upstash의 REST API를 통한 것입니다. Remix를 배포할 수 있는 Cloudflare 작업자 환경과 같은 모든 서버리스 환경에서 사용할 수 있으므로 두 번째 옵션을 사용하겠습니다. Upstash에는 실제 Redis 명령을 모방한 패키지가 있어 호출할 함수를 쉽게 알 수 있습니다.
이제 Upstash Redis 데이터베이스에 연결하는 데 필요한 두 가지 환경 변수를 저장할 방법이 필요합니다. Remix는 기본적으로 개발 환경 변수 지원과 함께 제공되지 않지만 dotenv를 개발 종속성으로 추가하여 수행할 수 있습니다.
npm add --save-dev dotenv
.env
에서 파일(.gitignore
에 추가해야 함) ) Upstash Redis에 연결하는 데 필요한 두 개의 환경 변수를 설정할 수 있습니다. @upstash/redis
패키지는 이를 자동으로 감지하므로 코드 내에서 연결할 필요가 없습니다. 이 값은 새 Redis 데이터베이스를 생성한 후 Upstash 대시보드에서 찾을 수 있습니다.
UPSTASH_REDIS_REST_URL="https://..."
UPSTASH_REDIS_REST_TOKEN="..."
dev
를 업데이트해야 합니다. dotenv가 환경 변수를 선택하도록 하는 스크립트입니다. 다른 스크립트는 그대로 유지될 수 있습니다.
{
"scripts": {
"dev": "node -r dotenv/config node_modules/.bin/remix dev"
}
}
기능 데이터 저장
기능 플래그는 특정 사용자 그룹에 대해 활성화된 사용자 기반 비율에 대한 롤아웃 계획으로 엄청나게 복잡해질 수 있지만 "켜기" 및 "끄기"처럼 간단할 수도 있습니다. Redis가 제공하는 해시 데이터 유형을 사용하여 기능 플래그를 저장할 것입니다. 데이터는 "1"이 활성화/켜짐 및 "0"이 비활성화/꺼짐"인 아래 JSON과 같이 표시됩니다.
{
"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>
);
}
데이터 로더
데이터 로더는 loader
라는 Remix에서 내보낸 함수입니다. 서버에서 실행되고 후크를 통해 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 구성 요소에 대한 자세한 내용은 나중에 다루겠지만 로더 함수에서 반환된 데이터에 액세스하는 방법을 보여주기 위해 useLoaderData
라는 Remix 후크를 사용합니다. .
const FeatureList = () => {
const { features } = useLoaderData<LoaderData>();
return (
<ul>
{features.map(([feature, active]) => (
<li key={feature}>{/* coming soon */}</li>
))}
</ul>
);
};
양식 작업
데이터가 로드되는 방식을 보았지만 이 단계에서는 실제로 기능 플래그 데이터베이스에 기능이 없습니다! 여기에서 폼 액션이 작동합니다. 데이터는 action
이라는 함수를 내보내서 Remix에서 처리됩니다. . loader
와 매우 유사합니다. , 이것은 서버에서 실행되며 일반적으로 json
을 반환합니다. React 구성 요소가 다른 후크를 통해 액세스할 수 있는 데이터 또는 브라우저에 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
도 제공했습니다. 기능 플래그를 생성할 때마다 브라우저 기록에 새 페이지를 추가하지 않도록 prop.
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 };
};
이제 다시 Remix의 useLoaderData
를 사용하여 컴포넌트에 로드된 데이터에 액세스할 수 있습니다. 훅. 그런 다음 플래그가 현재 활성화되어 있는지 또는 비활성화되어 있는지에 따라 웹사이트 기능이 어떻게 변경되어야 하는지 선택합니다.
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를 설정하고 프론트엔드에서 표준 양식 제출 이벤트를 재정의할 필요 없이 빠르게 반복할 수 있습니다. 양식이 데이터를 제출하는 방법에 대해 웹 표준에 의존하는 것을 보아온 대로 리믹스하십시오.