【TypeScript】型推論によるundefinedを退治したい

14502 ワード

TypeScript を使い始めたは良いものの、TypeScript による恩恵を享受できていない感が否めないのが正直なところであります。

したがって今回は、TypeScript のことをもう少し知る努力をして、TypeScript に私のことを好きになってもらうためのラブレターのような記事です。
ラブレターを一般公開する(そもそもラブレターは書いたことがない)という初の試みとしても良い経験になると思っています。

では早速本題へ。

型 undefined を型 A に当てはめることができません...><

今回は Next.js を用いた開発途中のアプリケーションのコードを使っての解説になります。

result.tsx
//tsxの内容

return (
    <div>
      {isLoading ? (
        <div>読み込み中</div>
      ) : (
        <Image src={srcData?.src} width={1200} height={630} alt={chara_name} />
      )}
    </div>
  )

上記の srcData?.src 部分では、

型 'string | undefined' を型 'string | StaticImport' に割り当てることはできません。
型 'undefined' を型 'string | StaticImport' に割り当てることはできません。

という風に怒られてしまっています。

以下のコードは、上記コードの少し上に書いたデータの処理をする部分です。

result.tsx

import exampleImg from "asset/images/resultImgs/exampleImg.png"

const Result = ({ chara_name }: PageProps) => {
  //chara_nameはgetServerSidePropsでサーバー側で受け取った値をpropsとして渡しています
  const [srcData, setSrcData] = useState<StaticImageData | undefined>(undefined)
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
    switch (chara_name) {
      case 'example_chara':
        setSrcData(exampleImg)
        break
      case 'none':
        alert('データの取得に失敗しました。最初からやり直してください')
    }
    if (srcData) {
      setIsLoading(false)
    }
  }, [srcData, chara_name])
}

そもそも、状態によって表示は状態の組み合わせの数だけ生成されます。
ここまでで「わからん」という方は、以下の記事が"表示と状態がセットである"ということの理解の手助けになると思うので読んでみてみてほしいです。
コンポーネントと状態はセットである

今回私は srcData と isLoading という 2 つの状態を useState で取り扱っています。従って、全ての状態のパターンを考慮すると 4 パターンあることになります。

  1. srcData:undefined,isLoading:true 場合
  2. srcData:undefined,isLoading:false 場合(※)
  3. srcData:StaticImageData,isLoading:true 場合
  4. srcData:StaticImageData,isLoading:false 場合

上記 2 の可能性も許してしまっているのが、今回の私の失態であります。
この問題は、TypeScript の型定義でうまく解決できるらしいです。
こちらの記事が最高でした。
TypeScript way で React を書く

型ファーストで戦法で型「undefined を型 A に当てはめることができません...><」を退治する

「TypeScript way で React を書く」という記事によると、型定義によって状態をうまく絞り込むことができるぞ!という内容が書かれていました。

それを踏まえて、型定義を見直して書いたものがこちら。

result.tsx
type ImgStateType = {
  State:
    | {
        srcData: StaticImageData
        isLoading: false
      }
    | { srcData: undefined; isLoading: true }
}

先ほどまでは 4 種類あった型のパターンを、srcData と isLoading の状態をセットにすることで大きく 2 種類にすることに成功しました。
これにより、isLoading が false の場合は、「srcData の型は StaticImageData という型しかないよね!だってそう定義してあるんだもの!」という風に TypeScript 側が理解してくれます。
これでようやく私の TypeScript へのラブレターが届きました。めでたし。

result.tsx
const Result = ({ chara_name }: PageProps) => {
	 const [srcStateData, setSrcData] = useState<ImgStateType>({
    State: { srcData: undefined, isLoading: true },
  })

useEffect(() => {
  switch (chara_name) {
    case 'example_chara':
      setSrcData({ State: { isLoading: false, srcData: exampleImg } })//ここで一気に二つの状態を指定できてしまう!
      break
    case 'none':
      alert('データの取得に失敗しました。最初からやり直してください')
      return
 }
  if(srcStateData.State.srcData){
//このif文も必要なくなりました
 }
}, [srcStateData.State.srcData])


return {
  <div>
   {srcStateData.State.isLoading ? (
    <div>
     読み込み中
    </div>
     ):(
<Image src={srcStateData.State.srcData.src} width={1200} height={630} alt={chara_name}/>
//「?」が消えました!
   )}
  </div>
  }

最後に

今回の件で TypeScript のことがより好きになれました。
私自身駆け出しなので、TypeScript 学ぶならこれがおすすめですよ!などご教授いただければ幸いです。