ユースケースから学ぶ satisfies
この記事は何か
typescript 4.9 で satisfies operator がリリースされています。
しかし、typescript 全体からみると比較的新しい? ためか、あまりまだ satisfies が有効な場面で使われていない印象を受けるレポジトリが多いです。
またこれに間接的に関係するのか、AI が提出するコードも、satisfies を交えたコードをあまり提出してきません。
satisfies についての説明は web に多々あれど、いまいち使い所がわからない方のための記事です.
前提知識
- 「immutable な変数とは何か」を把握していること
-
as constを理解していること
satisfies とは
ケース1: 定数定義 - 定義名と中身に制約を設けたい
もっとも単純で、恩恵がわかりやすく、すぐに実践できる内容です.
- ケース:ユーザーのロール名を定数管理している場合
- 定数は大文字で定義して、実値は小文字で統一したい場合
初期値
export const ROLE = {
ADMIN: "admin",
EDITOR: "editor",
}
上記の場合、推論はこうなります
type ROLE: {
ADMIN: string;
EDITOR: string;
}
as const をつける
as const をつけると
export const ROLE = {
ADMIN: "admin",
EDITOR: "editor",
- }
+ } as const
下記のように推論されます.
type ROLE: {
readonly ADMIN: "admin";
readonly EDITOR: "editor";
}
これにより、 ROLE.ADMIN とした時に、 VSCode ならカーソルホバーしたときなどに中身が admin であることがわかります。
satisfies をつける
- 定数は大文字で定義して、実値は小文字で統一したい場合
という制約を satisfies で実現します。
export const ROLE = {
ADMIN: "admin",
EDITOR: "editor",
- } as const
+ } as const satisfies {[key in string]: Lowercase<key>}
推論結果は変わりません。ただし satisfies があるなしで、 ROLE という定数が
- 定数は大文字で定義して、実値は小文字で統一
されているかの保証の有無が変わります。
例えば
export const ROLE = {
ADMIN: "admin",
- EDITOR: "editor",
+ EDITOR: "Editor";
} as const satisfies {[key in string]: Lowercase<key>}
とすると、型エラーになります
↓↓↓ 実際に試してみるのが一番わかりやすい ↓↓↓
ケース2: switch 分の網羅性担保
こちらも単純で、恩恵がわかりやすく、すぐに実践できる内容です。
全ての switch 文で実装すべきです。
ケース3: テストコード - 依存インスタンスがでっかいとき
テストコード記述する際、
- あるインスタンスを mock する必要がある
- しかし、mock するそのインスタンスのパラメータが非常に多い。
- だが、実際にそのインスタンスのうち実際に使うメソッドはほんの一部.テストのためにインスタンスすべての値を定義するのは無駄が大きい
具体的に、
- ある関数が内部で AWS DynamoDB の Client を利用している
とした場合、jest による mock で、一例として下記のようなコードを書くことになります
import * as AWS from "aws-sdk"
const mockDynamoDBClient = {
update: jest.fn().mockReturnValue({
promise: mockUpdatePromise,
}),
} as unknown as AWS.DynamoDB.DocumentClient
:::message warn
AWS.DynamoDB.DocumentClient は update 以外にも大量のメソッドを持っており、仮の値を入れるのは非現実的😟
:::
とりあえずこれで
- あるインスタンスを mock する必要がある
は実現できます。が、
- ⚠️
as AWS.DynamoDB.DocumentClientによる形と実際の値の不一致
を許してしまします。言い換えると
- ⚠️
AWS.DynamoDB.DocumentClientで定義されていないメソッド名で mock してしまう可能性がある
ということです。例えば、下記のように変更しても、形エラーが発生しません. 事故りかねません ⚠️
import * as AWS from "aws-sdk"
const mockDynamoDBClient = {
- update: jest.fn().mockReturnValue({
+ updates: jest.fn().mockReturnValue({
promise: mockUpdatePromise,
}),
} as unknown as AWS.DynamoDB.DocumentClient
ということで、 as const と satisfies を導入するとこうなります
import * as AWS from "aws-sdk"
const mockDynamoDBClient = {
update: jest.fn().mockReturnValue({
promise: mockUpdatePromise,
}),
- } as unknown as AWS.DynamoDB.DocumentClient
+ } as const satisfies Partial<AWS.DynamoDB.DocumentClient>
これにより、
- ✅
AWS.DynamoDB.DocumentClientでの型定義に沿った上で (satisfiesの効果) - ✅ 必要最低限のメソッドのみを mock 記述して (
satisfiesとPartialの効果) - ✅
mockDynamoDBClientを使うときはupdateしか参照できない = mock 定義してないメソッドを参照する事故をなくす (as constの効果)
の恩恵を受けます。いいことしかないです
まとめ
プロジェクト内で satisfies がまだ浸透していない場合、PR で使ったコード提出してドヤりましょう。 typescript 4.9 以上です。
他にも「こんなケースで嬉しいよ」があれば記事更新しておきます。
Discussion