TypeScriptで型安全なコードを書くために意識していること【実体験】

エンジニアの記録
技術メモ・実体験

TypeScriptを使っているのにanyだらけ、型エラーをasで無理やり回避、nullチェックを忘れてランタイムエラー。最初の自分はこんな状態でした。

実際のコードレビューで指摘を受けながら学んだ、「型安全なコードを書くために意識していること」をまとめます。

対象読者

TypeScriptを使い始めたエンジニア、特にReact Nativeで「とりあえず動いてるけど型定義が雑」という人に向けて書きます。

PROFILE 20代ITエンジニア|React Native(Expo)× TypeScriptで開発中|Raw Ambition 運営
キラ
キラ
TypeScriptはJavaScriptに型をつけるだけじゃなくて、「バグを事前に防ぐ設計」を強制してくれるツール。ちゃんと使えば強力。
Contents
  1. Pickで必要なpropsだけに絞る
  2. null/undefinedを安全に扱う(??演算子)
  3. JSON.parseに型アサーションをつける
  4. バリデーション関数をユーティリティとして分離する
  5. まとめ:型は「制約」ではなく「設計図」

① Pickで必要なpropsだけに絞る

コンポーネントのpropsを定義するとき、親コンポーネントの型をそのまま使い回すと、不要なpropsまで受け取れる状態になります。Pickを使って必要なpropsだけに絞ることで、コンポーネントの責務が明確になります。

// ❌ TextInputProps を全部受け取っている // このコンポーネントが本当に必要なのは crmField と field だけなのに const UrlInputFieldCmp: React.FC<TextInputProps> = (props) => { // … }; // ✅ Pick で必要なものだけに絞る // このコンポーネントが受け取れるのは crmField と field だけ const UrlInputFieldCmp: React.FC<Pick<TextInputProps, ‘crmField’ | ‘field’>> = (props) => { const { crmField, field } = props; return ( <TextInputFieldCmp crmField={crmField} field={field} keyboardType=”url” autoCapitalize=”none” autoCorrect={false} textContentType=”URL” /> ); };

? POINT

Pick<T, K>は型Tからキーを選んで新しい型を作るユーティリティ型です。コンポーネントの「使える範囲」を明示的に制限することで、誤った使い方をコンパイル時に防げます。
スカイ
スカイ
「このコンポーネントはこのpropsしか受け取らない」という意図がコードに表れるから、読む側も使う側もわかりやすくなるね。

② null/undefinedを安全に扱う(??演算子)

APIから取得したデータはnullundefinedが含まれることがあります。それをそのままpropsに渡すと警告やランタイムエラーの原因になります。

// ❌ field.value が null/undefined の場合に警告が出る // TextInput の value に null を渡すとコンポーネントが警告を出す <TextInputFieldCmp value={field.value} /> // ✅ ?? 演算子でフォールバックを設定する // null または undefined の場合は空文字列にする <TextInputFieldCmp value={field.value ?? ”} />

? ??(Nullish Coalescing)演算子とは

a ?? bは「aがnullまたはundefinedの場合はbを返す、そうでなければaを返す」という演算子です。||と似ていますが、||は0や空文字列もfalsyとして扱うのに対し、??はnull/undefinedのみを対象にします。
// || との違い const value1 = 0 || ‘default’; // ‘default’(0はfalsyなので) const value2 = 0 ?? ‘default’; // 0(0はnull/undefinedではないので) // フォームの値では ?? の方が正しい // 0 という値を「入力なし」として扱いたくない場合は ?? を使う
キラ
キラ
フォームの入力値に || を使うと「0を入力したのに空扱いになる」というバグが起きる。数値フィールドでは特に注意。

③ JSON.parseに型アサーションをつける

APIからJSON.parseした結果は、TypeScriptではany型になります。そのまま使うと型安全性が失われます。

// ❌ JSON.parseの結果はany型 const parsed = JSON.parse(meetingMinutes.data); // parsed.duration にアクセスできるが型チェックが効かない setDuration(parsed.duration); // ✅ 型アサーションで型を明示する const parsed = JSON.parse( meetingMinutes.data as string // data は string | null なので string に絞る ) as { duration?: number }; // パースした結果の型を明示 // duration は number | undefined として扱われる if (parsed.duration) { setDuration(parsed.duration); // 型チェックが効く }

⚠️ 注意点

asによる型アサーションは「コンパイラへの約束」であり、実行時に型チェックは行われません。パースした結果が本当に期待する型であることを、ロジックで保証する必要があります。型アサーションを乱用すると型安全性が失われるので注意。
スカイ
スカイ
「asで型をつけたから安全」ではなく、「実際にその型になっているか」を確認するのがセット。型アサーションは道具であって保証ではない。

④ バリデーション関数をユーティリティとして分離する

コンポーネントの中にバリデーションロジックを書くと、テストが難しくなります。バリデーション関数をユーティリティとして分離することで、テストしやすく・再利用しやすいコードになります。

// src/util/textInputField.ts // URL のバリデーション:scheme(https:// など)があるか export const isValidUrl = (value: string): boolean => { try { const url = new URL(value); return url.protocol === ‘https:’ || url.protocol === ‘http:’; } catch { return false; } }; // Email のバリデーション:@ を含むか export const isValidEmail = (value: string): boolean => { return value.includes(‘@’) && value.length > 3; }; // Phone のバリデーション:数字・ハイフン・+ のみか export const isValidPhone = (value: string): boolean => { return /^[0-9+\-\s()]*$/.test(value); };
// テストも書きやすくなる // src/util/textInputField.test.ts import { isValidUrl, isValidEmail, isValidPhone } from ‘./textInputField’; describe(‘isValidUrl’, () => { it(‘https:// から始まるURLはtrue’, () => { expect(isValidUrl(‘https://example.com’)).toBe(true); }); it(‘schemeがないURLはfalse’, () => { expect(isValidUrl(‘example.com’)).toBe(false); }); }); describe(‘isValidEmail’, () => { it(‘@を含むメールアドレスはtrue’, () => { expect(isValidEmail(‘test@example.com’)).toBe(true); }); it(‘@を含まない文字列はfalse’, () => { expect(isValidEmail(‘testexample.com’)).toBe(false); }); });

? POINT

ユーティリティ関数として分離すると「純粋関数(同じ入力に対して常に同じ出力を返す関数)」になります。純粋関数はテストが簡単で、副作用がなく、再利用しやすい。TypeScriptとの相性も抜群です。
キラ
キラ
コンポーネントにロジックを詰め込みすぎるとテストが書けなくなる。「ロジックはユーティリティへ、コンポーネントはUIだけ」が理想。

? この記事のまとめ
  • Pickで必要なpropsだけに絞る:コンポーネントの責務が明確になる
  • ??演算子でnull/undefinedを安全に扱う:||との違いに注意
  • JSON.parseには型アサーションをつける:asは保証ではなく約束
  • バリデーション関数はユーティリティとして分離:テストしやすく再利用しやすくなる

TypeScriptの型は「制約」ではなく「設計図」だと思っています。型を丁寧に書くことで、コードの意図が伝わりやすくなり、バグが減り、チーム開発がスムーズになります。

Recommended
ブログを始めるならConoHa WINGが最速
エンジニアの技術メモをブログで発信して収益化。月1,000円以下で始められます。
▶ ConoHa WINGを見てみる
※ アフィリエイトリンクです
Next
次回:「コードレビューをされる側・する側で学んだこと【エンジニア2年目の本音】」
Raw Ambition 始動から12日目

コメント

タイトルとURLをコピーしました