TSLab【第7回 TypeScript勉強会】オブジェクト型の制御と汎用的な型制御

TSLab
  • URLをコピーしました!

こんにちは!RYOTAです!

ご覧いただきありがとうございます!

当記事はTSLab勉強会(定例会)のレポート記事になります!

目次

はじめに

私が運営しているTSLabの第7回定例会の実施ログとなります。

今回のテーマはGenerics・keyof・typeofです!

第6回の内容は下記の記事にまとめていますので気になる方はあわせてご覧くださいませ。

あわせて読みたい
TSLab【第6回 TypeScript勉強会】オブジェクトの型定義 こんにちは!RYOTAです! ご覧いただきありがとうございます! 当記事はTSLab勉強会(定例会)のレポート記事になります! 【はじめに】 私が運営しているTSLabの第6回定...

ディスカッション内容

ジェネリクスによるプロパティ制約

例えば下記のようなオブジェクトとキーを受け取って、その値を返す関数があるとします。

エラーにある通りobjの型が確定していないので、obj[key]がanyになってしまいます。

// objが unknown型 として判定されてしまう
function getObjectValue<Type>(obj: Type, key: string) {
  // エラー: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'unknown'.No index signature with a parameter of type 'string' was found on type 'unknown'.
  return obj[key]
}

こういう場合はobjのプロパティのキー一覧を取得して、keyの型として指定することでアクセスが可能になります。

// "Key extends keyof Type" でobjのプロパティをキーの型として制約
function getObjectValue<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key];
}

const testObj = { a: 1, b: 2, c: 3, d: 4 };

// プロパティ "a" は存在するのでアクセス可能
console.log(getObjectValue(testObj, "a"));

// プロパティ "m" は存在しないのでエラーを返す
getObjectValue(testObj, "m");
// エラー: Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.

存在しないプロパティにアクセスしようとすると、エラーを吐いてくれるので良きですね!

オブジェクトのキーの一覧型の生成

オブジェクトのキー一覧と取得して、リテラル型として型に割り当てることが可能です。

下記の例だと、Userはname、ageというプロパティしか存在しないので、型Uを使ってemailという値を代入できない状態です。

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

// TestType のキーの一覧を取得
type U = keyof User;

// OK
const testName: U = "name"

// Error
const testEmail: U = "email"
// Type '"email"' is not assignable to type 'keyof User'.

よく使う構文になるので是非覚えておきたいですね!

関数の返り値型の生成 (ReturnType<Type>)

Utility Typesの一つ、ReturnType<Type>を使うことで、関数の返り値の型を取得することが可能です。

使い道は多そうですが、現時点でいい感じのユースケースが思いつかないので一旦参考程度に。

type TestFunction = (x: string) => boolean
type K = ReturnType<TestFunction>;
// type K = boolean

注意点: 型に対しては使えるが、関数自体には使えない

ReturnType<Type>は関数型に対しては割り当てる事が可能ですが、関数自体には割り当てることができません。

const TestFunction = () => {
  return {
    x: 'hoge',
    y: 'fuga'
  }
}

type K = ReturnType<TestFunction>;
// エラー: 'TestFunction' refers to a value, but is being used as a type here. Did you mean 'typeof TestFunction'?

「typeof使わないんか?大丈夫か?」って怒ってくれてますね。

const TestFunction = () => {
  return {
    x: 'hoge',
    y: 'fuga'
  }
}

// typeof で関数の型を取得してあげると ReturnType に割り当て可能
type K = ReturnType<typeof TestFunction>;

キー、バリュー型からオブジェクト型にマッピング (Record<Type>)

今回の議題とは逸れたのですが、ディスカッションしている中で使った事ないメソッドをご紹介いただきました。

Utility Typesの一つ、Record<Type>を使うことで、多次元構造のオブジェクトのキー、オブジェクトを制御・結合できるっぽい!

type UserInfo = {
  name: string
  age: number
  email: string
}

type UserName = "testA" | "testB" | "testC"

const users: Record<UserName, UserInfo> = {
  testA: { name: "Test A", age: 11, email: "test.a@hoge.com"},
  testB: { name: "Test B", age: 22, email: "test.b@hoge.com"},
  testC: { name: "Test C", age: 33, email: "test.c@hoge.com"},
}

この場合だとUserNameをエクスポートすることで、UserNameを使い回しつつ条件分岐等もかけられそうなので、使い道が多そうですね!学び!

アンビエント宣言

ディスカッションの中で“declareってなんぞ?”という会話になり、割と深めの話になったのですが、言語化がまだ難しいので別の機会に触れていこうと思います。

Reactの@typesファイルを見ながら、下記のようにインポートしてTSが解釈できる理由について語って行きました。

import React, { useState } from "react"

実際に@typesファイルを見てみると使ってますね。declare。

export = React;
export as namespace React;

declare namespace React {
// ~~~~~~~~~~~~~~~~~
}

今までノリで使った事があるdeclareの役割を”完全に理解しました”。

今度改めて記事にしようと思います。

さいごに

ジェネリクスが理解できてること前提で進みつつあるので、徐々に難易度が上がってきてますね。笑

今回、参加者さんの一人がものゴッツイ型パズルを作ってきてくださったのですが、私の理解力ではまだ分解しきれないので、またの機会に触れていこうと思います。笑

以上!最後までご覧くださりありがとうございました!

今後はTSの勉強会とは別に、React会も同時並行で動かしていこうと思います!

一緒にTypeScriptを学びたい方はぜひ下記のアカウントからご連絡ください!

TSLab

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次