Relayのチュートリアルをやってみた

# はじめに

https://relay.dev/

現在触っているシステムで、`Relay`を使用しているため、まず理解するために、チュートリアルを行いました。
そこで学んだ内容を、ここにアウトプットいたします。

:::note info
`バージョン18.0.0`のDocsを参照しています。
:::

:::note alert
GraphQLの解説は、行いません。
:::

# Relayとは

一言でいうと、GraphQLを使ったデータ取得・キャッシュ・同期を最適化し、UIの一貫性を保つためのライブラリです。

https://relay.dev/docs/

そのため、GraphQLとRelayはセットで使用します。

# メリット

`Relay`の最大のメリットは、**各コンポーネントが独自のデータ要件をローカルで宣言し、それらの要件をより大きなクエリにまとめること**ができます。


![スクリーンショット 2025-01-04 15.37.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/3ab23704-9d35-b4b1-ee20-6fb9c3673880.png)

（※ ↑ 公式ドキュメント参照)

各コンポーネントで、Fragmentを用意して、それを大元のクエリで結合させます。
そうすることで、修正が発生した際に、各コンポーネントごとで修正ができます。

# デメリット

チュートリアルをやってみましたが、学習コストは高いなと思いました。
直感的に分かる。というより、`Relay`独自の概念があるので、そこはしっかり抑える必要があるなと思います。


# 仕組み

![スクリーンショット 2025-01-04 15.43.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/a8650d0f-a249-f591-3ffc-7bd618487bae.png)

（※ ↑ 公式ドキュメント参照)

`Relay runtime`がコアエンジンとなり、サーバーに向けて、クエリを実行してくれます。
この図から分かるように、各コンポーネントで`Fragment`を大元のクエリに結合させます。

# 実装例

```js
import { graphql } from 'relay-runtime';
import { useLazyLoadQuery } from "react-relay";
import type {NewsfeedQuery as NewsfeedQueryType} from './__generated__/NewsfeedQuery.graphql';

export default function Newsfeed({}) {
  const data = useLazyLoadQuery<NewsfeedQueryType>(NewsfeedQuery, {});

  const stories = data.topStories;

  return (
    <div className="newsfeed">
      {stories.map(story =>
        <Story key={story.id} story={story} />)}
    </div>
  );
}

const NewsfeedQuery = graphql`
  query NewsfeedQuery {
    topStories {
      id
      ...StoryFragment
    }
  }
`;
```

（※ ↑ 公式ドキュメント参照)

`NewsfeedQuery`で、`graphql`でマークされた文字列リテラルを用意します。
これを用意すると、`Relay`がコンパイルしてくれます。

`query NewsfeedQuery { `となっているので、これが大元のクエリとなっています。
これを、`useLazyLoadQuery`でクエリを実行してくれます。

また、`...StoryFragment`が`Fragment`になって、各コンポーネントで定義をしていきます。

```js
import { graphql } from 'relay-runtime';
import { useFragment } from 'react-relay';
import type {StoryFragment$key} from './__generated__/StoryFragment.graphql';
import StoryCommentsSection from './StoryCommentsSection';

type Props = {
  story: StoryFragment$key;
};

export default function Story({story}: Props) {
  const data = useFragment(
    StoryFragment,
    story,
  );
  return (
    <Card>
      <Heading>{data.title}</Heading>
      <PosterByline poster={data.poster} />
      <Timestamp time={data.createdAt} />
      <Image image={data.thumbnail} width={400} height={400} />
      <StorySummary summary={data.summary} />
      <StoryCommentsSection story={data} />
    </Card>
  );
}

const StoryFragment = graphql`
  fragment StoryFragment on Story {
    title
    summary
    createdAt
    poster {
      ...PosterBylineFragment
    }
    thumbnail {
      ...ImageFragment @arguments(width: 400)
    }
    ...StoryCommentsSectionFragment
  }
`;
```

（※ ↑ 公式ドキュメント参照)

上記が、子コンポーネントになります。
`fragment StoryFragment on Story`で、`Fragment`で定義しています。
それを、`useFragment`で実行いたします。

もしFragmentに修正が入れば、コンポーネントごとに修正できるので、わざわざ大元のクエリを変更して...みたいな手間はありません。

図にすると、以下のイメージです。

![スクリーンショット 2025-01-04 16.09.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849898/a2421e1e-20cd-e933-0204-fe7f43c4af4a.png)

（※ ↑ 公式ドキュメント参照)

## コンパイルの方法
Relayは変更すると、必ずコンパイルをし直す必要があります。
これを忘れてしまうと、エラーが出続けてしまいます。

```json
"scripts": {
    "relay": "relay-compiler",
    ...
```

`package.json`に上記のように記載すると、`npm run relay`でコンパイルを実行してくれます。

```sh
newsfeed % npm run relay

> newsfeed@0.0.0 relay
> relay-compiler

[INFO] [default] compiling...
[INFO] [default] compiled documents: 17 reader, 5 normalization, 18 operation text
[INFO] Done.
```


# まとめ
上記までで、`Relay`を使用して、データ取得するまでの基本は抑えられたかと思います。
まだこの他にも、学ぶべき箇所はあるかと思います。

- ページネーション
- データの再取得 ...etc

メジャーバージョンによっても、実装方法は異なると思うので、扱っているシステムでどのような実装をしているか、確認しようと思います。

:::note warn
ドキュメントは、全て英語になっています。
チュートリアル等は、翻訳をうまく使いながら、挑戦してみてください。
:::

最後までご覧いただき、ありがとうございました。