🗣️

Typescript これ使ってみて Part 1

2024/07/17に公開

はじめに

TypeScriptには新しい機能がたくさん追加されていて、どれを使うか迷うことがあります。本ブログでは、次の開発にすぐ使えそうなTypeScriptの面白い機能をいくつかピックアップして紹介します。

using キーワードでリソース管理をシンプルに

こちらは C#usingPythonwith と同じものです。

using が導入される前は、以下のようにリソース管理を行いました。

export function doSomeWork() {
    const path = ".some_temp_file";
    const file = fs.openSync(path, "w+");
    try {
        // use file...
    }
    finally {
        // Close the file and delete it.
        fs.closeSync(file);
        fs.unlinkSync(path);
    }
}

最後に fs.closeSync(file);fs.unlinkSync(path); を忘れてしまうと大変なので、using キーワードを使えば楽になります。

class TempFile implements Disposable {
    [Symbol.dispose]() {
        // Close the file and delete it.
        fs.closeSync(this.#handle);
        fs.unlinkSync(this.#path);
    }
}

export function doSomeWork() {
    using file = new TempFile(".some_temp_file");
    // use file...
    if (someCondition()) {
        // do some more work...
        return;
    }
} // スコープの最後に `Symbol.dispose` が呼ばれる

using はフロントエンド開発ではあまり使わないかもしれませんが、実はテストコードを書くときに便利かもしれません。

const mockSomething = () => {
    // モックコード...

    return {
        [Symbol.dispose]: () => {
            // モックのテアダウン処理
        }
    }
}

it('should foo', () => {
    using mock = mockSomething();
    // スコープの最後に自動的にモックのテアダウン処理が呼ばれる
});

そもそもusing キーワードは ECMAScript Explicit Resource Management (proposal stage 3) のものなので、そろそろTypeScriptだけでなくJavaScriptでも使えるようになります。

const T

const T は、定数型パラメータの一種です。

例えば、以下のようにジェネリック関数を定義すると

declare function useStatus<T>(statuses: T[]): T;

パラメーターの statusesstring[] を渡すと、関数の戻り値の型が string になります。

const loadingStatus = useStatus(['loading', 'idle']);
// loadingStatus: string

しかし、const T で定義すると関数の戻り値の型がもっと具体的に推論されます。

declare function useStatus<const T>(statuses: T[]): T;

const loadingStatus = useStatus(['loading', 'idle']);
// loadingStatus: 'loading' | 'idle'

以前は同じことをするために、パラメータを渡す際に as const を付ける必要がありましたが、const T で定義すれば使用する側が使いやすくなります!

Awaited

AwaitedUtility Types の一つで、Promiseが解決する型を抽出するために使用されます。

type ExampleType = Promise<string>;
type ResolvedType = Awaited<ExampleType>;  // ResolvedTypeはstring

ネストされたPromiseでも抽出することが可能です。

type NestedPromise = Promise<Promise<number>>;
type ResolvedNestedType = Awaited<NestedPromise>;  // ResolvedNestedTypeはnumber

非Promise型で使用すると、Awaitedはその型自体を返します。

type NonPromiseType = string;
type ResolvedNonPromiseType = Awaited<NonPromiseType>;  // ResolvedNonPromiseTypeはstring

React開発におけるAwaitedユーティリティ型の実用例として、非同期fetchデータをuseStateに型定義する際に Await<ReturnType<typeof foo>> の組み合わせで使えます。

async function fetchUserData(): Promise<{ name: string; age: number }> {
    // Simulate an API call
    return { name: 'John Doe', age: 30 };
}

function useUserData() {
    const [data, setData] = useState<Awaited<ReturnType<typeof fetchUserData>> | null>(null);

    useEffect(() => {
        async function loadData() {
            const userData = await fetchUserData();
            setData(userData);
        }

        loadData();
    }, []);

    return data;
}

終わりに

まだ他にもたくさんあるので、Part 2に続けます(この記事が❤️をいっぱいもらえれば🙏)

参考

SocialPLUS Tech Blog

Discussion