frontend/typescript-rules
React/TypeScriptの型安全性、コンポーネント設計、状態管理ルールを適用。Reactコンポーネント実装時に使用。
$ Instalar
git clone https://github.com/shinpr/ai-coding-project-boilerplate /tmp/ai-coding-project-boilerplate && cp -r /tmp/ai-coding-project-boilerplate/.claude/skills-ja/frontend/typescript-rules ~/.claude/skills/ai-coding-project-boilerplate// tip: Run this command in your terminal to install the skill
SKILL.md
name: frontend/typescript-rules description: React/TypeScriptの型安全性、コンポーネント設計、状態管理ルールを適用。Reactコンポーネント実装時に使用。
TypeScript 開発ルール(フロントエンド)
型システム
型安全性の原則
- strictモード必須: tsconfig.jsonでstrict: trueを設定
- any型使用禁止: コードベースでany型を使用しない
- as使用最小化: 型キャストはやむを得ない場合のみ(理由をコメント)
- unknown優先: any型が必要な場合はunknown + 型ガード
// 良い: 型ガード付きのunknown
function processData(data: unknown): User {
if (!isUser(data)) throw new Error('Invalid user data')
return data
}
// 悪い: any型の使用
function processData(data: any): User {
return data as User
}
型定義のベストプラクティス
オブジェクト型
- interface優先: 拡張可能なオブジェクト型にはinterfaceを使用
- typeはunion/intersection用: 複合型やユーティリティ型に使用
- readonlyの活用: 不変なプロパティにはreadonlyを明示
// 良い: 明確な型定義
interface User {
readonly id: string
name: string
email: string
}
type UserWithRole = User & { role: 'admin' | 'user' }
関数型
- 戻り値型を明示: 複雑なロジックを持つ関数
- ジェネリクスの活用: 再利用可能な型安全な関数
// 良い: 戻り値型を明示
function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price, 0)
}
// 良い: ジェネリクスの活用
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
// implementation
}
Reactコンポーネント設計
Function Components必須
// 良い: Function Component
const UserCard: React.FC<UserCardProps> = ({ user, onSelect }) => {
return (
<div onClick={() => onSelect(user.id)}>
{user.name}
</div>
)
}
// 悪い: Class Component(非推奨)
class UserCard extends React.Component<UserCardProps> { }
Props型定義
interface ButtonProps {
label: string
onClick: () => void
variant?: 'primary' | 'secondary'
disabled?: boolean
}
const Button: React.FC<ButtonProps> = ({
label,
onClick,
variant = 'primary',
disabled = false
}) => {
// implementation
}
Children Props
interface LayoutProps {
children: React.ReactNode
sidebar?: React.ReactNode
}
const Layout: React.FC<LayoutProps> = ({ children, sidebar }) => (
<div>
<main>{children}</main>
{sidebar && <aside>{sidebar}</aside>}
</div>
)
状態管理
useState型定義
// 良い: 明示的な型
const [user, setUser] = useState<User | null>(null)
const [items, setItems] = useState<Item[]>([])
// 良い: 初期値から推論可能な場合
const [count, setCount] = useState(0)
useReducerの型安全性
type Action =
| { type: 'SET_USER'; payload: User }
| { type: 'CLEAR_USER' }
| { type: 'SET_ERROR'; payload: string }
interface State {
user: User | null
error: string | null
}
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload, error: null }
case 'CLEAR_USER':
return { ...state, user: null }
case 'SET_ERROR':
return { ...state, error: action.payload }
}
}
Custom Hooks
interface UseUserReturn {
user: User | null
loading: boolean
error: Error | null
refetch: () => Promise<void>
}
function useUser(userId: string): UseUserReturn {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
const refetch = useCallback(async () => {
setLoading(true)
try {
const data = await fetchUser(userId)
setUser(data)
} catch (e) {
setError(e instanceof Error ? e : new Error('Unknown error'))
} finally {
setLoading(false)
}
}, [userId])
useEffect(() => { refetch() }, [refetch])
return { user, loading, error, refetch }
}
エラーハンドリング
Error Boundary
interface ErrorBoundaryProps {
children: React.ReactNode
fallback: React.ReactNode
}
interface ErrorBoundaryState {
hasError: boolean
}
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
state = { hasError: false }
static getDerivedStateFromError(): ErrorBoundaryState {
return { hasError: true }
}
render() {
if (this.state.hasError) return this.props.fallback
return this.props.children
}
}
APIエラーハンドリング
interface ApiError {
code: string
message: string
details?: Record<string, string>
}
function isApiError(error: unknown): error is ApiError {
return (
typeof error === 'object' &&
error !== null &&
'code' in error &&
'message' in error
)
}
async function fetchWithErrorHandling<T>(url: string): Promise<T> {
const response = await fetch(url)
if (!response.ok) {
const error: unknown = await response.json()
if (isApiError(error)) {
throw new ApiError(error.code, error.message)
}
throw new Error('Unknown API error')
}
return response.json() as Promise<T>
}
イベントハンドリング
イベント型
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
// handle click
}
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
// handle change
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
// handle submit
}
コーディング規約
命名規則
- コンポーネント: PascalCase(例:
UserProfile) - フック: camelCase + use接頭辞(例:
useUserData) - 型/インターフェース: PascalCase(例:
UserProps) - 定数: SCREAMING_SNAKE_CASE(例:
MAX_RETRY_COUNT) - ファイル名: コンポーネントはPascalCase、その他はcamelCase
インポート順序
- React関連
- 外部ライブラリ
- 内部モジュール(絶対パス)
- 内部モジュール(相対パス)
- 型のみのインポート
- スタイル/アセット
import { useState, useEffect } from 'react'
import { useQuery } from '@tanstack/react-query'
import { api } from '@/lib/api'
import { formatDate } from '../utils'
import type { User } from '@/types'
import styles from './Component.module.css'
アンチパターン
避けるべきパターン
// 悪い: Propsのスプレッド展開
const Button = (props: ButtonProps) => <button {...props} />
// 良い: 明示的なPropsの受け渡し
const Button = ({ label, onClick, disabled }: ButtonProps) => (
<button onClick={onClick} disabled={disabled}>{label}</button>
)
// 悪い: インラインでの複雑なロジック
{items.filter(i => i.active).map(i => <Item key={i.id} {...i} />)}
// 良い: 事前に変数として抽出
const activeItems = items.filter(item => item.active)
{activeItems.map(item => <Item key={item.id} item={item} />)}
Repository

shinpr
Author
shinpr/ai-coding-project-boilerplate/.claude/skills-ja/frontend/typescript-rules
147
Stars
15
Forks
Updated6d ago
Added1w ago