git: 2dc9da156e2f18b7a6a5e4c9636279faa9e00409
Relay를 프론트엔드에서 사용하기 위해서는 다음 작업들이 필요함.
relay-compiler와 babel-plugin-relay의 역할 이해vite.config.ts에서 Babel 및 Plugin 설정fetchGraphQL.ts, relayEnvironment.ts 파일 작성yarn add react-relay relay-runtime graphql
yarn add -D relay-compiler vite-plugin-relay
vite.config.ts)import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import svgr from "vite-plugin-svgr";
import relay from "vite-plugin-relay";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
return {
plugins: [
svgr(),
react(),
relay, // default export된 Plugin 객체 그대로 사용
],
};
});
vite-plugin-relay 사용법은 GPT가 알려주는 걸 그대로 사용하면 계속 에러나서 못씀.
맨날 맞다고 우기는거 제일 짜증..
vite에서의 relay 플러그인 사용법은 다음 두 곳을 참조하면 됨. 위의 코드는 두번째 방법을 사용한 것.
GraphQL 요청을 axios를 통해 보내는 함수 정의. Rest API 용 axios 와 구분하기 위해 axiosInstanceGraphql 을 만들어두고 이걸 연결시킴.
import { RequestParameters, Variables } from "relay-runtime";
import { axiosInstanceGraphql } from "../axios/axiosInstanceGraphql";
export async function fetchGraphQL(
params: RequestParameters,
variables: Variables,
) {
const response = await axiosInstanceGraphql.post("", {
query: params.text,
variables,
});
return response.data;
}
Relay 네트워크와 캐시 저장소 설정
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import { fetchGraphQL } from "./fetchGraphQL";
export const relayEnvironment = new Environment({
network: Network.create(fetchGraphQL),
store: new Store(new RecordSource()),
});
React 컴포넌트 트리에 Relay Environment 주입
import { ReactNode } from "react";
import { RelayEnvironmentProvider as BaseProvider } from "react-relay/hooks";
import { relayEnvironment } from "./relayEnvironment";
export function RelayEnvironmentProvider({ children }: { children: ReactNode }) {
return (
<BaseProvider environment={relayEnvironment}>
{children}
</BaseProvider>
);
}
App을 RelayEnvironmentProvider로 감싸면 내부에서 일어나는 모든 graphql query 요청을 연결해 둔 axiosInstanceGraphql을 통해 하게 됨.
<RelayEnvironmentProvider>
<App />
</RelayEnvironmentProvider>
graphql 태그를 컴파일 타임에 처리하여 Relay artifact 파일로 변환함.
Vite에서는 vite-plugin-relay가 내부적으로 babel-plugin-relay를 포함하고 있음.
큰 틀에서 보면 Swagger api를 orval 로 생성하는 것과 비슷.
schema.graph 파일로 변환 (grpahql-codegen)replay-compiler 를 실행해서 리액트 코드 내부에 적혀있는 fragment 코드들을 보고 자동으로 타입 생성. ./__generated__/ 하위에 생성됨.curl -X POST $ENDPOINT \
-H "Content-Type: application/json" \
--data '{"query":"query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } }"}' \
--insecure \
-o $INTROSPECTION_JSON
npx graphql-codegen --config codegen.config.ts
relay-compiler
GPT는 아래처럼 하라는데 이렇게 하면 또 에러남. GPT야 거짓말좀 그만..
// package.json
{
"scripts": {
"relay": "relay-compiler --src ./src --schema ./schema.graphql"
}
}
yarn relay
__generated__/MyComponentQuery.graphql.ts 형태로 생성됨usePreloadedQuery 또는 useFragment로 해당 쿼리를 사용함fetchGraphQL()에서 axios 등을 통해 수행함Relay와 함께 Apollo Client를 사용할 수도 있음
import { ApolloClient, InMemoryCache } from "@apollo/client";
export const apolloClient = new ApolloClient({
uri: "/graphql",
cache: new InMemoryCache(),
});
<ApolloProvider client={apolloClient}>
<RelayEnvironmentProvider>
<App />
</RelayEnvironmentProvider>
</ApolloProvider>
fragment는 GraphQL 쿼리 결과 중 필요한 필드만 분리해서 재사용하는 단위// CategoryItem.tsx
import { graphql, useFragment } from "react-relay";
import type { CategoryItem_category$key } from "./__generated__/CategoryItem_category.graphql";
const fragment = graphql`
fragment CategoryItem_category on GraphQLBaseCategoryDto {
id
name
}
`;
export const CategoryItem = (props: { categoryRef: CategoryItem_category$key }) => {
const data = useFragment(fragment, props.categoryRef);
return <div>{data.name}</div>;
};
// SettingsCategory.tsx
const { categories } = useLazyLoadQuery<SettingsCategoryQuery>(
graphql`
query SettingsCategoryQuery {
categories {
id
...CategoryItem_category
}
}
`,
{}
);
return categories.map((category) => (
<CategoryItem key={category.id} categoryRef={category} />
));
useFragment로 받아서 중간에서 소비 가능// CategoryGroup.tsx (중간부모)
import { graphql, useFragment } from "react-relay";
import type { CategoryGroup_category$key } from "./__generated__/CategoryGroup_category.graphql";
const fragment = graphql`
fragment CategoryGroup_category on GraphQLBaseCategoryDto {
id
name
...CategoryItem_category
}
`;
export const CategoryGroup = ({ categoryRef }: { categoryRef: CategoryGroup_category$key }) => {
const data = useFragment(fragment, categoryRef);
return (
<div>
<h3>{data.name}</h3>
<CategoryItem categoryRef={data} />
</div>
);
};
// SettingsCategory.tsx
return categories.map((category) => (
<CategoryGroup key={category.id} categoryRef={category} />
));
__generated__/FragmentName.graphql.ts 안에 타입이 정의됨CategoryItem_category$keyCategoryItem_category$data// 예시
export type CategoryItem_category$key = {
readonly " $data"?: CategoryItem_category$data;
readonly " $fragmentSpreads": FragmentRefs<"CategoryItem_category">;
};
graphql`` 태그는 .tsx` 안에 선언만 하면 컴파일 가능useLazyLoadQuery 등에서 최상단 페이지에서 사용핵복잡하네... 1주일만에 겨우성공.