😇

Typescriptの一部完全に理解してた。。(思ったことをそのまま書きます)

に公開

みなさんはTypescriptは大好きですか?

私は。。。😇

Typescriptは難しい言語だと思っています。
先日TSKaigiに参加をしてきましたが「まだまだ知らないことがたくさんあるな」「ものすごく勉強になった」というような内容がたくさんありました!

https://2025.tskaigi.org/

「カンファレンスは、、いいよなぁ〜〜〜」

また来年もあったら行きたいなと思いました!

話は変わって、以前このような記事を書いたと思います。

https://zenn.dev/terusi/articles/46abffaaf7f002

この記事を書いてから時間が経ちふと、疑問に思ったことがあったので検証してみました。

検証のソースコード

前回のソースコードを少しだけ変更してみました。

App.tsx
import { AComponent } from "./components/a-component";

interface Sample {
  username: string;
  password: string;
}

function App() {
  const sample: Sample = {
    username: "taro",
    password: "hogehoge",
  };

  const omitSample: Omit<Sample, "password"> = {
    ...sample,
  };

  return <AComponent {...omitSample} />;
}

export default App;

みなさん、ここで今回対象のソースコードをよく眺めてください。

このソースコードについて何を思いますか???

ここで余談を挟みますが

App.tsx
import { AComponent } from "./components/a-component";

interface Sample {
  username: string;
  password: string;
}

function App() {
  const sample: Sample = {
    username: "taro",
    password: "hogehoge",
  };

  return <AComponent {...sample} />;
}

export default App;

とすると、

エラーが出るように型定義しておきました。

今回対象のコードでは、エラーは出ていません。

対象のコードについて

長々と余談を挟んだところで上記コードについてまずは良いのか悪いのかを述べます。

結論、

悪いです!!!!!

では何が悪いのでしょうか??

悪いところは、、、、

const omitSample: Omit<Sample, "password"> = {
    ...sample,
};

ここです。
これは何が悪いのでしょうか???

そもそもTypescriptって型を持ったJavascriptであると思っています。
私は、Typescriptは化粧をしたJavascriptだとよく表現をしています。

「anyばっかのすっぴん状態やtype safeの濃い化粧をしたとしても中身はJavascriptだ」と自分の中では思っています。

では型という名の化粧をとってみましょう。

const omitSample = {
    ...sample,
};

つまりこれって書き換えると、

const sample = {
    username: "taro",
    password: "hogehoge",
};

と同じオブジェクトになります。

つまり、

const omitSample = {
    username: "taro",
    password: "hogehoge",
};

となるので、AComponentに渡されるpropsにはpasswardが入ってしまっているのです。

案の定、

passwordが渡ってしまっていますね。

ではどうするべきなのか

スプレッド構文の使い方を間違えるとちゃんと型定義をしているにも関わらず、それをすり抜けてしまい予期せぬバグや問題が起こってしまいます。

なるべく最初からスプレッド構文を使うのではなく、ランタイムで要素を削るなり抽出するなりしてから使いたければスプレッド構文を使うようにするのが良いのかなと思います。

Typescriptは型定義をしていても明示的でない要素を定義する(スプレッド構文やAPI取得など)ときは型をすり抜けてエラーが吐かれないことがあります。

無闇にスプレッド構文を使うのはあまりよろしくないなと実感しました。

例えばですが、

export function omitKeyObject<T extends object, S extends keyof T>(
    obj: T,
    keys: S[]
): Omit<T, S> {
    const result = { ...obj };

    for (const key of keys) {
        delete result[key];
    }

    return result;
}

という関数を作り

App.tsx
import { AComponent } from "./components/a-component";
import { omitKeyObject } from "@/utils/omit-key-object";

interface Sample {
  username: string;
  password: string;
}

function App() {
  const sample: Sample = {
    username: "taro",
    password: "hogehoge",
  };

 const omitSample:Omit<Sample,"password"> = omitKeyObject(sample,["password"])
 
  return <AComponent {...omitSample} />;
}

export default App;

とするとomitSample

{
    username:"taro"
}

となるのでスプレッド構文を使えますね!!!

まとめ

今回、疑問に思ったことを調査してそれを記事にしました。
このスプレッド構文問題はTypescriptのバグなのかissueが上がっているのかは分かりませんが、少なくともchecker.tsでやっていると思うので流石にそのソースは見たくない😇

https://github.com/microsoft/TypeScript/blob/main/src/compiler/checker.ts

私自身スプレッド構文を無闇に使っていたので「やべ!」と思いました。
同じ反応をした人はこれから気をつけて実装していきましょう!!!

AIの発展がすごい昨今ですが、AIにもミスはありますしそこを見抜けるかがこれから我々の生きていく方法なのかなと思っています。
そのためには基礎知識があることが前提となっていくでしょう。
それも踏まえるとこの記事も大切な内容にはなるのではないでしょうか。

Discussion