record-types-sync について
record-types-syncは、Record型を使って型と関連する設定やデータ構造を同期させるTypeScriptスキルです。プロパティ追加時に不足項目をコンパイラーが検出し、バリデーター、デフォルト値、ラベルなどの保守に役立ちます。
主な機能
- 01Recordで全プロパティを網羅
- 02並行するデータ構造を同期
- 03コンパイル時に不足項目を検出
- 04バリデーターや既定値、ラベルを設定
活用シーン
- 01全プロパティへの設定を必須化
- 02更新判定をプロパティと同期
- 03完全なバリデーターや既定値を定義
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill record-types-syncインストール後、Claude Code で/record-types-syncを入力して読み込まれたか確認します。
詳細
値の同期を維持するために Record 型を使う
概要
型と、その型のプロパティに関する設定オブジェクトのように、並行するデータ構造の同期を保つ必要がある場合は、Record<keyof T, V> を使って、すべてのプロパティが確実に網羅されるようにします。この手法により、型に新しいプロパティを追加すると、関連するコードも更新するようコンパイルエラーで通知されます。
このパターンは、最適化チェック、プロパティバリデーター、型のすべてのプロパティを列挙または設定する必要があるあらゆるコードで非常に役立ちます。
この手法を使う場面
- プロパティの設定を同期させる必要がある
- 新しいプロパティを追加すると、別の箇所も更新する必要がある
- shouldComponentUpdate 型の最適化を実装する
- プロパティバリデーターや変換処理を作成する
- 並行するデータ構造を保守する
絶対に守るルール
Record<keyof T, V> を使って、関連する設定オブジェクトで T のすべてのプロパティが網羅されていることを保証します。
検出方法
次のような保守上の危険に注意してください。
// RED FLAGS - Manual synchronization
interface Props {
data: Data;
onClick: () => void;
}
function shouldUpdate(old: Props, new: Props) {
// Manual checks - easy to miss new properties
return old.data !== new.data; // Forgot onClick!
}
// Comments that won't be read:
// Note: if you add a property here, update shouldUpdate!
問題点
interface ScatterProps {
xs: number[];
ys: number[];
xRange: [number, number];
yRange: [number, number];
color: string;
onClick?: () => void;
}
// "Fail open" - might redraw too often
function shouldUpdate(old: ScatterProps, new: ScatterProps) {
for (const k in old) {
if (old[k] !== new[k]) {
if (k !== 'onClick') return true; // Forgot new event handlers!
}
}
return false;
}
// "Fail closed" - might miss necessary redraws
function shouldUpdate(old: ScatterProps, new: ScatterProps) {
return (
old.xs !== new.xs ||
old.ys !== new.ys ||
// Forgot xRange, yRange, color!
// Also forgot to exclude onClick
);
}
解決策:Record 型
const REQUIRES_UPDATE: Record<keyof ScatterProps, boolean> = {
xs: true,
ys: true,
xRange: true,
yRange: true,
color: true,
onClick: false, // false = change doesn't require redraw
};
function shouldUpdate(old: ScatterProps, new: ScatterProps) {
for (const k in old) {
const key = k as keyof ScatterProps;
if (old[key] !== new[key] && REQUIRES_UPDATE[key]) {
return true;
}
}
return false;
}
これにより、新しいプロパティを追加する際に、次のことを決める必要があります。
interface ScatterProps {
// ... existing properties
onDoubleClick?: () => void; // New property added
}
// COMPILE ERROR: Property 'onDoubleClick' is missing
const REQUIRES_UPDATE: Record<keyof ScatterProps, boolean> = {
// ... existing entries
// Error reminds you to add: onDoubleClick: ???
};
プロパティバリデーター
interface UserInput {
name: string;
email: string;
age: number;
}
// Enforce that every field has a validator
const validators: Record<keyof UserInput, (value: unknown) => boolean> = {
name: (v) => typeof v === 'string' && v.length > 0,
email: (v) => typeof v === 'string' && v.includes('@'),
age: (v) => typeof v === 'number' && v >= 0 && v < 150,
};
// Adding a field forces adding a validator
デフォルト値
interface Config {
timeout: number;
retries: number;
debug: boolean;
}
// Enforce defaults for all properties
const defaults: Record<keyof Config, Config[keyof Config]> = {
timeout: 5000,
retries: 3,
debug: false,
};
function loadConfig(partial: Partial<Config>): Config {
return { ...defaults, ...partial };
}
プロパティラベル
interface FormData {
firstName: string;
lastName: string;
email: string;
}
// Enforce labels for all fields
const labels: Record<keyof FormData, string> = {
firstName: 'First Name',
lastName: 'Last Name',
email: 'Email Address',
};
// Use in UI
Object.entries(formData).map(([key, value]) => (
<label>{labels[key as keyof FormData]}</label>
));
圧力に負けないための手順
並行する構造を保守する場合は、次の手順に従います。
- 結び付きを特定する:どの構造の同期を保つ必要がありますか?
- Record<keyof T, V> を使う:完全に網羅されていることを保証します
- 意味のある値の型を選ぶ:boolean、関数、文字列など
- パターンを文書化する:Record を使う理由を説明します
- オプショナルプロパティを扱う:必要に応じて
keyof Required<T>を使います
危険信号
| アンチパターン | 問題 | 解決策 |
|---|---|---|
| 「Y が変更されたら X を更新」と書かれたコメント | 強制されません | Record 型 |
| プロパティを手動で列挙する | プロパティを見落としやすい | keyof を使った Record |
| 設定項目がオプショナル | 必須項目を忘れる可能性がある | すべて必須にする |
よくある言い訳
「更新するのを忘れない」
現実:忘れます。同僚も忘れます。コンパイラーが Record 型で強制します。
「小さな設定にすぎない」
現実:小さな設定も大きくなります。Record 型なら、保守の負担を増やさずに拡張できます。
「設定が不要なプロパティもある」
現実:設定し忘れるより、null/false/空の値を明示的に設定する方が適切です。
クイックリファレンス
| 用途 | Record 型 | 値の例 |
|---|---|---|
| 最適化フラグ | Record<keyof T, boolean> | true/false |
| バリデーター | Record<keyof T, ValidatorFn> | 検証関数 |
| デフォルト値 | Record<keyof T, T[keyof T]> | デフォルト値 |
| ラベル | Record<keyof T, string> | 表示名 |
| 変換処理 | Record<keyof T, TransformFn> | 変換関数 |
要点
Record<keyof T, V> を使って、並行するデータ構造と型の同期を保ちます。プロパティを追加すると、コンパイラーが関連するコードの更新を通知します。
参考資料
- Dan Vanderkam 著『Effective TypeScript』第 2 版
- 項目 61:値の同期を維持するために Record 型を使う