hooksの型推論が効かなかったのはTypeScriptのas constがわかっていなかったから

久しぶりにReactを触っているのですが、hooksを自作したら分割代入したときにTypeScriptの型推論が効かなかったマンです。

hooksの型推論が思うように効かない

下のようにAPIを呼び出して、結果を使うhooksを作りました。

import React, { useState, useEffect } from 'react';
import yelp from '../api/yelp';
import { SearchResult } from '../types';

export default () => {
  const [results, setResults] = useState<SearchResult[]>([]);
  const [errorMessage, setErrorMessage] = useState<string>('');

  const searchApi = async (term: string) => {
    // 省略:検索の非同期処理
  };

  useEffect(() => {
    searchApi('pasta');
  }, []);

  return [searchApi, results, errorMessage] as const;
};

使う側でいい感じに型を推論してほしかったのですが、分割代入すると思うような結果にならなかったです。。

f:id:kenev:20210504110353p:plain

hooksの作り方が悪いのかなと思ったら普通にTypeScriptのことをわかっていないだけでした。

TypeScriptにおける配列の型推論

下のようなコードをTypeScriptで書いてみました。配列を作って、その配列から分割代入してます。

const ary = [1, 'hoge', true, () => 'moge', {a: 'a', b: 'b'}];

const [one, hoge, trueValue, mogeFunc, obj] = ary;

普通に型推論が効くかと思いきや、残念な結果に。。。

f:id:kenev:20210504105420p:plain

oneは 1 になってほしいところですが、以下のように「いずれかの型」という推論しかされません。

const one: string | number | boolean | (() => string) | {
    a: string;
    b: string;
}

当然、この状態では加算だってできません。。。

f:id:kenev:20210504105606p:plain

以下にplaygroundを用意したので気になる人は試してみてください。

TypeScript: TS Playground - An online editor for exploring TypeScript and JavaScript

const assertions

この現象はTypeScript3.4に追加された「const assertions」についての理解が乏しかったせいでした。配列やオブジェクトなどに as const をつけることでコンパイラに「中身は変わりませんよ」というのを教えてあげる必要があるのです。

ということで aryas const をつけてあげることで、型推論がちゃんと効くようになります!

const ary = [1, 'hoge', true, () => 'moge', {a: 'a', b: 'b'}] as const; // ここに追加

const [one, hoge, trueValue, mogeFunc, obj] = ary;

f:id:kenev:20210504111429g:plain

hooksに関しても原因は一緒

冒頭のhooksに関しても、 return するものに as const をつけてあげることでちゃんと型推論が効きます。

return [searchApi, results, errorMessage] as const; // ここに追加

これでhooksの使う側も型推論が効いて心穏やかにコーディングできます。

f:id:kenev:20210504111944g:plain