frontend-development について
React、Vue 3、Svelte 5、Angularを扱うフロントエンド開発ガイドのスキルです。各フレームワークの指針に沿って、パターン選定やコンポーネント、ルーティング、データ取得、型付きのアプリ構成を支援します。
主な機能
- 014つのフロントエンドフレームワークの比較
- 02コンポーネントと機能構成の指針
- 03ルーティングとデータ取得の解説
- 04TypeScriptとパフォーマンスの実践
- 05フレームワーク別パターンの提示
活用シーン
- 01新しいフロントエンドプロジェクトの計画
- 02コンポーネントやアプリ機能の構築
- 03データ取得やルーティングの改善
- 04フレームワーク移行の検討
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samhvw8/ dotfiles --skill frontend-developmentインストール後、Claude Code で/frontend-developmentを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
resources/common-patterns.mdcomplete-examples.mdcomponent-patterns.mddata-fetching.mdfile-organization.mdloading-and-error-states.mdperformance.mdrouting-guide.mdstyling-guide.mdtypescript-standards.md詳細
フロントエンド開発ガイドライン
目的
React、Vue 3、Svelte 5、Angular を対象とした、モダンなフロントエンド開発の包括的なガイドです。各フレームワーク固有のパターン、共通するアーキテクチャの原則、フレームワークを問わないベストプラクティスを取り上げます。
このスキルを使用する場面
- React、Vue、Svelte、Angular でコンポーネントやページを作成する
- フレームワーク固有のパターンを使って新機能を構築する
- 状態管理を実装する(Pinia、Zustand、ストア、シグナル)
- ルーティングを設定する(TanStack Router、Vue Router、SvelteKit、Angular Router)
- データ取得パターンを実装する(TanStack Query、コンポーザブル、SvelteKit のロード関数)
- フレームワーク全体のパフォーマンスを最適化する
- コンポーネントの構成と再利用性を高める
- TypeScript の統合とベストプラクティスに対応する
- プロジェクトに適したフレームワークを選ぶ
- フレームワーク間を移行する
フレームワーク選定ガイド
プロジェクトの要件に基づいてフレームワークを選択してください。
| フレームワーク | 最適な用途 | 学習曲線 | パフォーマンス | エコシステム |
|---|---|---|---|---|
| React | 大規模アプリ、強い型付け、エンタープライズ | 中程度 | 良好 | 最大 |
| Vue 3 | 段階的な導入、習得しやすさ | 低い | 優秀 | 拡大中 |
| Svelte 5 | 小〜中規模アプリ、ネイティブな使用感 | 低い | 最高 | 小規模 |
| Angular | エンタープライズ、多機能 | 高い | 良好 | 充実 |
簡単な判断基準:
- 既存のコードベースがあるか? 既存のものを使うか、移行を計画します
- チームに経験があるか? 既存の知識を活用します
- プロジェクトの規模は? 大規模 → React/Angular、中規模 → Vue/React、小規模 → Svelte/Vue
- パフォーマンスが重要か? Svelte > Vue > React ≈ Angular
- TypeScript が必要か? すべて十分に対応しています(Angular は統合が最も優れています)
- エコシステムの要件は? React > Vue > Angular > Svelte
各フレームワークの詳しいパターンについては、以下の各セクションを参照してください。
フレームワークの簡易比較
| 機能 | React | Vue 3 | Svelte 5 | Angular |
|---|---|---|---|---|
| リアクティビティ | フック(useState) | ref/reactive | ルーン($state) | シグナル |
| コンポーネント | JSX/TSX | SFC (.vue) | SFC (.svelte) | デコレーター/クラス |
| 状態管理 | Zustand/Context | Pinia | ストア | サービス |
| ルーティング | React Router/TanStack | Vue Router | SvelteKit | Angular Router |
| データ取得 | TanStack Query | Composables/VueQuery | ロード関数 | HttpClient/RxJS |
| スタイリング | CSS-in-JS/Modules | スコープ付き CSS | スコープ付き CSS | コンポーネントスタイル |
| フルスタック | Next.js | Nuxt | SvelteKit | Universal/SSR |
| バンドルサイズ | ~40KB | ~32KB | ~3KB | ~60KB |
| コンパイラー | ランタイム | ランタイム | コンパイル時 | AOT コンパイラー |
移動先:
- React 開発 - フック、Suspense、TanStack エコシステム
- Vue 3 開発 - Composition API、Pinia、Nuxt
- Svelte 5 開発 - ルーン、SvelteKit、最小限のランタイム
- Angular 開発 - シグナル、スタンドアロンコンポーネント、RxJS
- フレームワーク共通のパターン - 汎用的な概念
React 開発
新規コンポーネントのチェックリスト
コンポーネントを作成しますか?以下のチェックリストに従ってください。
- TypeScript で
React.FC<Props>パターンを使用する - 大きなコンポーネントは遅延読み込みする:
React.lazy(() => import()) - ローディング状態には
<SuspenseLoader>でラップする - データ取得には
useSuspenseQueryを使用する - インポートエイリアス:
@/、~types、~components、~features - スタイル:100 行未満ならインライン、100 行を超えるなら別ファイルにする
- 子コンポーネントに渡すイベントハンドラーには
useCallbackを使用する - デフォルトエクスポートは末尾に置く
- ローディングスピナーを伴う早期リターンは使用しない
- ユーザーへの通知には
useMuiSnackbarを使用する
新機能チェックリスト
機能を作成しますか? 次の構成を用意してください。
-
features/{feature-name}/ディレクトリを作成する - サブディレクトリを作成する:
api/、components/、hooks/、helpers/、types/ - API サービスファイルを作成する:
api/{feature}Api.ts -
types/に TypeScript の型を設定する -
routes/{feature-name}/index.tsxにルートを作成する - 機能コンポーネントを遅延読み込みする
- Suspense 境界を使用する
- 機能の
index.tsから公開 API をエクスポートする
インポートエイリアス早見表
| エイリアス | 解決先 | 例 |
|---|---|---|
@/ | src/ | import { apiClient } from '@/lib/apiClient' |
~types | src/types | import type { User } from '~types/user' |
~components | src/components | import { SuspenseLoader } from '~components/SuspenseLoader' |
~features | src/features | import { authApi } from '~features/auth' |
定義場所:vite.config.ts の 180~185 行
よく使うインポート早見表
// React & Lazy Loading
import React, { useState, useCallback, useMemo } from 'react';
const Heavy = React.lazy(() => import('./Heavy'));
// MUI Components
import { Box, Paper, Typography, Button, Grid } from '@mui/material';
import type { SxProps, Theme } from '@mui/material';
// TanStack Query (Suspense)
import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query';
// TanStack Router
import { createFileRoute } from '@tanstack/react-router';
// Project Components
import { SuspenseLoader } from '~components/SuspenseLoader';
// Hooks
import { useAuth } from '@/hooks/useAuth';
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
// Types
import type { Post } from '~types/post';
トピック別ガイド
🎨 コンポーネントのパターン
最新の React コンポーネントでは、次を使用します。
- 型安全性のための
React.FC<Props> - コード分割のための
React.lazy() - ロード状態のための
SuspenseLoader - 名前付き const + デフォルトエクスポートのパターン
主な概念:
- 重いコンポーネント(DataGrid、グラフ、エディター)を遅延読み込みする
- 遅延読み込みするコンポーネントは、必ず Suspense でラップする
SuspenseLoaderコンポーネント(フェードアニメーション付き)を使用する- コンポーネントの構成:Props → Hooks → Handlers → Render → Export
📖 完全ガイド:resources/component-patterns.md
📊 データ取得
基本パターン:useSuspenseQuery
- Suspense 境界と併用する
- キャッシュ優先の戦略(API の前にグリッドキャッシュを確認)
isLoadingのチェックを置き換える- ジェネリクスによる型安全性
API サービスレイヤー:
features/{feature}/api/{feature}Api.tsを作成する- axios インスタンスの
apiClientを使用する - 機能ごとにメソッドを集約する
- ルート形式:
/form/route(/api/form/routeではありません)
📖 完全ガイド:resources/data-fetching.md
📁 ファイル構成
features/ と components/ の違い:
features/:ドメイン固有(投稿、コメント、認証)components/:真に再利用可能なもの(SuspenseLoader、CustomAppBar)
機能のサブディレクトリ:
features/
my-feature/
api/ # API service layer
components/ # Feature components
hooks/ # Custom hooks
helpers/ # Utility functions
types/ # TypeScript types
📖 完全ガイド:resources/file-organization.md
🎨 スタイル設定
インラインと分離:
- 100 行未満:インラインの
const styles: Record<string, SxProps<Theme>> - 100 行超:独立した
.styles.tsファイル
基本的な方法:
- MUI コンポーネントには
sxプロパティを使用する SxProps<Theme>による型安全性- テーマへのアクセス:
(theme) => theme.palette.primary.main
MUI v7 Grid:
<Grid size={{ xs: 12, md: 6 }}> // ✅ v7 syntax
<Grid xs={12} md={6}> // ❌ Old syntax
📖 完全ガイド:resources/styling-guide.md
🛣️ ルーティング
TanStack Router - フォルダーベース:
- ディレクトリ:
routes/my-route/index.tsx - コンポーネントを遅延読み込みする
createFileRouteを使用する- loader にパンくずリストのデータを設定する
例:
import { createFileRoute } from '@tanstack/react-router';
import { lazy } from 'react';
const MyPage = lazy(() => import('@/features/my-feature/components/MyPage'));
export const Route = createFileRoute('/my-route/')({
component: MyPage,
loader: () => ({ crumb: 'My Route' }),
});
📖 完全ガイド:resources/routing-guide.md
⏳ ロード状態とエラー状態
重要なルール:早期リターンをしない
// ❌ NEVER - Causes layout shift
if (isLoading) {
return <LoadingSpinner />;
}
// ✅ ALWAYS - Consistent layout
<SuspenseLoader>
<Content />
</SuspenseLoader>
理由: Cumulative Layout Shift(CLS)を防ぎ、UX を改善できます。
エラー処理:
- ユーザーへのフィードバックには
useMuiSnackbarを使用する react-toastifyは絶対に使用しない- TanStack Query の
onErrorコールバックを使用する
📖 完全ガイド:resources/loading-and-error-states.md
⚡ パフォーマンス
最適化パターン:
useMemo:負荷の高い計算(filter、sort、map)useCallback:子に渡すイベントハンドラーReact.memo:負荷の高いコンポーネント- デバウンス検索(300~500ms)
- メモリーリークの防止(useEffect 内でクリーンアップ)
📖 完全ガイド:resources/performance.md
📘 TypeScript
標準:
- strict モードを使用し、
any型は禁止します - 関数には明示的な戻り値の型を指定します
- 型のインポート:
import type { User } from '~types/user' - コンポーネントの props インターフェースには JSDoc を付けます
📖 完全ガイド:resources/typescript-standards.md
🔧 共通パターン
対象トピック:
- Zod バリデーションを使った React Hook Form
- DataGrid ラッパーの契約
- ダイアログコンポーネントの標準
- 現在のユーザーを取得する
useAuthフック - キャッシュ無効化を伴うミューテーションパターン
📖 完全ガイド:resources/common-patterns.md
📚 完全な例
動作する完全な例:
- すべてのパターンを取り入れた最新のコンポーネント
- 完全な機能の構成
- API サービスレイヤー
- 遅延読み込みを使用するルート
- Suspense + useSuspenseQuery
- バリデーション付きフォーム
📖 完全ガイド:resources/complete-examples.md
React 専用ナビゲーションガイド
| 必要な作業 | 参照するリソース |
|---|---|
| React コンポーネントを作成する | component-patterns.md |
| TanStack Query でデータを取得する | data-fetching.md |
| ファイル/フォルダーを整理する | file-organization.md |
| MUI v7 でスタイルを設定する | styling-guide.md |
| TanStack Router を設定する | routing-guide.md |
| 読み込み中やエラーを処理する | loading-and-error-states.md |
| React のパフォーマンスを最適化する | performance.md |
| TypeScript の型 | typescript-standards.md |
| フォーム/認証/DataGrid | common-patterns.md |
| React の完全な例を見る | complete-examples.md |
注: 上記のリソースは React 専用です。Vue/Svelte/Angular については、このドキュメントの各フレームワークのセクションを参照してください。
基本原則(すべてのフレームワーク)
- 重いコンポーネントを遅延読み込みする:ルート、データグリッド、チャート、エディター、モーダル
- 一貫した読み込み状態:適切な読み込み UI を用意し、レイアウトシフトを避けます
- 型安全性:すべてのコンポーネントと API に TypeScript を使用します
- コンポーネントの合成:責務が明確な、小さく焦点を絞ったコンポーネントを作成します
- 単方向のデータフロー:props は下へ、イベントは上へ
- パフォーマンスを最優先する:コストの高い計算をメモ化し、リストには仮想スクロールを使用します
- 機能を整理する:関連するコード(コンポーネント、状態、型、ユーティリティ)をまとめます
- フレームワークの慣用パターン:フレームワーク固有のパターン(フック、コンポーザブル、ルーン、サービス)を使用します
React 固有の原則
- useSuspenseQuery:新しいコードでの主要なデータ取得パターン
- 早期リターンをしない:レイアウトシフトを防ぎます(Suspense 境界を使用します)
- インポートエイリアス:@/、~types、~components、~features を使用します
- サイズに応じたスタイル:インラインは <100、別ファイルは >100
Vue 固有の原則
- Composition API:再利用性を高めるため、Options API より優先します
- Script Setup:より簡潔な構文と高いパフォーマンスのために使用します
- コンポーザブル:React フックのように、再利用可能なロジックを切り出します
- 状態管理には Pinia:TypeScript に対応した最新の Vuex 代替手段です
Svelte 固有の原則
- リアクティビティにはルーン:$state、$derived、$effect を使用します(Svelte 5+)
- アプリには SvelteKit:ファイルベースのルーティングとサーバー関数を活用します
- ストアを最小限にする:ルーンにより、コンポーネントでストアを使う必要性が減ります
- プログレッシブエンハンスメント:JavaScript なしで構築し、JavaScript で機能を強化します
Angular 固有の原則
- スタンドアロンコンポーネント:v17 以降のデフォルトです(NgModules は避けます)
- 状態管理にはシグナル:シンプルなリアクティビティでは RxJS より優先します
- OnPush 検出:変更検出を最適化します
- 依存性注入:最新の DI には inject() を使用します
クイックリファレンス:ファイル構成
src/
features/
my-feature/
api/
myFeatureApi.ts # API service
components/
MyFeature.tsx # Main component
SubComponent.tsx # Related components
hooks/
useMyFeature.ts # Custom hooks
useSuspenseMyFeature.ts # Suspense hooks
helpers/
myFeatureHelpers.ts # Utilities
types/
index.ts # TypeScript types
index.ts # Public exports
components/
SuspenseLoader/
SuspenseLoader.tsx # Reusable loader
CustomAppBar/
CustomAppBar.tsx # Reusable app bar
routes/
my-route/
index.tsx # Route component
create/
index.tsx # Nested route
モダンなコンポーネントテンプレート(すぐにコピー)
import React, { useState, useCallback } from 'react';
import { Box, Paper } from '@mui/material';
import { useSuspenseQuery } from '@tanstack/react-query';
import { featureApi } from '../api/featureApi';
import type { FeatureData } from '~types/feature';
interface MyComponentProps {
id: number;
onAction?: () => void;
}
export const MyComponent: React.FC<MyComponentProps> = ({ id, onAction }) => {
const [state, setState] = useState<string>('');
const { data } = useSuspenseQuery({
queryKey: ['feature', id],
queryFn: () => featureApi.getFeature(id),
});
const handleAction = useCallback(() => {
setState('updated');
onAction?.();
}, [onAction]);
return (
<Box sx={{ p: 2 }}>
<Paper sx={{ p: 3 }}>
{/* Content */}
</Paper>
</Box>
);
};
export default MyComponent;
完全なサンプルについては、resources/complete-examples.md をご覧ください。
Vue 3 の開発
コンポーネントのパターン
Composition API - <script setup> を使ったモダンな Vue 3 のアプローチ:
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import type { User } from '@/types/user'
interface Props {
userId: number
}
const props = defineProps<Props>()
const emit = defineEmits<{
update: [user: User]
}>()
const user = ref<User | null>(null)
const isLoading = ref(true)
const displayName = computed(() => user.value?.name ?? 'Unknown')
onMounted(async () => {
user.value = await fetchUser(props.userId)
isLoading.value = false
})
function handleUpdate() {
if (user.value) emit('update', user.value)
}
</script>
<template>
<div class="user-profile">
<div v-if="isLoading">Loading...</div>
<div v-else>
<h2>{{ displayName }}</h2>
<button @click="handleUpdate">Update</button>
</div>
</div>
</template>
<style scoped>
.user-profile {
padding: 1rem;
}
</style>
主なパターン:
<script setup>- 簡潔な Composition API 構文defineProps<T>()- ジェネリクスによる型安全な propsdefineEmits<T>()- 型安全なイベントref()、reactive()- リアクティビティのプリミティブcomputed()- 派生状態watch()、watchEffect()- 副作用onMounted、onUnmounted- ライフサイクルフック
状態管理(Pinia)
// stores/userStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// State
const users = ref<User[]>([])
const currentUser = ref<User | null>(null)
// Getters
const userCount = computed(() => users.value.length)
const isAuthenticated = computed(() => currentUser.value !== null)
// Actions
async function fetchUsers() {
users.value = await api.getUsers()
}
function setCurrentUser(user: User) {
currentUser.value = user
}
return { users, currentUser, userCount, isAuthenticated, fetchUsers, setCurrentUser }
})
// Component usage
<script setup lang="ts">
import { useUserStore } from '@/stores/userStore'
const userStore = useUserStore()
</script>
ルーティング(Vue Router)
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: () => import('@/views/HomeView.vue')
},
{
path: '/users/:id',
component: () => import('@/views/UserView.vue'),
props: true
}
]
})
// Component with route params
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const userId = route.params.id
function goBack() {
router.push('/')
}
</script>
データ取得のパターン
コンポーザブル - 再利用可能なロジック:
// composables/useUser.ts
import { ref, type Ref } from 'vue'
export function useUser(id: Ref<number>) {
const user = ref<User | null>(null)
const loading = ref(false)
const error = ref<Error | null>(null)
async function fetchUser() {
loading.value = true
try {
user.value = await api.getUser(id.value)
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
watchEffect(() => {
fetchUser()
})
return { user, loading, error, refetch: fetchUser }
}
// Usage in component
<script setup lang="ts">
const props = defineProps<{ userId: number }>()
const { user, loading, error } = useUser(toRef(props, 'userId'))
</script>
Nuxt.js のサーバーサイド:
<script setup lang="ts">
// Nuxt auto-imports composables
const { data: user, pending, error } = await useFetch(`/api/users/${route.params.id}`)
</script>
Vue 3 のベストプラクティス
- Options API より Composition API - TypeScript のサポートと再利用性が向上します
- Script setup - 定型コードを減らし、自動登録を実現します
- Vuex より Pinia - API がシンプルで、TypeScript のサポートに優れ、Composition API と互換性があります
- コンポーザブル - 再利用可能なロジックを抽出します(React のフックに相当)
- v-memo - コストの高いレンダリングを最適化します(React.memo に相当)
- defineOptions - script setup でコンポーネントのオプションを設定します
- 浅いリアクティビティ - 大きなオブジェクトには
shallowRef()を使います
Svelte 5 の開発
Runes ベースのリアクティビティ
Svelte 5 Runes - 新しいリアクティビティシステム:
<script lang="ts">
import type { User } from '$lib/types/user'
interface Props {
userId: number
onUpdate?: (user: User) => void
}
let { userId, onUpdate }: Props = $props()
// Reactive state with $state
let user = $state<User | null>(null)
let isLoading = $state(true)
// Derived state with $derived
let displayName = $derived(user?.name ?? 'Unknown')
let userAge = $derived.by(() => {
if (!user?.birthDate) return null
return calculateAge(user.birthDate)
})
// Effects with $effect
$effect(() => {
// Runs when userId changes
loadUser(userId)
})
async function loadUser(id: number) {
isLoading = true
user = await fetchUser(id)
isLoading = false
}
function handleUpdate() {
if (user) onUpdate?.(user)
}
</script>
{#if isLoading}
<div>Loading...</div>
{:else if user}
<div class="user-profile">
<h2>{displayName}</h2>
{#if userAge}
<p>Age: {userAge}</p>
{/if}
<button onclick={handleUpdate}>Update</button>
</div>
{/if}
<style>
.user-profile {
padding: 1rem;
}
</style>
Svelte 5 Runes:
$state()- リアクティブな状態(リアクティビティ用のletを置き換えます)$derived- 計算値($:を置き換えます)$derived.by()- 複雑な派生状態$effect()- 副作用($:文を置き換えます)$props()- 分割代入によるコンポーネントの props$bindable()- props の双方向バインディング$inspect()- リアクティブな値のデバッグ
状態管理(Svelte Stores)
// stores/user.ts
import { writable, derived, readonly } from 'svelte/store'
function createUserStore() {
const { subscribe, set, update } = writable<User[]>([])
return {
subscribe,
setUsers: (users: User[]) => set(users),
addUser: (user: User) => update(users => [...users, user]),
removeUser: (id: number) => update(users => users.filter(u => u.id !== id)),
reset: () => set([])
}
}
export const users = createUserStore()
export const userCount = derived(users, $users => $users.length)
// Component usage (Svelte 4 style)
<script>
import { users } from '$lib/stores/user'
</script>
<p>Total users: {$users.length}</p>
// Or with runes (Svelte 5)
<script>
import { users } from '$lib/stores/user'
let currentUsers = $state($users)
</script>
SvelteKit のルーティングとデータ読み込み
// src/routes/users/[id]/+page.ts
import type { PageLoad } from './$types'
export const load: PageLoad = async ({ params, fetch }) => {
const user = await fetch(`/api/users/${params.id}`).then(r => r.json())
return {
user
}
}
// src/routes/users/[id]/+page.svelte
<script lang="ts">
import type { PageData } from './$types'
let { data }: { data: PageData } = $props()
let { user } = $derived(data)
</script>
<h1>{user.name}</h1>
SvelteKit のパターン:
+page.svelte- ページコンポーネント+page.ts- ページデータの読み込み(サーバーとクライアントの両方で実行)+page.server.ts- サーバー専用のロード関数+layout.svelte- 共有レイアウト+server.ts- API エンドポイント- フォームアクション - プログレッシブエンハンスメント
Svelte 5 のベストプラクティス
- Runes を使う - モダンなリアクティビティ(Svelte 5 以降)
- 明示的なリアクティビティ - 暗黙的な
letの代わりに$state()を使います - $: より $derived** - 意図が明確になり、最適化も向上します
- コンポーネントの合成 - スロットとスニペットを使います
- アプリには SvelteKit - ルーティングと SSR を備えたフルスタックフレームワークです
- ストアを最小限にする - Runes によりコンポーネント内でストアを使う必要性が減ります
- プログレッシブエンハンスメント - より良い UX のためにフォームアクションを使います
Angular の開発
スタンドアロンコンポーネント(モダン)
// user-profile.component.ts
import { Component, Input, Output, EventEmitter, signal, computed } from '@angular/core'
import { CommonModule } from '@angular/common'
import type { User } from '@/types/user'
@Component({
selector: 'app-user-profile',
standalone: true,
imports: [CommonModule],
template: `
<div class="user-profile" *ngIf="!isLoading(); else loading">
<h2>{{ displayName() }}</h2>
<p>Age: {{ userAge() }}</p>
<button (click)="handleUpdate()">Update</button>
</div>
<ng-template #loading>
<div>Loading...</div>
</ng-template>
`,
styles: [`
.user-profile {
padding: 1rem;
}
`]
})
export class UserProfileComponent {
@Input({ required: true }) userId!: number
@Output() userUpdate = new EventEmitter<User>()
// Signals - reactive primitives
user = signal<User | null>(null)
isLoading = signal(true)
// Computed signals
displayName = computed(() => this.user()?.name ?? 'Unknown')
userAge = computed(() => {
const birthDate = this.user()?.birthDate
return birthDate ? this.calculateAge(birthDate) : null
})
async ngOnInit() {
this.user.set(await this.fetchUser(this.userId))
this.isLoading.set(false)
}
handleUpdate() {
const currentUser = this.user()
if (currentUser) this.userUpdate.emit(currentUser)
}
}
Angular Signals - 新しいリアクティビティシステム(v16 以降):
signal()- 書き込み可能なリアクティブ値computed()- 派生状態effect()- 副作用.set()、.update()- シグナル値を変更します()- シグナル値を読み取ります(関数として呼び出します)
状態管理(サービス + シグナル)
// services/user.service.ts
import { Injectable, signal, computed } from '@angular/core'
import { HttpClient } from '@angular/common/http'
@Injectable({ providedIn: 'root' })
export class UserService {
private users = signal<User[]>([])
private currentUser = signal<User | null>(null)
// Public computed signals
readonly userCount = computed(() => this.users().length)
readonly isAuthenticated = computed(() => this.currentUser() !== null)
constructor(private http: HttpClient) {}
async fetchUsers() {
const users = await this.http.get<User[]>('/api/users').toPromise()
this.users.set(users)
}
setCurrentUser(user: User) {
this.currentUser.set(user)
}
}
// Component usage
export class MyComponent {
constructor(public userService: UserService) {}
// Access in template
// {{ userService.userCount() }}
}
ルーティング(Angular Router)
// app.routes.ts
import { Routes } from '@angular/router'
export const routes: Routes = [
{
path: '',
loadComponent: () => import('./home/home.component').then(m => m.HomeComponent)
},
{
path: 'users/:id',
loadComponent: () => import('./users/user-detail.component').then(m => m.UserDetailComponent)
}
]
// Component with route params
import { Component } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
export class UserDetailComponent {
userId = signal<string>('')
constructor(
private route: ActivatedRoute,
private router: Router
) {
this.userId.set(this.route.snapshot.paramMap.get('id') ?? '')
}
goBack() {
this.router.navigate(['/'])
}
}
データ取得(RxJS + Signals)
import { Component, signal } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { toSignal } from '@angular/core/rxjs-interop'
export class UserListComponent {
private http = inject(HttpClient)
// Convert Observable to Signal
users = toSignal(this.http.get<User[]>('/api/users'), { initialValue: [] })
// Or manual signal management
manualUsers = signal<User[]>([])
async loadUsers() {
const users = await this.http.get<User[]>('/api/users').toPromise()
this.manualUsers.set(users)
}
}
Angular のベストプラクティス
- スタンドアロンコンポーネント - v17 以降のデフォルトです(NgModules は不要)
- RxJS より Signals - 状態管理をシンプルにするリアクティビティ
- toSignal - 必要に応じて Observable を Signal に変換します
- inject() - モダンな依存性注入(コンストラクターは不要)
- 遅延読み込み - ルートには
loadComponentを使います - OnPush 変更検知 - レンダリングを最適化します
- 型付きフォーム - 型安全性のために
FormGroup<T>を使います
フレームワークに依存しないパターン
状態管理の比較
| パターン | React | Vue 3 | Svelte 5 | Angular |
|---|---|---|---|---|
| ローカル状態 | useState | ref() | $state() | signal() |
| 派生状態 | useMemo | computed() | $derived | computed() |
| 副作用 | useEffect | watch/watchEffect | $effect() | effect() |
| グローバル状態 | Zustand/Context | Pinia | ストア | サービス |
| 非同期状態 | TanStack Query | VueQuery/Composables | ストア | RxJS/Signals |
コンポーネントの合成パターン
Props/Events パターン(すべてのフレームワーク):
- 親から子へ props を介してデータを渡します
- 子から親へイベントを送信します
- 単方向のデータフローです
Slots/Children パターン:
// React
<Layout>
<Header />
<Content />
</Layout>
// Vue
<Layout>
<template #header><Header /></template>
<template #content><Content /></template>
</Layout>
// Svelte
<Layout>
<Header slot="header" />
<Content slot="content" />
</Layout>
// Angular
<app-layout>
<app-header header></app-header>
<app-content content></app-content>
</app-layout>
ルーティングパターン
ファイルベースのルーティング:
- Next.js(React)、Nuxt(Vue)、SvelteKit(Svelte)
- フォルダー構成によってルートが定義されます
- 動的セグメントには
[id]を使用します
プログラムによるルーティング:
- React Router、Vue Router、Angular Router
- 設定でルートを定義します
- より柔軟ですが、記述量が増えます
パフォーマンスの最適化
共通の手法:
- コード分割 - ルートや大きなコンポーネントを遅延読み込みします
- メモ化 - コストの高い計算結果をキャッシュします
- 仮想スクロール - 表示範囲内の項目だけを描画します
- デバウンス - コストの高い処理(検索、サイズ変更)を制限します
- 画像の最適化 - 遅延読み込み、レスポンシブ画像、最新のフォーマットを使用します
- バンドル分析 - サイズの大きな依存関係を特定して削除します
フレームワーク固有の手法:
- React:
React.memo、useMemo、useCallback、Suspense - Vue:
v-memo、shallowRef、markRaw、KeepAlive - Svelte: 自動最適化、
$derived、最小限のランタイム - Angular: OnPush、signals、trackBy、純粋パイプ
関連スキル
重要:UI/UX デザイン作業では、専門スキルを呼び出してください:
Skill("ui-ux-pro-max") → UI/UX design, visual hierarchy, color theory, spacing, typography
UI/デザイン:
- ui-ux-pro-max:UI/UX デザインの原則、視覚的階層、色彩理論、スペーシングシステム、タイポグラフィ。デザインに関する判断、UI の美化、色やフォントの選択、レイアウトの設計にはこれを使用してください。
- ui-design-system:コンポーネントデザインシステム、アクセシビリティ、デザイントークン
- ui-styling:Tailwind CSS、すべてのフレームワークに対応したスタイリングパターン
バックエンドとの連携:
- backend-development:フロントエンドで利用する API パターン
- better-auth:認証の実装(すべてのフレームワークで動作します)
フルスタックフレームワーク:
- web-frameworks:Next.js(React)、Nuxt(Vue)、SvelteKit(Svelte)の詳細解説
開発:
- debugging:フレームワークに依存しないデバッグ戦略
- refactoring-expert:コード品質とリファクタリングのパターン
- chrome-devtools:ブラウザーのデバッグ、パフォーマンスのプロファイリング
ui-ux-pro-max を呼び出すタイミング
次の場合は Skill("ui-ux-pro-max") を呼び出してください:
- 新しい UI コンポーネントやページをデザインするとき
- カラーパレット、タイポグラフィ、スペーシングを選ぶとき
- 視覚的階層やレイアウトを改善するとき
- インターフェースをプロフェッショナル/モダン/すっきりとした見た目にするとき
- ユーザーが「見栄えが悪い」「一貫性がない」「ごちゃごちゃしている」「デザインの助けが必要」と述べたとき
- ダッシュボード、管理パネル、SaaS インターフェースを構築するとき
スキルのステータス:段階的な情報開示によるマルチフレームワーク対応