オブジェクトの分割代入って何をしているのか
まずイメージからいきましょう。
オブジェクトの分割代入は、「オブジェクトの中から、欲しいプロパティだけを“取り出して”変数にする書き方」です。
const user = {
name: "Taro",
age: 20,
};
const { name, age } = user;
console.log(name); // "Taro"
console.log(age); // 20
TypeScript{ name, age } = user という書き方で、
「user.name を name という変数に」「user.age を age という変数に」取り出しています。
TypeScript 的には、「オブジェクトの型から、プロパティごとの型をちゃんと推論してくれる便利な取り出し方」だと思ってください。
一番基本の分割代入と型推論
シンプルなオブジェクトから取り出す
type User = {
name: string;
age: number;
};
const user: User = {
name: "Taro",
age: 20,
};
const { name, age } = user;
TypeScriptこのとき、TypeScript はこう推論します。
nameの型はstringageの型はnumber
つまり、次のようなことが起きます。
name.toUpperCase(); // OK(string のメソッド)
age.toFixed(1); // OK(number のメソッド)
// name = 123; // エラー: Type 'number' is not assignable to type 'string'.
// age = "20"; // エラー: Type 'string' is not assignable to type 'number'.
TypeScriptポイントは、「分割代入しても型情報はちゃんと保たれる」ということです。user に型が付いていれば、その中から取り出した変数にも正しい型が付きます。
変数名を変えて取り出す(別名をつける)
プロパティ名と違う変数名を使いたいとき
ときどき、「プロパティ名は name だけど、変数名は userName にしたい」みたいな場面があります。
そんなときは、こう書きます。
const user = {
name: "Taro",
age: 20,
};
const { name: userName, age: userAge } = user;
console.log(userName); // "Taro"
console.log(userAge); // 20
TypeScriptname: userName は、
「user.name を userName という変数に入れる」という意味です。
TypeScript 的には、
userNameの型はstringuserAgeの型はnumber
と推論されます。
「左側の name は“どのプロパティを取るか”、右側の userName は“変数名”」
この対応関係を意識して読むと、すぐ慣れます。
デフォルト値付きの分割代入と型
プロパティがないときに使う「デフォルト値」
オブジェクトにそのプロパティがない場合に備えて、デフォルト値を設定することもできます。
type User = {
name: string;
age?: number;
};
const user: User = {
name: "Taro",
};
const { age = 18 } = user;
console.log(age); // 18
TypeScriptここで age は、「number 型として扱われる」ことが多いです。age プロパティがあればその値が入り、なければ 18 が入ります。
重要なのは、「optional なプロパティを分割代入するときは、“ないかもしれない”ことを意識する」ということです。
デフォルト値を付けるのは、「ないときの振る舞い」を明示する一つの方法です。
ネストしたオブジェクトの分割代入
一段ネストしたプロパティを取り出す
ネストしたオブジェクトでも、分割代入はそのまま使えます。
type User = {
name: string;
address: {
city: string;
zip: string;
};
};
const user: User = {
name: "Taro",
address: {
city: "Tokyo",
zip: "100-0001",
},
};
const {
address: { city, zip },
} = user;
console.log(city); // "Tokyo"
console.log(zip); // "100-0001"
TypeScriptここで起きていることは、
user.address.cityをcityという変数にuser.address.zipをzipという変数に
取り出しているだけです。
TypeScript は、User の型からさらに address の型を辿って、city: string, zip: string だとちゃんと理解してくれます。
ネスト部分に別名をつける
const {
address: { city: userCity, zip: userZip },
} = user;
TypeScriptこう書けば、
userCityの型はstringuserZipの型はstring
として扱われます。
ネストが深くなっても、「プロパティの構造に沿って書いているだけ」だと捉えると、怖さが減ります。
残りをまとめて受け取る(rest構文との組み合わせ)
一部だけ取り出して、残りをまとめる
分割代入は、「一部だけ取り出して、残りをまとめて受け取る」こともできます。
const user = {
name: "Taro",
age: 20,
email: "taro@example.com",
};
const { name, ...rest } = user;
console.log(name); // "Taro"
console.log(rest); // { age: 20, email: "taro@example.com" }
TypeScriptここで rest の型は、ざっくりこう推論されます。
{
age: number;
email: string;
}
TypeScriptつまり、「元のオブジェクトの型から、取り出したプロパティを除いた残りの型」になります。
この書き方は、「一部だけ個別の変数にして、残りはまとめて次の処理に渡したい」ときにとても便利です。
関数の引数での分割代入と型
引数でそのまま分割する
関数の中でよく使うのが、「引数をその場で分割代入する」書き方です。
type User = {
name: string;
age: number;
};
function printUser({ name, age }: User) {
console.log(`${name} (${age})`);
}
const user: User = { name: "Taro", age: 20 };
printUser(user);
TypeScriptここでは、
- 引数の型は
User - その
Userからnameとageを分割代入で取り出している
という構造になっています。
関数の中で user.name や user.age と書かなくてよくなるので、
「この関数は User のどのプロパティを使っているのか」が一目で分かるようになります。
別名やデフォルト値も組み合わせられる
function printUser({ name: userName, age = 18 }: User) {
console.log(`${userName} (${age})`);
}
TypeScriptこのように、
nameをuserNameという変数名で受け取りageがなければ 18 を使う
といった柔軟な受け取り方も、型付きのまま書けます。
分割代入と型を扱うときに意識してほしいこと
オブジェクトの分割代入は、見た目が少しトリッキーに感じるかもしれませんが、
やっていることは一貫してシンプルです。
「このオブジェクトの、このプロパティを、この変数名で取り出したい」
それを、短く・宣言的に書くための記法です。
TypeScript はそこに対して、
- 元のオブジェクトの型から、プロパティごとの型をちゃんと推論し
- optional なプロパティなら「ないかもしれない」ことを意識させ
- 関数の引数でも同じように型安全に扱えるようにしてくれます。
なので、コードを書くときはこう考えてみてください。
「この関数、本当は user.name と user.age しか使ってないよな」
「だったら、引数のところで { name, age }: User と分割してしまおう」
そうすると、「どのデータを使っているのか」が型とコードの両方からハッキリ見えるようになります。
それが、分割代入と型を組み合わせる一番おいしいところです。
