TypeScript開発のベストプラクティス:型安全性を高める実践的テクニック
TypeScriptを効果的に活用するためのベストプラクティスと、実際のプロジェクトで役立つ型定義のテクニックを紹介します。
TypeScript 開発のベストプラクティス
TypeScript を使った開発で、型安全性を高め、開発効率を向上させるためのベストプラクティスを解説します。
基本的な型定義
インターフェースの活用
// 良い例:インターフェースを使った型定義
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
updatedAt: Date;
}
interface CreateUserRequest {
name: string;
email: string;
}
// 継承を活用した型の拡張
interface AdminUser extends User {
role: "admin";
permissions: string[];
}
Union Types と Type Guards
type Theme = "light" | "dark" | "auto";
type Status = "loading" | "success" | "error";
// Type Guardの実装
function isError(response: any): response is { error: string } {
return response && typeof response.error === "string";
}
function handleApiResponse(response: unknown) {
if (isError(response)) {
console.error(response.error);
return;
}
// 正常なレスポンスの処理
}
高度な型テクニック
ジェネリクス
// APIレスポンスの汎用型
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// 汎用的なRepository pattern
interface Repository<T, K = number> {
findById(id: K): Promise<T | null>;
findAll(): Promise<T[]>;
create(entity: Omit<T, "id">): Promise<T>;
update(id: K, entity: Partial<T>): Promise<T>;
delete(id: K): Promise<void>;
}
class UserRepository implements Repository<User> {
async findById(id: number): Promise<User | null> {
// 実装
}
async findAll(): Promise<User[]> {
// 実装
}
// その他のメソッド...
}
Utility Types
// 既存の型から新しい型を生成
type CreateUser = Omit<User, "id" | "createdAt" | "updatedAt">;
type UpdateUser = Partial<Pick<User, "name" | "email">>;
type UserKeys = keyof User;
// Conditional Types
type NonNullable<T> = T extends null | undefined ? never : T;
// Mapped Types
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// 使用例
type OptionalEmailUser = Optional<User, "email">;
React + TypeScript のベストプラクティス
コンポーネントの型定義
import React, { ReactNode } from "react";
interface ButtonProps {
variant: "primary" | "secondary" | "danger";
size?: "small" | "medium" | "large";
disabled?: boolean;
children: ReactNode;
onClick?: () => void;
}
const Button: React.FC<ButtonProps> = ({
variant,
size = "medium",
disabled = false,
children,
onClick,
}) => {
return (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
};
// カスタムHooksの型定義
interface UseApiResult<T> {
data: T | null;
loading: boolean;
error: string | null;
refetch: () => void;
}
function useApi<T>(url: string): UseApiResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchData = useCallback(async () => {
try {
setLoading(true);
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err instanceof Error ? err.message : "Unknown error");
} finally {
setLoading(false);
}
}, [url]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
設定とツール
tsconfig.json の推奨設定
{
"compilerOptions": {
"target": "es2020",
"module": "esnext",
"lib": ["dom", "dom.iterable", "es6"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"incremental": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/utils/*": ["./src/utils/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
ESLint と Prettier の設定
// .eslintrc.json
{
"extends": [
"@typescript-eslint/recommended",
"@typescript-eslint/recommended-requiring-type-checking"
],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/no-explicit-any": "error"
}
}
実践的な Tips
1. any を避ける
// 悪い例
function processData(data: any): any {
return data.someProperty;
}
// 良い例
interface DataType {
someProperty: string;
}
function processData(data: DataType): string {
return data.someProperty;
}
2. 型ガードの活用
function isString(value: unknown): value is string {
return typeof value === "string";
}
function processValue(value: unknown) {
if (isString(value)) {
// この時点でvalueはstring型として扱われる
console.log(value.toUpperCase());
}
}
3. 型アサーションは最小限に
// 避けるべき
const data = response as User;
// より安全な方法
function isUser(obj: any): obj is User {
return obj && typeof obj.id === "number" && typeof obj.name === "string";
}
if (isUser(response)) {
// responseはUser型として安全に使用可能
}
まとめ
TypeScript の型システムを効果的に活用することで、バグの早期発見、コードの可読性向上、開発効率の改善が期待できます。
これらのベストプラクティスを日々の開発に取り入れて、より堅牢なアプリケーションを構築しましょう。
最後まで読んでいただきありがとうございました!てばさん(@basabasa8770)でした!