8
4

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 3 years have passed since last update.

Gatsby+TypeScriptを快適にするためのgatsby-plugin-graphql-codegenの設定

Posted at

Gatsby + TypeScript の構成で GraphQL の型定義を自動生成するための gatsby-plugin-graphql-codegen というライブラリがあります。
これを使用すると GraphQL から取得したデータに自動で型が付与されてかなり快適に開発できるようになるのですが、一部困ったことが出てきたので、概要と解決策を記事にまとめます。

ちなみに Gatsby + TypeScript の環境を構築するにはこちらの記事が非常に参考になります。
Gatsby.js を完全TypeScript化する - Qiita

環境

  • TypeScript 3.9.7
  • React 16.13.1
  • Gatsby 2.4.13
  • gatsby-plugin-graphql-codegen 2.7.1

結論

結論から言うと、gatsby-config.js(.ts)でプラグインを読み込む際に以下の設定をすると幸せになれます。

gatsby-config.js
module.exports = {
  plugins: [
    // ...中略
    {
      resolve: 'gatsby-plugin-graphql-codegen',
      options: {
        codegenConfig: { maybeValue: 'T | undefined' }, // これを追加!
      },
    },
  ]
}

どういうこと?

gatsby-plugin-graphql-codegenは、GraphQL クエリから型生成する際、全ての戻り値を以下のMaybeという型でラップします。

graphql-types.ts
export type Maybe<T> = T | null;

GraphQL から取得するデータはnullになる可能性があるので、これは妥当な型定義ではあります。
TypeScript では Optional Chaining?.を利用することで、このような Nullable な値に対しても安全に値を取得できることができます。

Gatsby のデフォルトスターター gatsby-starter-default を例にとると、以下のように GraphQL クエリで画像を読み込んでいるコンポーネントがあります。

src/components/image.js
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"

const Image = () => {
  const data = useStaticQuery(graphql`
    query {
      placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {
        childImageSharp {
          fluid(maxWidth: 300) {
            ...GatsbyImageSharpFluid
          }
        }
      }
    }
  `)

  return <Img fluid={data.placeholderImage.childImageSharp.fluid} />
}

export default Image

これを TypeScript 化すると以下のように書くことができます。

src/components/image.tsx
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"
import { ImageQuery } from "../../graphql-types"

const Image: React.FC = () => {
  const data = useStaticQuery<ImageQuery>(graphql`
    query Image {
      placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {
        childImageSharp {
          fluid(maxWidth: 300) {
            ...GatsbyImageSharpFluid
          }
        }
      }
    }
  `)

  return <Img fluid={data.placeholderImage?.childImageSharp?.fluid} />
}

export default Image

gatsby-plugin-graphql-codegenは、queryに名前を付けると(上の場合query ImageImageQueryという名前でgraphql-types.tsに型定義を自動生成してくれます。
それをuseStaticQueryに型指定してあげることでdataが型補完されます。
Imgコンポーネントに対しては Optional Chainging を使用して値を渡すことで、画像が取得できなかった場合でも実行時エラーにならずに処理してくれます。

起きたこと

しかしここでtsconfig.jsonの指定によっては1以下のようなコンパイルエラーが発生します。

この呼び出しに一致するオーバーロードはありません。
  2 中 1 のオーバーロード, '(props: Readonly<GatsbyImageProps>): GatsbyImage' により、次のエラーが発生しました。
    型 'Pick<ImageSharpFluid, "base64" | "aspectRatio" | "src" | "srcSet" | "sizes"> | null | undefined' を
型 'FluidObject | FluidObject[] | undefined' に割り当てることはできません。
      型 'null' を型 'FluidObject | FluidObject[] | undefined' に割り当てることはできません。
  2 中 2 のオーバーロード, '(props: GatsbyImageProps, context?: any): GatsbyImage' により、次のエラーが発生しました。
    型 'Pick<ImageSharpFluid, "base64" | "aspectRatio" | "src" | "srcSet" | "sizes"> | null | undefined' を
型 'FluidObject | FluidObject[] | undefined' に割り当てることはできません。
      型 'null' を型 'FluidObject | FluidObject[] | undefined' に割り当てることはできません。

要点を抜き出すと「nullundefinedに割り当てることはできません」と言っています。
gatsby-imageコンポーネントのImgundefinedを受け付けるように型定義されているのですが、nullは受け取ってくれないようです。
先程述べた通りgatsby-plugin-graphql-codegenは全ての型をMaybe<T> = T | nullでラップするので、data.placeholderImage?.childImageSharp?.fluidnullになりうると判断されてしまいます。
これは、以下のようにnullだったらundefinedになるように書けば回避できます。

src/components/image.tsx
const Image: React.FC = () => {
  // ...略
  return <Img fluid={data.placeholderImage?.childImageSharp?.fluid ?? undefined} />
}

export default Image

ただ正直 GraphQL から取得したデータ全てに対してこれを行うのは結構大変です。特に React コンポーネントの Optional なpropsの定義はT | undefinedであることが多いので、結構な頻度でnull -> undefinedの変換が発生してきます。

Maybe の型を変える

そこで冒頭の結論に戻りますが、プラグイン読み込み時に以下の設定を記載します。

gatsby-config.js
module.exports = {
  plugins: [
    // ...中略
    {
      resolve: 'gatsby-plugin-graphql-codegen',
      options: {
        codegenConfig: { maybeValue: 'T | undefined' }, // これを追加!
      },
    },
  ]
}

codegenConfig.maybeValueで、生成されるMaybeの定義をオーバーライドできます。
T | nullの代わりにT | undefinedとすることで、Nullable な値がundefinedに統一され、シンプルな Optional Chaining だけで書けるようになります。

src/components/image.tsx
const Image: React.FC = () => {
  // ...略
  return <Img fluid={data.placeholderImage?.childImageSharp?.fluid} />
}

export default Image

型定義を変えて大丈夫なの?

nullを勝手にundefinedと扱ってしまって大丈夫なのかと思うかもしれませんが、基本的に問題はないと思っています。
型定義を変えるだけなので、少なくともトランスパイル後の JavaScript には影響はありません。
最近のライブラリならnullundefinedの違いで大きな問題が起こることはないと思っていますが、全てを確認したわけではないです。
ダメだった場合はごめんなさい。

まとめ

  • gatsby-plugin-graphql-codegenの生成するnullに悩まされている方はcodegenConfig: { maybeValue: 'T | undefined' }を設定すると快適になるかもしれません
  1. "strict": trueにしている等

8
4
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
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?