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)
でプラグインを読み込む際に以下の設定をすると幸せになれます。
module.exports = {
plugins: [
// ...中略
{
resolve: 'gatsby-plugin-graphql-codegen',
options: {
codegenConfig: { maybeValue: 'T | undefined' }, // これを追加!
},
},
]
}
どういうこと?
gatsby-plugin-graphql-codegen
は、GraphQL クエリから型生成する際、全ての戻り値を以下のMaybe
という型でラップします。
export type Maybe<T> = T | null;
GraphQL から取得するデータはnull
になる可能性があるので、これは妥当な型定義ではあります。
TypeScript では Optional Chaining?.
を利用することで、このような Nullable な値に対しても安全に値を取得できることができます。
例
Gatsby のデフォルトスターター gatsby-starter-default を例にとると、以下のように GraphQL クエリで画像を読み込んでいるコンポーネントがあります。
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 化すると以下のように書くことができます。
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 Image
)ImageQuery
という名前で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' に割り当てることはできません。
要点を抜き出すと「null
をundefined
に割り当てることはできません」と言っています。
gatsby-image
コンポーネントのImg
はundefined
を受け付けるように型定義されているのですが、null
は受け取ってくれないようです。
先程述べた通りgatsby-plugin-graphql-codegen
は全ての型をMaybe<T> = T | null
でラップするので、data.placeholderImage?.childImageSharp?.fluid
はnull
になりうると判断されてしまいます。
これは、以下のようにnull
だったらundefined
になるように書けば回避できます。
const Image: React.FC = () => {
// ...略
return <Img fluid={data.placeholderImage?.childImageSharp?.fluid ?? undefined} />
}
export default Image
ただ正直 GraphQL から取得したデータ全てに対してこれを行うのは結構大変です。特に React コンポーネントの Optional なprops
の定義はT | undefined
であることが多いので、結構な頻度でnull -> undefined
の変換が発生してきます。
Maybe の型を変える
そこで冒頭の結論に戻りますが、プラグイン読み込み時に以下の設定を記載します。
module.exports = {
plugins: [
// ...中略
{
resolve: 'gatsby-plugin-graphql-codegen',
options: {
codegenConfig: { maybeValue: 'T | undefined' }, // これを追加!
},
},
]
}
codegenConfig.maybeValue
で、生成されるMaybe
の定義をオーバーライドできます。
T | null
の代わりにT | undefined
とすることで、Nullable な値がundefined
に統一され、シンプルな Optional Chaining だけで書けるようになります。
const Image: React.FC = () => {
// ...略
return <Img fluid={data.placeholderImage?.childImageSharp?.fluid} />
}
export default Image
型定義を変えて大丈夫なの?
null
を勝手にundefined
と扱ってしまって大丈夫なのかと思うかもしれませんが、基本的に問題はないと思っています。
型定義を変えるだけなので、少なくともトランスパイル後の JavaScript には影響はありません。
最近のライブラリならnull
とundefined
の違いで大きな問題が起こることはないと思っていますが、全てを確認したわけではないです。
ダメだった場合はごめんなさい。
まとめ
-
gatsby-plugin-graphql-codegen
の生成するnull
に悩まされている方はcodegenConfig: { maybeValue: 'T | undefined' }
を設定すると快適になるかもしれません
-
"strict": true
にしている等 ↩