FE에서 `GraphQL` 및 `Relay Fragment` 사용법

ddoachi·2025년 6월 28일

TekaPicker

목록 보기
27/30

git: 2dc9da156e2f18b7a6a5e4c9636279faa9e00409

환경설정하기


1. 개요

Relay를 프론트엔드에서 사용하기 위해서는 다음 작업들이 필요함.

  • 필요한 패키지 설치
  • GraphQL 클라이언트 환경 구성 (Apollo 또는 Relay)
  • relay-compilerbabel-plugin-relay의 역할 이해
  • vite.config.ts에서 Babel 및 Plugin 설정
  • 실제 요청을 처리하는 fetchGraphQL.ts, relayEnvironment.ts 파일 작성
  • GraphQL 쿼리 artifact 생성

2. 필수 패키지 설치

yarn add react-relay relay-runtime graphql
yarn add -D relay-compiler vite-plugin-relay

3. Vite 설정 (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 플러그인 사용법은 다음 두 곳을 참조하면 됨. 위의 코드는 두번째 방법을 사용한 것.


4. Relay 환경 구성

4.1 fetchGraphQL.ts

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;
}

4.2 relayEnvironment.ts

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()),
});

4.3 RelayEnvironmentProvider.tsx

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>
  );
}

5. App.tsx에서 Provider 구성

App을 RelayEnvironmentProvider로 감싸면 내부에서 일어나는 모든 graphql query 요청을 연결해 둔 axiosInstanceGraphql을 통해 하게 됨.

<RelayEnvironmentProvider>
  <App />
</RelayEnvironmentProvider>

6. GraphQL 쿼리 및 컴파일

6.1 babel-plugin-relay

graphql 태그를 컴파일 타임에 처리하여 Relay artifact 파일로 변환함.
Vite에서는 vite-plugin-relay가 내부적으로 babel-plugin-relay를 포함하고 있음.

6.2 동작 순서

큰 틀에서 보면 Swagger api를 orval 로 생성하는 것과 비슷.

  • 먼저 백엔드에 정의된 schema를 가져와서 json으로 저장 후
  • 이걸 schema.graph 파일로 변환 (grpahql-codegen)
  • 그리고 최종적으로 replay-compiler 를 실행해서 리액트 코드 내부에 적혀있는 fragment 코드들을 보고 자동으로 타입 생성.
    • 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

7. 내부 동작 원리

  • `graphql`` 태그는 런타임 함수가 아니라, 컴파일 타임에 babel-plugin-relay가 처리함
  • 처리 결과는 __generated__/MyComponentQuery.graphql.ts 형태로 생성됨
  • React 컴포넌트에서 usePreloadedQuery 또는 useFragment로 해당 쿼리를 사용함
  • 실제 요청은 fetchGraphQL()에서 axios 등을 통해 수행함

8. Apollo Client 병행 사용 시

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>

코드에서는 어떻게 사용하나?


1. 기본 개념

  • Relay의 fragment는 GraphQL 쿼리 결과 중 필요한 필드만 분리해서 재사용하는 단위
  • 컴포넌트 단위에서 필요한 데이터만 선언하고 사용할 수 있게 해줌

2. 부모 - 자식 구조

예시: CategoryItem이 개별 필드를 쓸 때

// 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} />
));

3. 부모 - 중간 부모 - 자식 구조

  • 중간 부모도 데이터를 사용하는 경우 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} />
));

4. 관련 타입

  • 각 fragment는 자동 생성된 __generated__/FragmentName.graphql.ts 안에 타입이 정의됨
  • fragment 키 타입: CategoryItem_category$key
  • fragment 데이터 타입: CategoryItem_category$data
// 예시
export type CategoryItem_category$key = {
  readonly " $data"?: CategoryItem_category$data;
  readonly " $fragmentSpreads": FragmentRefs<"CategoryItem_category">;
};

5. Tip: GraphQL 태그 위치와 순서

  • graphql`` 태그는 .tsx` 안에 선언만 하면 컴파일 가능
  • 해당 fragment를 사용하는 컴포넌트 내부에서 선언하는 것이 좋음
  • 쿼리는 useLazyLoadQuery 등에서 최상단 페이지에서 사용

느낀점

핵복잡하네... 1주일만에 겨우성공.

profile
내일도 풀스택

0개의 댓글