😑

型 '(string | undefined)[]' を型 'string[]' に割り当てることはできません。と出たので正攻法で解決したい

2022/06/03に公開3

なにをしている時に起きたのか

reactを使用中、配列に対して処理を行ったあとにstateに値をセットしようとした時に起きた。

エラーサンプル

以下のような場合、掲題通りのエラーが発生します。

const users = [
  {
    name: 'sample taro',
    age: 30
  },
  {
    name: 'sample jiro'
    age: 25
  },
  {
    name: 'example saburo',
    age: 30
  }
]

const NameListComponent = () => {
  const [names, setNames] = useState<string[]>([]);
  useEffect(() => {
    const _names = users.flatMap(user => {
      if (user.name.includes('sample')) {
        return user.name;
      }
    }).filter(Boolean);
    // ここで掲題のエラーが発生します。
    setNames(_names);
  },[users]);
  
  return <>{names.map(n => <p>{n}</p>)}</>
}

※ サンプルとして適当であるかや途中で使っている flatMap の使いかたなど趣旨と異なるコメント大歓迎です。

エラーを駆逐する

以降のコードではエラーサンプルのうちで、修正するコードとその周辺のみを記載します。

無理矢理解決する

useEffect(() => {
    const _names = users.flatMap(user => {
      if (user.name.includes('sample')) {
        return user.name;
      }
    }).filter(Boolean) as string[];
    setNames(_names);
  },[users]);

そもそもfilterメソッドで nullundefined を除外しているため、それ以外は存在しえないという論調で as ~(型) を使用して型を無理矢理当てる方法です。

ただ、この as ~ はなんでもかんでも型を注入?定義?できてしまうため、あまり望ましくありません。
filterで除外しているので今回は別にいい気もするんですが、でもこれじゃjs書いているのと一緒じゃんて感じです。
よって違う方法を使用します。

type guardを使う

useEffect(() => {
    const _names = users.flatMap(user => {
      if (user.name.includes('sample')) {
        return user.name;
      }
    }).filter((n): n is string => n !== undefined);
    setNames(_names);
  },[users]);

filterメソッドの返却がstringであることを、filter内で定義しています。
これはとても硬い気がしますが、なんだか冗長ですしもっといい方法がありそうな気もします。

flatMapの処理・返却内容を変える

useEffect(() => {
    const _names = users
      .flatMap(user => user.name.includes('sample') ? user.name : []);
    setNames(_names);
  },[users]);

この方がすっきりしていますし、あるべき姿のように思います。(同僚に教えてもらいました。)
flatMapで undefinedではなく何も返却しない場合 は、上記のように三項演算を使用し返却値を空の配列オブジェクトにすること実現できるようです。

返却値に空配列を使用すると Array in Array になりそうですが、どうやらflatMapは配列の値を2次元に変換して返却するものらしくこうしないといけないようです。
うまく説明ができないので勉強しておきます。

感想

tsはやり方が色々あって、どうあるべきなのかなど模索していいると無限に時間が溶けていきますね。
きっと処理速度(効率)などによってこの辺りは決定していくのが筋なんだろうと思いますが、迷いながらいろんな方法で書いてみるのも楽しいですね。

以上です。

Discussion

wintyowintyo

参考までに、僕はnull除去用のtype guardメソッドを用意しちゃってそれを通すようにしてますね。

const omitNullableHandler = <T>(item: T): item is NonNullable<T> => {
  return item != null;
};

  const _names = users.flatMap(user => {
    if (user.name.includes('sample')) {
      return user.name;
    }
  }).filter(omitNullableHandler);
  setNames(_names);
isseiissei

なるほど、恥ずかしながらNonNullable初めて知りました。めちゃくちゃ勝手がいいですね!
使わせていただきます!

Kaido IwamotoKaido Iwamoto
users.flatMap((u) => {
  if (cond) {
    return [u.name]
  }
  return []
})

こう書けば良いですよ。flatMapは、配列の要素を関数で置き換え(map)たあとに、配列の入れ子をフラット(flat)にする関数なので、空の配列を返すことでスキップするようなことができます。