Introduction:
こんにちは、コードリックのシステム開発部の瀬川です。
TypeScriptのレベルにまだまだ伸びしろがあると思い勉強中です!
最近、管理画面の作成や個人開発でTypeScriptを使っていて疑問点などが多かったので、解説していきたいと思います。
- nullとundefinedの取り扱いについて
- 型を値として使用したい
- index signatureは使用するべきなのか
Suject:各セクションにまとめましたので、ハイライトされている部分の認識がある方は確認いただければと思います。
・nullとundefinedの取り扱いについて
- この二つの定義を同じものと理解していた
- Typeの取り扱い方が不安
まず最初にnullと
undefinedの違いについて確認したいと思います。 - null→データが存在しない。(初期値が決まっていない)
- undefined→初期値が入っていない。
このようにnullにもundefindにも明確に意味がありますが、似たように感じますね。
どのプロジェクトでも使用するとは思いますが、最初に各プロジェクトによって定義されたルールにしたがっていきたいですね。
null
❌ undefined⭕️(❌をやむを得ない場合許容する)
undefined
❌ null⭕️
(❌をやむを得ない場合許容する)
undefined⭕️
null⭕️
1のパターンが一般的かもしれませんね。
1の場合でも3のパターンでも下記のように
nullとundefinedは一個で解決したいです。
//等価演算子
function foo1(arg: string | null | undefined) {
if (arg != null) {
return 'string'
}
return 'null | undefined'
}
//厳密等価演算子
function foo2(arg: string | null | undefined) {
if (arg !== null) {
return 'string | undefined'
}
return 'null'
}
ただ基本的にTypeScriptはガイドラインでも定められていて、Douglas Crockford氏はnullは使わずundefinedを使う方がいいと考えているみたいですね。
興味のある方は
TSガイドラインを確認してみてください。
私自身はnullやundfinedに特別な意味などは持たせたくないので、基本的にnullは避けて実装していきたいと改めて思いました。
・型を値として使用したい
- ユニオン型で定義したのに再度値を入れないといけないのでもっと簡略化できそう
つい最近、型でユニオン型定義をしている際、例えばselector optionなどに初期値を入れるときに、また値を入れなければいけないのかと思ったことがあり、下記のように実装しました。
export enum HotelStatus {
CheckIn = 'チェックイン',
CheckOut = 'チェックアウト',
RoomService = 'ルームサービス',
CleaningInProgress = '清掃中',
LaundryService = 'ランドリーサービス',
GuestRequest = 'ゲストリクエスト',
MaintenanceInProgress = 'メンテナンス中',
Completed = '完了'
}
export type Hotel = {
name: string;
address: string;
status: HotelStatus;
}
//使用例
const hotel1: Hotel = {
name: 'Hotel A',
address: '123 Main St, City, Country',
status: HotelStatus.Completed
};
const hotel2: Hotel = {
name: 'Hotel B',
address: '456 Elm St, City, Country',
status: HotelStatus.GuestRequest
};
const hotel3: Hotel = {
name: 'Hotel C',
address: '789 Oak St, City, Country',
status: HotelStatus.MaintenanceInProgress
};
このように実装することで、型定義と値定義をできるので便利ですね。
ドキュメントを少し見ているとこのようにenumを使用せずに「コンパニオンオブジェクト」を使用しているパターンも載っていました。
このコンパニオンオブジェクトの場合、値として定義しておけば、値も型も一つで取得できるのでスマートです。
せっかくTypeScriptを使用しているので、型推論していない場所と通常の場所で使えるといいですね。
export type Rectangle = {
height: number;
width: number;
};
export const Rectangle = {
from(height: number, width: number): Rectangle {
return {
height,
width,
};
},
};
import { Rectangle } from "./rectangle";
const rec: Rectangle = Rectangle.from(1, 3);
console.log(rec.height); //1
console.log(rec.width);//3
・index signatureは使用するべきなのか
- かなり自由に作れちゃうので本当に大丈夫かな?
- Firebaseのルールを細かく書きたいときには不便かも
そもそもindex signatureって?
下記のようにプロパティに任意の型を持たせることができる便利な型です。
interface MyObject {
[key: string]: number; // 文字列型のキーに対応する値の型はnumber
}
const obj: MyObject = {
key1: 10,
key2: 20,
key3: 30
};
console.log(obj['key1']); // 10
console.log(obj['key2']); // 20
console.log(obj['key3']); // 30
index signatureを使用すべき場合:
- オブジェクトが動的にプロパティを持つ場合:オブジェクトがさまざまなプロパティを持ち、それらのプロパティがあらかじめわかっていない場合、index signatureは非常に便利です。例えば、辞書やマップのようなデータ構造を表現する際に使用されます。
- オブジェクトのプロパティ名が変化する場合:オブジェクトのプロパティ名が静的でなく、実行時に決まる場合、index signatureを使用してその柔軟性を確保できます。
index signatureを使用すべきでない場合:
- オブジェクトが固定されたプロパティ構造を持つ場合:オブジェクトのプロパティが事前にわかっており、それらのプロパティ名が変化しない場合、index signatureは不要です。
- オブジェクトのプロパティに特定の制約がある場合:特定の型や範囲の値を持つプロパティを持つオブジェクトの場合、index signatureでは制約を表現することができません。
これはすごい便利な型でもありますが、下記の場合はちょっと型としては甘くなってしまいます。下記はその例です。
const obj: { [key: string]: string } = { a: 'a' };
// itemはstringとして推論される、実態はundefined
const item = obj.b;
// Cannot read properties of undefined (reading 'toUpperCase')
item.toUpperCase();
//対応方法
const hoge = new Map<string, number>();
hoge.set('hoge', 1);
hoge.get('hoge'); // -> number | undefinedになる
undefinedも許容できるMapの方がよりきれいに型が格納できますね。
Firebaseでは
・追加されるのではなくプロパティを再度取得して取ってこなければならないので、情報取得回数が増えてしまいます。
・ルールで使いにくい。存在していないことがあるので、エラーの原因になりやすい。
結論として、便利な型ではあるのでメリットとデメリットをしっかり把握して使用していきたいですね。
参考にさせてもらったサイトは
こちらです。
Finaly:最後まで読んでいただきありがとうございました。
TypeScriptの5.4も出て、よさそうなアップデートなので、その情報もしっかり理解してTypeScriptをより効率的に使えるよう、強くなって業務効率向上に邁進いたします。