TypeScriptでよく見る「?」「!」と仲良くしたい
どうも、株式会社iCAREフロントエンドエンジニアのoreoです。
弊社フロントエンドは、Vue.js, TypeScriptで開発をしています。あるPRで、弊社技術顧問の方から、下記レビューを頂きました。
リファクタするときに気になったんですが、不要なところに?がついているのを散見しましたキャスト! | ? | asはどれも危険なコードでレビュワーの負担も大きくなるので、きちんと必要なところだけに入れるように気をつけてみてください
今まで?
や!
を安易に使っていたので、彼らの挙動について整理してみたいと思います!
?
について
?
)
オプションパラメーター(概要
引数にオプションパラメーターをつけると、その引数が省略可能になります。例1.1で、test()
を実行した場合、Expected 1 arguments, but got 0.
と怒られますが、オプションパラメーターをつけると(例1.2)、怒られなくなります。
//例1.1
const test = (x:number,y:number):void=>{
console.log(x);
};
test(1); //エラー Expected 2 arguments, but got 1.
//例1.2
const test = (x:number,y?:number):void=>{
console.log(x);
}
test(1); //OK
注意点
- 例1.2において、オプションパラメーターを使用されると、引数yは、
number | undefined
のユニオン型と認識されてしまいます。 - また、オプションパラメーターは必ず引数の最後に書かないといけません(例1.3)。
//例1.3
const test = (x?:number,y:number):void=>{
console.log(x,y);
};
//エラー A required parameter cannot follow an optional parameter.
補足:デフォルトパラメーター
👇のような書き方で、オプションパラメーターにデフォルト値を与えることもできます。また、デフォルトパラメーターは、引数の最後に書かなくてもOKです。
//例1.4
const test = (x: number, y = 3): void => {
console.log(x+y);
console.log(x);
};
test(1); //OK
// console.log(x+y)は4と出力
// console.log(x)は1と出力
?.
)
オプショナルチェイニング(概要
TypeScriptの 3.7以降で使用可能。null
やundefined
のプロパティに対して.
で参照するとエラーが発生する(例2.1)が、?.
を使うと、エラーは発生せず、undefined
が返ります(例2.2)。
//例2.1
type Address = {
prefecture: {
name: string
city?: {
name: string
}
}
}
const address: Address = {
prefecture: {
name: 'tokyo',
},
}
const targetAddress = address.prefecture.city.name //エラー Object is possibly 'undefined'.
console.log(targetAddress)
//例2.2
type Address = {
prefecture: {
name: string
city?: {
name: string
}
}
}
const address: Address = {
prefecture: {
name: 'tokyo',
},
}
const targetAddress = address.prefecture.city?.name //OK
console.log(targetAddress)
注意点
-
?.
を使用した場合すると、短絡評価されます。短絡評価では、?.
よりも左側がnull
やundefined
の場合、?.
より右側は評価(実行)されなくなります。
??
)
Null合体演算子(概要
TypeScriptの 3.7以降で使用可能。左辺がnull
またはundefined
の場合に右の値を返し、それ以外の場合は左の値を返します(例3.1)。
//例3.1
const name = null ?? 'Yamada Taro';
console.log(name);
// 'Yamada Taro'が出力
注意点
??
は、OR演算子||
とは違って、null
またはundefined
以外のfalsyな値の場合(例えば、''
や0
)、左の値を返します。
//例3.2 OR演算子
const age = 0 || 18;
console.log(age);
// 18が出力
//例3.3 Null合体演算子
const age = 0 ?? 18;
console.log(age);
// 0が出力
??=
)
Null合体代入演算子(概要
TypeScriptの 4.0以降で使用可能。左辺がnull
またはundefined
の場合に代入します(例4.1)。
//例4.1
const pilot = { name: null };
pilot.name ??= "shinji";
console.log(pilot.name); //shinjiが出力
//例4.2
const pilot = { name: "shinji" };
pilot.name ??= "asuka";
console.log(pilot.name); //shinjiが出力。asukaが代入されない。
例4.3の2行は同じ結果となります。
const pilot = { name: null };
//例4.3
pilot.name ??= "shinji"; //Null合体代入演算子
pilot.name = pilot.name ?? "shinji"; //Null合体演算子
注意点
左辺がnull
またはundefined
以外のfalsyな値の場合(例えば、''
や0
)、代入されません。
//例4.4
const pilot = { age: 0 };
pilot.age ??= 14;
console.log(pilot.age) //0が出力
!
について
!
)
非nullアサーション演算子(概要
null許容型(T | null
またはT | null | undefined
)に対して、非nullアサーション(!
)を使用することで、その型がnull | undefined
ではなくT
であることをコンパイラに明示できます。
//例5.1
type Dog = { name: string };
const mydog = { name: "sora" };
const getDog = (dog?: Dog) => {
console.log(dog.name); //エラー Object is possibly 'undefined'
};
getDog(mydog);
//例5.2
type Dog = { name: string };
const mydog = { name: "sora" };
const getDog = (dog?: Dog) => {
console.log(dog!.name); //OK
};
getDog(mydog);
注意点
強制的にnull | undefined
ではないことを明示してしまい、型の変化に気づけなくなる可能性があるので、基本的にあまり使わない方が良いかも。
!
)
明確な割り当てアサーション(概要
例6.1のように変数に値が割り当てらていない可能性がある場合(TypeScript が静的に検出できない場合)、その変数を使用すると怒られます。しかし、!
を使うことで、変数を使う時までにその変数に値が割り当てられていることをコンパイラに明示できます。
//例6.1
let name: string;
const addName = (val: string) => {
name = val;
};
addName("shinji");
console.log(name); //エラー Variable 'name' is used before being assigned.
//例6.2
let name!: string;
const addName = (val: string) => {
name = val;
};
addName("shinji");
console.log(name); //OK
注意点
コードの変更などで、変数が割り当てられななった場合に気づけなくなるので、こちらも基本的にあまり使わない方が良いかも。
最後に
改めてまとめることで、頭の中が整理されました。?
、!
は、とても便利ですが、その危険性(型の変化等に気づけなる)ことをしっかり理解した上で、使用すべきですね。また、チームメンバー間での、?
、!
についての理解度のすり合わせやコーディングルール明確化も重要なのかなとも思いました。
参考
株式会社オライリー・ジャパン「プログラミングTypeScript」
Discussion
いい記事!
ここやけど多分yじゃないかな?
あとこのオプショナルパラメーターって具体的にどういう関数で使えると思う?(クイズではなく単純に教えてほしい🙏)
コメントありがとうございます!
記載ミスしておりました..修正しております!
例えば、ライブラリのラッパー関数を作る時など、さまざまな場面で呼び出される可能性があって共通化する時に使うのかなと思っております!影響範囲が小さい(呼び出される場面が限定的)な場合は、オプションにすることはあまりないのかと。
違和感あれば教えていただけると幸いです!!
うーんまだ抽象的やからもっと具体的な例が知りたいな〜
例えば👇みたいな形で、
isAsc
をオプショナルパラメーターとして設定してあげると、返り値になる配列の要素の並び替え(昇順)を選択できます!競プロでstring型の標準入力をnumber[]型に変換して配列操作したい時に使う想定しています(昇順に並び替える機会、そんなにないと思いますが、、、、)なるほど〜めちゃ具体的になったな!
気になったのはこれやとデフォルトパラメーターでisAsc=falseってやっても全く同じことできそうやけど、そこの使い分けってなんかあるんかな?
初めに書けば良かったんやけど、自分が一番気になってるのはそこなんよね
確かに👇みたいに、
isAsc=false
にしても、同じ結果が得られますね!オプショナルパラメーターを使うと、
isAsc
の型がisAsc: boolean | undefined
と認識されてしまいますが、デフォルトパラメーターだと、isAsc
の型がisAsc: boolean
となるので、undefined
としての型を認識させるのかどうかの視点で使い分けできそうかと思いました🤔そうやな、いう通り
かな!
質問してばっかりやとあれやから自分も考えてみたけど
て感じになるんかな〜と思った。
例としては
デフォルト引数
Slackにメッセージを送信する。チャンネルを指定しなければデフォルトのチャンネルに送信される。
オプショナルパラメータ
メールを送信してエラーをコールバックで受け取りたい場合
みたいな感じかな、オプショナルの方もうちょっといいのありそうやけど
TypeScriptはオーバーロードの仕組みがないからその代わりでも使えるみたい
→ ごめん勘違いしててオーバーロードの仕組みは普通にあったわ🙏
なるほど、わかりやすい、、、!お話させて頂いて、理解が深まりました!ありがとうございます!!!
検索しにくかった事柄(「?」と「!」)がまとめて載っていて、助かりました!
ところで、undefinedのスペルが多くの箇所で間違ってます。
コメントありがとうございます!お恥ずかしい、、
修正させていただきました!!