クイズ: 分割代入の魅力
分割代入の達人ですか?
それとも、あなたの破壊の交響曲ですか?
このクイズは JavaScript の分割代入に関する知識を測ります。基本的なオブジェクト構文から入れ子のデストラクチャ、デフォルト値まで網羅します。さらに TypeScript のインライン型に関するボーナス問題もあります。
まずはウォームアップから――分割代入スキルを証明してください! 👇
このコードは何を出力しますか?
const person = { name: 'Dan Levy', location: 'Cape Town',};const { name, age } = person;console.log(`Name: ${name}, Age: ${age}`);person に age プロパティが存在しないので、age は undefined になります。絶対に Infinity ではありません 😅
これにより次のようになります:
Name: Dan Levy, Age: undefinedこのコードは何をしますか?
const person = [ 'Dan Levy', 'Cape Town' ];const [ name, origin, age ] = person;console.log(`Name: ${name}, Age: ${age}`);age 変数は tuple 配列に存在しないので、undefined になります。
これにより次のようになります:
Name: Dan Levy, Age: undefined入れ子の分割代入はどうですか?
'use strict';const person = { name: { first: 'Dan' }, address: { city: 'Denver' },};const { name: { first }, address: { city }, birth: { place },} = person;console.log( `First: ${first}, City: ${place}`,);birth: { place } プロパティは person に存在しないため、エラーがスローされます。
1つの解決策は、入れ子のプロパティにデフォルト値を提供することです。
入れ子のプロパティにアクセスする際は注意が必要です。エラーが見つけにくく、ブラウザやプラットフォームによってエラーメッセージが異なるため、デバッグがやや難しくなります。
最新の Chrome では: TypeError: Cannot read properties of undefined (reading 'place')
Node でも同様に TypeError になります。JavaScript が place を読み取る前に undefined から place を分割代入しようとするためです。
正確な文言はブラウザやランタイムによって異なります。
デフォルトをいくつか入れたら、これどうなる?
'use strict';const person = { name: { first: 'Dan' }, address: { city: 'Denver' },};const { name: { first = 'Unknown' }, birth: { place = 'Unknown' },} = person;console.log( `Hi ${first} from ${place}`,);birth が存在しないため、JavaScriptは undefined から place を分割代入しようとします。place のデフォルト値を適用する前に例外が発生します。
TypeError正確なエラーメッセージは実行環境によって異なります。入れ子のプロパティだけでなく、外側の birth オブジェクトにも = {} のデフォルトが必要です。
関数パラメータとして、これが何をするか?
'use strict';function displayUser({ name = "Unknown", age = -1,} = { place: "Unknown" }) { console.log(`Hi ${name} from ${place}`);}displayUser({ name: "Dan" });この関数は name と age プロパティを抽出し、必要に応じてデフォルトを使用します。この場合、デフォルトオブジェクトの place キーは単なるノイズで、displayUser() 内では使用されていません。
strict モードでも挙動は変わりません: 宣言されていない place バインディングを読み取ると ReferenceError がスローされます。
undefined 値はどのように扱われますか?
'use strict';function displayPlace({ name = "N/A", place = "N/A", age = -1,} = { place: "Unknown" }) { console.log(`${place}`);}displayPlace({ name: "Dan" });displayPlace({ name: "Dan", place: undefined });displayPlace({ name: "Dan", place: "Joburg" });引数を省略した場合と明示的に undefined を渡した場合に、デフォルトの引数オブジェクト { place: "Unknown" } が使われます。displayPlace() と displayPlace(undefined) はどちらも Unknown を出力します。
オブジェクトを渡した場合、その place プロパティが存在しないか undefined なら、プロパティのデフォルト値 "N/A" が使われます。引数全体とプロパティのデフォルトは別の段階で適用されます。
displayPlace() // Unknown displayPlace({ name: "Dan" }) // N/A displayPlace({ name: "Dan", place: undefined }) // N/A前の問題と似ているけど… null はどう扱われる?
function displayPlace({ name = "N/A", place = "N/A", age = -1,} = { place: "Unknown" }) { console.log(`${place}`);}displayPlace({ name: "Dan", place: null });displayPlace({ name: "Dan", place: undefined });1回目は place が null、2回目は undefined です。オブジェクト自体が渡されていても、place が存在しないか undefined ならプロパティのデフォルト値 "N/A" が使われます。null はデフォルト値を適用しないため、出力は null と N/A です。
ここからはTypeScript
さて、TypeScriptで… 何が起こるでしょうか?
'use strict';function displayPlace( { name = 'N/A', place = 'N/A', }: { name: string; place: string; age: number; },) { console.log(`${place}`);}displayPlace({ name: 'Dan', place: null });TypeScript は place が string と型付けされているのに、呼び出しで null を渡しているためエラーを報告します。また、必須の age プロパティが省略されています。
型エラーを無視すれば、コードを実行するとコンソールに null が出力されます。
リネーム/代入を試してみよう…
'use strict';function displayPlace({ name = 'N/A', place: location = 'N/A',}: { name: string; place: string; age?: number;}) { console.log(`${location}`);}displayPlace({ name: 'Dan', place: 'Denver' });これによりコンソールに Denver が出力されます。関数シグネチャで place プロパティが location にリネームされています。サードパーティのデータ構造に合わせる際の一般的なパターン(分割代入時のプロパティ名変更)です。
型エラーを見つけてください:
function greet({ name: {first = "N/A", last = "N/A"}, birth: {place = "N/A"} = {}, age = -1,}: { name: {first?: string, last?: string}; birth: {place?: string}; age: number;}) { console.log(`Hi ${first} ${last} from ${place}`);}greet({ name: {first: 'Dan'} });greet 関数のシグネチャにエラーがあります。渡されたオブジェクトに age と birth プロパティがないので、型定義ではそれらをオプションにすべきです。
birth プロパティはデフォルト値で分割代入されていますが、型定義では存在することが要求されています。TypeScript でプロパティをオプションにするには ? 演算子を使います。
birth?: { place?: string } は birth: { place?: string } | undefined と同じではないことに注意してください。
今度は 代入(f、l、p 変数に注目)
'use strict';function greet( { name: {first: f = "N/A", last: l = "N/A"}, birth: {place: p = "N/A"} = {}, age = -1, }: { name: {first?: string, last?: string}; birth?: {place?: string}; age?: number; }) { console.log(`Hi ${f} ${l} from ${place}`); // What will 👆 do?}greet({ name: {first: 'Dan', last: 'Levy'}, birth: {place: 'Cape Town'},});別のエラー!もう推測し始めましたか?!
デフォルトや代入、型が絡む分割代入の階層は読みにくいです!
place を p 変数に再代入すると、console.log 文のスコープ内ではもはや定義されていません。
console.log(`Hi ${f} ${l} from ${place}`); // ❌// to:console.log(`Hi ${f} ${l} from ${p}`); // ✅さらに挑戦したい方は、クイズ一覧もどうぞ。
