6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

未来の構成を先取りする ~ React サーバーコンポーネント × GraphQL ~

Posted at

GraphQLアドベントカレンダー24日目の投稿です!

:santa: メリークリスマス! :christmas_tree:

と心の中で思いつつ、記事を書いたので見てください

はじめに

Next.js Conf 2021React Conf 2021 皆さんはご覧になりましたか?

とてもワクワクする未来の話が多かったように思います。
とりわけ、僕がワクワクしたのはサーバーコンポーネントです。

コンポーネントごとに、サーバー側でデータフェッチを行い、サーバー側でレンダリングを行う。
端末に依存されず、高速にレスポンスを返すことができる世界線を少し早く体験したいなーと思い、今回のテーマを選びました。

この記事で何をするか

サーバーコンポーネントからGraphQLでデータをフェッチし、レンダリングをします。
GraphQLサーバーを立てるのは面倒だったので、GithubのGraphQLを使わせてもらいます

下準備

2021年12月24日現在、Next.jsにおいてサーバーコンポーネントはAlphaバージョンのため、少し下準備が必要です
あと、TypeScriptだとうまく動かないとどこかで聞いたので、jsでやります。

1. Next.jsアプリケーションの作成

yarn create next-app

2. reactのバージョンをrcへ変更

yarn add react@rc react-dom@rc

3. 実験モードをONにする

next.config.js
module.exports = {
 ...
  experimental: {
    concurrentFeatures: true,
    serverComponents: true,
  },
}

concurrentFeatures
レンダリングをサスペンスしたコンポーネントに対し、
Promiseをthrowすることで通信が終わったことを伝える機能です。(後で使います)

実装1: サーバーコンポーネントでGraphQLがフェッチできるようにする

GithubからGraphQLでフェッチできるようにする関数を作っておきます
GraphQLは、実際はただのPOSTなので、そんなに難しくなくできます。

libs/fetchGraphQLFromGithub.js
export const fetchGraphQLFromGithub = async (query, variables) => {
  return (
    await fetch('https://api.github.com/graphql', {
      method: 'POST',
      headers: {
        Authorization: `bearer ${process.env.GRAPHQL_API_TOKEN}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        query,
        variables,
      }),
    })
  ).json()
}

裏話
本当はReact Relayを用いてやれないかなぁと画策していたのですが、
エラーのオンパレードでできなかったので、おとなしくサーバーコンポーネント対応済みのfetchを使いました

実装2. Pageコンポーネントをサーバーコンポーネントに変更

サーバー側でレンダリングするコンポーネントは*.server.jsという形にする必要があります
IndexPageをこの拡張子に変更し、同じくサーバーコンポーネントであるViewerコンポーネントを呼び出します

pages/index.server.js
import Viewer from '../components/viewer.server'

export default function Index() {
  return (
    <div>
      <h1>Server Components with GraphQL Example</h1>
      <Viewer />
    </div>
  )
}

実装3. Viewerコンポーネントを作成

Viewerコンポーネントでは、GraphQLの通信を行い、コンポーネントをレンダリングします。

components/viewer.server.js
import { Suspense } from 'react'
import { useData } from '../libs/use-data'
import { fetchGraphQLFromGithub } from '../libs/fetch-from-github'

export default function Viewer() {
  const query = `
    query {
      viewer {
        login
      }
    }
  `
  const data = useData('viewer', () => fetchGraphQLFromGithub(query, {}))
  return <Suspense fallback={'Loading...'}>{JSON.stringify(data)}</Suspense>
}

useDataでは、Suspenseにデータフェッチが終わったことを伝えるため、promiseをthrowするようにしています。

libs/use-data.js
const cache = {}

export const useData = (key, fetcher) => {
  if (!cache[key]) {
    let data
    let promise
    cache[key] = () => {
      if (data !== undefined) return data
      if (!promise) promise = fetcher().then((r) => (data = r))
      throw promise
    }
  }
  return cache[key]()
}

エラー以外をthrowするというのが気持ち悪いような気がしたのですが、
非同期処理を隠蔽化するためにpromiseをthrowし、Suspenseでそれを解釈するというのはいかにもReactっぽいなぁと思いました

まとめ

サーバーコンポーネント と GraphQLを用いて未来を先取りしてみました。
サーバーコンポーネントはまだまだα状態なので、hooksも使えず、APIクライアントなども限られるため、かなり不自由な世界です。

ただ、コンポーネントごとにAPIリクエストを送り、クライアント側に大きな仕事をさせない、というのはかなり未来が明るいなぁと思いました。

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?