🧌

TSKaigi 2024で発表した内容の元ネタ「なぜZustand TypeScript実装はこんなに醜いのか」

2024/05/13に公開

TSKaigi 2024で短い発表をしましたが、これには元ネタというかオリジナルの記事があります。

https://blog.axlight.com/posts/why-zustand-typescript-implementation-is-so-ugly/

ChatGPTに翻訳してもらいました👇


なぜZustand TypeScript実装はこんなに醜いのか

ちなみに、JavaScript実装はとてもクリーンです

序論

注: この投稿はZustandライブラリのTypeScript実装に焦点を当てています。
ユーザーコードには影響しませんが、クリーンに保つべきです。

ZustandのJavaScript実装は非常に小さいですが、以下のツイートで見られます。

https://twitter.com/dai_shi/status/1583082766081531905

しかし、そのTypeScript実装はかなり複雑です。
その理由はいくつかありますが、この投稿ではそのうちの一つを探ります。

SetStateInternal型

SetStateInternal 型をもう少し詳しく見てみましょう。

(これは内部使用を意図していることを念頭に置いてください。一般的なAPIとしては StoreApi['setState'] の使用を推奨します。)

type SetStateInternal<T> = {
  _(
    partial: T | Partial<T> | { _(state: T): T | Partial<T> }['_'],
    replace?: boolean | undefined
  ): void
}['_']

これらの奇妙な文字['_']は何でしょうか?

TypeScriptのハックであり、実際に使用されている文字(この場合は_)は重要ではなく、どんな単語でも機能します。
TypeScriptの技術的な詳細には触れませんが、このようなハックを使用しない場合に何が起こるかを見てみましょう。

想定されるより単純な代替タイプは次のとおりです。

type SetStateInternal2<T> = (
  partial: T | Partial<T> | ((state: T) => T | Partial<T>),
  replace?: boolean | undefined
) => void

これは特定のケースでは機能しません。
作為的な例でどう失敗するかを探ります。

以下の最初の例です:

type State = { name: string; age: number };

declare function run<Fn extends SetStateInternal<unknown>>(fn: Fn): void;
declare function run2<Fn extends SetStateInternal2<unknown>>(fn: Fn): void;
declare const setState: SetStateInternal<State>
declare const setState2: SetStateInternal2<State>
run(setState);
run2(setState2);

run2では型エラーが発生します。

二番目の例は以下の通りです:

type NameOnlyState = { name: string };
type StoreApi<T> = { setState: SetStateInternal<T> };
type StoreApi2<T> = { setState: SetStateInternal2<T> };
declare const store: StoreApi<State>;
declare const store2: StoreApi2<State>;
const nameOnlyStore: StoreApi<NameOnlyState> = store;
const nameOnlyStore2: StoreApi2<NameOnlyState> = store2;

nameOnlyStore2への割り当てが失敗します。

完全な再現はTypeScriptプレイグラウンドで確認できます: https://tsplay.dev/NBVO4N

ライブラリの利用者として、なぜこれが起こるのかや解決方法を知る必要はありません。
これはTypeScriptの魔法使いが対処すべきことです。

終わりに

この投稿では、ZustandのTypeScript実装の複雑さの背後にある理由の一つを強調しました。
他の理由もありますが、将来の投稿で取り上げることができます。

Discussion