こんにちは。フロントエンドエンジニアの渡邉です。 普段ReactとTypeScriptを書いています。 今回はTypeScriptのUtility Typesについて紹介します。 記事のタイトルが某 大柴さんみたいになっていますが、この記事を読んだ方の力に少しでもなれたら幸いです。
目次
- Utility Types
- よく使うUtility Types
- その他Utility Types
- 最後に
Utility Types
Utility Typesは楽にType Transformするための型で、TypeScriptによって提供されています。
Partial<T>
Readonly<T>
Record<K,T>
Pick<T,K>
Omit<T,K>
Exclude<T,U>
Extract<T,U>
NonNullable<T>
Parameters<T>
ConstructorParameters<T>
ReturnType<T>
InstanceType<T>
Required<T>
ThisParameterType
OmitThisParameter
ThisType<T>
よく使うUtility Types
以下のUtility Typesは個人的によく使うので説明と実際の使用例を紹介します。
- Pick
- Omit
- Parameters
- ReturnType
Pick<T,K>
Tに渡した型から指定のプロパティを抽出した型に変換します。
type Hoge = { hoge: string foo: number } type PickFoo = Pick<Hoge, | 'foo'> /* type PickFoo = { foo: number } */
Pickをよく使う理由として、ある型の一部分だけが必要な型を作成する場面がよく出てくるからです。 例えば下記コードのようなUser型があり、その中でnameとgenderだけを使う型が欲しい時に使います。
interface User { id: string name: string gender: string email: string birthday: Date } type ViewUserInfo = Pick<User, 'naem' | 'gender'>
Omit<T,K>
Tに渡した型から指定のプロパティを除去した型に変換します。
type Hoge = { hoge: string foo: number } type OmitFoo = Omit<Hoge, | 'foo'> /* type OmitFoo = { hoge: string } */
OmitはPickとは逆に、ある型の一部分だけがいらない型を作成するときに使います。
User型からgender
だけ使わない型が必要な場合。
interface User { id: string name: string gender: string email: string birthday: Date } type ViewUserInfo = Omit<User, 'gender'>
Parameters<T>
Tに渡した関数の引数の型をタプルとして抽出した型にします。
type Hoge = { hoge: string foo: number } const hogeFunc = (arg: Hoge) => { console.log(arg) } type ParametersHoge = Parameters<typeof hogeFunc> /* type HogeParameters = [Hoge] */
postでリクエストする場面を例に説明します。
型ファイル(types.ts
)とapi呼び出し関数をまとめたファイル(apis.ts
)、実際に実行するファイル(example.ts
)に分けています。
example.tsをまずParameters
を使わず実装してみます。
types.ts
export interface PostData { name: string email: string }
apis.ts
import { PostData } from './types' import axios from 'axios' // PostData型の引数を取る export const postData = async(data: PostData) => { await axios('/users', data) return 'success' }
example.ts
import { postData } from './apis' postData({ name: '太郎', email: 'hoge@hoge.com' }).then(res => { // ...処理 })
上のコードの様に引数に直接objectをを渡すと可読性が落ちるので、引数に渡す用の変数を宣言します。 ただその時点では型が分からないため補完が効かないのと、dataに何が必要なのか分かりません。
import { postData } from './api' const data = { name: '太郎', } // dataのプロパティに漏れがあった場合ここで気付きます。 postData(data).then(res => { // ...処理 })
なので、types.tsからPostData型をimportしてきても良いのですが、postDataがすでにimportされているので、Parameters
を使って変数dataをpostDataのパラメータの型にします。
example.ts
import { postData } from './apis' // 第一引数の型がほしいので[0]で抽出しています。 const data: Parameters<typeof postData>[0] = { name: '太郎', email: 'hoge@hoge.com' } postData(data)
直感的に「postData関数のパラメータの型だ」ということが分かるので良いです。
ReturnType<T>
関数の返り値の型を返します。
type Hoge = { hoge: string foo: number } const hogeFunc = (arg: Hoge) => { return arg.hoge } type ReturnTypeHoge = ReturnType<typeof hogeFunc> /* type ReturnTypeHoge = string */
redux-sagaを使って開発しているときに自分はReturnType
を使っています。
redux-sagaについて知りたい方はスタメンでもこちらの記事で紹介しています。
redux-saga/effects
のselect
を使いstate
からnameInputValue
とemailInputValue
抽出してそのデータをaxios
を使いpostする例で紹介します。
前提条件
- redux-sagaを使うための設定等は省いて、実際に使用するコードの部分だけ記載
- input要素からonChangeイベントでnameInputValue
とemailInputValue
がstateにセットされている
const POST_DATA = 'POST_DATA'; interface AppState { user: UserState, // その他state } interface UserType { id: string name: string email: string } interface UserState { users: UserType[] nameInputValue: string // input value state emailInputValue: string // input value state } interface PostData { name: string, email: string } // action const postDataAction = (id: string) => ({ type: POST_DATA }); // api呼び出し関数 export const postData = async(data: PostData) => { try{ await axios.post('/users', data) return {payload: 'success'} }catch { return {error: 'error'} } } // セレクター const userSelector = (state:AppState) => state.user; // saga task function* runPostData(){ // ReturnType<typeof userSelector> 型変数を宣言して、代入時に型チェックする const { nameInputValue, emailInputValue }: ReturnType<typeof userSelector> = yield select(userSelector) // ここでも Parameters<typeof postData> 型変数を宣言して、代入時に型チェックする const data: Parameters<typeof postData> = { name: nameInputValue email: emailInputValue } const { payload, error }: { payload?: string, error?: string } = yield call(CompassNoteAPI.requestCompassNote, params) if(payload && !error){ yield put(...) // 成功時の処理 }else{ yield put(...) // 失敗時の処理 } } // saga task function* handlePostData() { yield takeEvery(POST_DATA, runPostData) }
yield
を使用して変数に代入すると型推論でany型になってしまうため、ReturnType
を使い型を指定しています。
その他Utility Types
Partial<T>
Tに渡した型のプロパティを全て省略可能にします。
type Hoge = { hoge: string foo: number } type PartialHoge = Partial<Hoge> /* PartialHoge = { hoge?: string foo?: number } */
Readonly<T>
Tに渡した型のプロパティを全てreadonly
にして再代入不可にします。
type Hoge = { hoge: string foo: number } type ReadonlyHoge = Readonly<Hoge> /* type ReadonlyHoge = { readonly hoge: string readonly foo: number } */
Record<K,T>
Kに渡した型がプロパティとなりTがそのプロパティの型になります。
interface Hoge { title: string; } type Foo = 'home' | 'about' | 'contact'; type RecordHoge = Record<Foo, Hoge> /* type RecordHoge = { home: Hoge; about: Hoge; contact: Hoge; } */
Exclude<T,U>
Tに渡した型から、Uの型を除去した型に変換します。
type Hoge = { hoge: string foo: number } type Bar = { bar: boolean } type ExcludeHoge = Exclude<Hoge | Bar, Hoge> /* type ExcludeHoge = { bar: boolean } */
Extract<T,U>
Tに渡した型から、Uの型を抽出した型に変換します。
type Hoge = { hoge: string foo: number } type Bar = { bar: boolean } type ExtractHoge = Extract<Hoge | Bar, Hoge> /* type ExtractHoge = { hoge: string foo: number } */
NonNullable<T>
T型からnullとundefinedを除外した型にします。
type Hoge = { hoge: string | undefined foo: number | null } type NonNullableHoge = NonNullable<Hoge> /* type NonNullableHoge = { hoge: string; foo: number; } */
これには注意点が一つあって、optionalのプロパティは除去されないです。
type Hoge = { hoge?: string foo: number | null } type NonNullableHoge = NonNullable<Hoge> /* type NonNullableHoge = { hoge?: string; foo: number; } */
ConstructorParameters<T>
Parametersのコンストラクター版です。
class Hoge { constructor(a: string, b: number) {} } type ConstructorParametersHoge = ConstructorParameters<typeof Hoge> /* ConstructorParametersHoge = [string, number] */
InstanceType<T>
型Tのコンストラクタの返り値の型を返します。
class Hoge { constructor(a: string, b: number) {} } type InstanceTypeHoge = InstanceType<typeof Hoge> /* type InstanceTypeHoge = Hoge */
Required<T>
型Tの省略可能のプロパティを必須にします。
type Hoge = { hoge?: string foo?: number } type RequiredHoge = Required<Hoge> /* type RequiredHoge = { hoge: string; foo: number; } */
ThisParameterType
thisのパラメータを取得します。(使い所がわからない)
function toHex(this: Number) { return this.toString(16); } function numberToString(n: ThisParameterType<typeof toHex>) { return toHex.apply(n); }
OmitThisParameter
thisのパラメーターを削除します。(使い所がわからない)
注:--strictFunctionTypes
が有効になっている場合にのみ正しく機能します。
function toHex(this: Number) { return this.toString(16); } // The return type of `bind` is already using `OmitThisParameter`, this is just for demonstration. const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);
ThisType<T>
objectの中のthisを型Tにします。
interface Hoge { hoge: string; } interface Foo { foo(): void; } // objの型はFooであり、obj内でのthisの型はHogeと明示的に指定します const obj: Foo & ThisType<Hoge> = { foo() { console.log(this.hoge); // undefined }, };
最後に
実際に触ってみてUtility Typesはとても便利ですが、公式ドキュメントの見つけづらいところにあります。 今回の記事で少しでも多くの人に知ってもらえれば幸いです。
株式会社スタメンでは一緒に働くエンジニアを募集しています。ご興味のある方はぜひエンジニア採用サイトをご覧ください。