Step-by-step Guide

Step 1. Create React App

# NPM
npx create-react-app your-app-name

# Yarn
yarn create react-app your-app-name
# NPM
cd your-app-name
npm start

# Yarn
cd your-app-name
yarn start

Step 2. Fetch GraphQL (Relay 없이 fetch() 사용)

2.1. GitHub GraphQL Authentication

# your-app-name/.env.local
REACT_APP_GITHUB_AUTH_TOKEN=<TOKEN>

2.2. A fetchGraphQL Helper

// your-app-name/src/fetchGraphQL.js
async function fetchGraphQL(text, variables) {
	...

  // Fetch data from GitHub's GraphQL API:
  const response = await fetch('https://api.github.com/graphql', {
    method: 'POST',
    ...,
    body: JSON.stringify({
      query: text,
      variables,
    }),
  });

  // Get the response as JSON
  return await response.json();
}

export default fetchGraphQL;

2.3. Fetching GraphQL From React

// your-app-name/src/App.js
...

function App() {
  // We'll load the name of a repository, initially setting it to null
  const [name, setName] = useState(null);

  // When the component mounts we'll fetch a repository name
  useEffect(() => {
    let isMounted = true;
    fetchGraphQL(`
      query RepositoryNameQuery {
        # feel free to change owner/name here
        repository(owner: "facebook" name: "relay") {
          name
        }
      }
    `).then(response => {
      // Avoid updating state if the component unmounted before the fetch completes
			// 데이터 패칭이 완료되기 전 unMount 되는 경우 처리
      if (!isMounted) {
        return;
      }
      const data = response.data;
      setName(data.repository.name);
    }).catch(error => {
      console.error(error);
    });

    return () => {
      isMounted = false;
    };
  }, [fetchGraphQL]);

  // Render "Loading" until the query completes
  return (
    <div className="App">
      <header className="App-header">
        <p>
          {name != null ? `Repository: ${name}` : "Loading"}
        </p>
      </header>
    </div>
  );
}

export default App;

Step 3. When To Use Relay (Relay 사용해 데이터 가져오기)

step2를 사용하면, 어플리케이션의 규모가 커지는 속도와 크기에 대응하기 어려워진다.

이 때 Relay를 사용하면 빠르고 믿을 수 있는 방식으로 이에 대응할 수 있는데, 이유는 다음과 같다.

  1. GraphQL 프래그먼트, 데이터 일관성, mutations을 컴포넌트에 위치시켜 데이터 의존성을 모아놓기 (colocating)

Step 4. Adding Relay To Our Project

Relay는 3가지 핵심으로 이루어져있다.

  1. relay-compiler : 컴파일러 (빌드 타임에 사용됨)

  2. relay-runtime : 코어 런타임 (React 친화적)

  3. react-relay : React integration layer

# NPM Users
npm install --save relay-runtime react-relay
npm install --save-dev relay-compiler babel-plugin-relay

# Yarn Users
yarn add relay-runtime react-relay
yarn add --dev relay-compiler babel-plugin-relay

4.1. Configure Relay Compiler

Relay example app의 .graphql 스키마 카피를 다운로드 받기 위해

cd your-app-name
curl https://raw.githubusercontent.com/relayjs/relay-examples/main/issue-tracker/schema/schema.graphql > schema.graphql

package.json 설정은 다음과 같다.

// your-app-name/package.json
{
  ...
  "scripts": {
    ...
    "start": "yarn run relay && react-scripts start",
    "build": "yarn run relay && react-scripts build",
    "relay": "yarn run relay-compiler $@"
    ...
  },
  "relay": {
    "src": "./src/",
    "schema": "./schema.graphql"
  }
  ...
}
cd your-app-name
yarn start

여기서, GraphQL을 사용하게 되면 Relay는 이를 감지하여 해당 프로젝트에서 작성한 쿼리를 나타내는 코드를 your-app-name/src/__generated__/ 에 생성한다.

4.2. Configure Relay Runtime

컴파일러 설정이 완료되었으니 런타임을 세팅할 수 있는데, 이는 Relay에게 우리의 GraphQL 서버와 어떻게 연결할 것인지에 대해 알려주는 것과 같다.

위의 코드를 동일하게 사용하되, 추가적으로 Relay Environment 를 정의한다. 이는 서버 (Relay Network) 에 저장된 캐시를 어떻게 활용할 것인지에 대해 캡슐화하는 것을 의미한다.

// your-app-name/src/RelayEnvironment.js
import {Environment, Network, RecordSource, Store} from 'relay-runtime';
import fetchGraphQL from './fetchGraphQL';

// Relay passes a "params" object with the query name and text. So we define a helper function
// to call our fetchGraphQL utility with params.text.
async function fetchRelay(params, variables) {
  console.log(`fetching query ${params.name} with ${JSON.stringify(variables)}`);
  return fetchGraphQL(params.text, variables);
}

// Export a singleton instance of Relay Environment configured with our network function:
export default new Environment({
  network: Network.create(fetchRelay),
  store: new Store(new RecordSource()),
});

Step 5. Fetching a Query With Relay

...
import fetchGraphQL from './fetchGraphQL';
import graphql from 'babel-plugin-relay/macro';
import {
  RelayEnvironmentProvider,
  loadQuery,
  usePreloadedQuery,
} from 'react-relay/hooks';
import RelayEnvironment from './RelayEnvironment';

const { Suspense } = React;

// Define a query
const RepositoryNameQuery = graphql`
  query AppRepositoryNameQuery {
    repository(owner: "facebook", name: "relay") {
      name
    }
  }
`;

// 앱이 시작하자마자 쿼리를 즉시 로드한다.
// 실제 앱에서는 라우팅 configuration에 이를 설정하고, 새로운 route로 이동 시 데이터를 프리-로드한다.
const preloadedQuery = loadQuery(RelayEnvironment, RepositoryNameQuery, {
  /* query variables */
});

// 프리로드된 쿼리를 읽는 이너 컴포넌트는 `usePreloadedQuery`를 사용한다.
// - 쿼리가 실행 완료되면, 쿼리의 결과를 리턴한다.
// - 쿼리가 아직 펜딩 중이면, Suspend 한다. 이는 부모 컴포넌트 중 가장 가까이 위치한 fallback에 근거한다.
// - 쿼리가 (데이터 패칭을) 실패하면, 실패 에러를 띄운다.
function App(props) {
  const data = usePreloadedQuery(RepositoryNameQuery, props.preloadedQuery);

  return (
    <div className="App">
      <header className="App-header">
        <p>{data.repository.name}</p>
      </header>
    </div>
  );
}

// 위 App 컴포넌트는 어떻게 Relay Environment에 접근할지에 대한 정보가 있어야 하고,
// Suspend 경우를 위한 fallback을 설정해야 한다.
function AppRoot(props) {
  return (
    <RelayEnvironmentProvider environment={RelayEnvironment}>
      <Suspense fallback={'Loading...'}>
        <App preloadedQuery={preloadedQuery} />
      </Suspense>
    </RelayEnvironmentProvider>
  );
}

export default AppRoot;
  1. RepositoryNameQuery

    • 쿼리를 정의한다.

  2. preloadQuery

    • 사전에 정의한 RelayEnvironment와 RepositoryNameQuery, 쿼리 변수(args)를 전달한다.

  3. AppRoot

    • <RelayEnvironmentProvider> 은 현재 Relay Environment 인스턴스와의 소통 방식을 child 컴포넌트에 전달한다.

    • <Suspense> 는 child가 suspend할 경우의 fallback을 지정한다.

Last updated