frontend-development by samhvw8/dotfiles
npx skills add https://github.com/samhvw8/dotfiles --skill frontend-development涵盖 React、Vue 3、Svelte 5 和 Angular 的现代前端开发综合指南。包括框架特定模式、通用架构原则和跨框架最佳实践。
根据项目需求选择框架:
| 框架 | 最适合 | 学习曲线 | 性能 | 生态系统 |
|---|---|---|---|---|
| React | 大型应用、强类型、企业级 | 中等 | 良好 | 最大 |
| Vue 3 | 渐进式采用、易于上手 | 低 | 优秀 | 增长中 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| Svelte 5 | 中小型应用、原生体验 | 低 | 最佳 | 较小 |
| Angular | 企业级、功能齐全 | 陡峭 | 良好 | 完整 |
快速决策:
查看下面的框架特定部分以获取详细模式。
| 特性 | React | Vue 3 | Svelte 5 | Angular |
|---|---|---|---|---|
| 响应式 | Hooks (useState) | ref/reactive | Runes ($state) | Signals |
| 组件 | JSX/TSX | SFC (.vue) | SFC (.svelte) | 装饰器/类 |
| 状态管理 | Zustand/Context | Pinia | Stores | Services |
| 路由 | 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.FC<Props> 模式React.lazy(() => import())<SuspenseLoader> 包装以处理加载状态useSuspenseQuery 进行数据获取@/、~types、~components、~featuresuseCallback 处理传递给子组件的事件处理器useMuiSnackbar 进行用户通知创建功能?设置此结构:
features/{feature-name}/ 目录api/、components/、hooks/、helpers/、types/api/{feature}Api.tstypes/ 中设置 TypeScript 类型routes/{feature-name}/index.tsx 中创建路由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 用于加载状态关键概念:
主要模式:useSuspenseQuery
isLoading 检查API 服务层:
features/{feature}/api/{feature}Api.tsapiClient axios 实例/form/route(非 /api/form/route)features/ 与 components/:
features/:领域特定(帖子、评论、认证)components/:真正可重用(SuspenseLoader、CustomAppBar)功能子目录:
features/
my-feature/
api/ # API 服务层
components/ # 功能组件
hooks/ # 自定义钩子
helpers/ # 工具函数
types/ # TypeScript 类型
内联与分离:
const styles: Record<string, SxProps<Theme>>100 行:单独的
.styles.ts文件
主要方法:
sx 属性SxProps<Theme> 实现类型安全(theme) => theme.palette.primary.mainMUI v7 网格:
<Grid size={{ xs: 12, md: 6 }}> // ✅ v7 语法
<Grid xs={12} md={6}> // ❌ 旧语法
TanStack Router - 基于文件夹:
routes/my-route/index.tsxcreateFileRoute示例:
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' }),
});
关键规则:不要提前返回
// ❌ 永远不要 - 导致布局偏移
if (isLoading) {
return <LoadingSpinner />;
}
// ✅ 始终 - 一致的布局
<SuspenseLoader>
<Content />
</SuspenseLoader>
原因: 防止累积布局偏移(CLS),更好的用户体验
错误处理:
useMuiSnackbar 提供用户反馈react-toastifyonError 回调优化模式:
useMemo:昂贵的计算(过滤、排序、映射)useCallback:传递给子组件的事件处理器React.memo:昂贵的组件标准:
any 类型import type { User } from '~types/user'涵盖主题:
useAuth 钩子完整工作示例:
| 需要... | 阅读此资源 |
|---|---|
| 创建 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,请参阅本文档中的框架部分。
src/
features/
my-feature/
api/
myFeatureApi.ts # API 服务
components/
MyFeature.tsx # 主组件
SubComponent.tsx # 相关组件
hooks/
useMyFeature.ts # 自定义钩子
useSuspenseMyFeature.ts # Suspense 钩子
helpers/
myFeatureHelpers.ts # 工具函数
types/
index.ts # TypeScript 类型
index.ts # 公共导出
components/
SuspenseLoader/
SuspenseLoader.tsx # 可重用加载器
CustomAppBar/
CustomAppBar.tsx # 可重用应用栏
routes/
my-route/
index.tsx # 路由组件
create/
index.tsx # 嵌套路由
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
组合式 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> - 简洁的组合式 API 语法defineProps<T>() - 使用泛型的类型安全属性defineEmits<T>() - 类型安全事件ref()、reactive() - 响应式基础computed() - 派生状态watch()、watchEffect() - 副作用onMounted、onUnmounted - 生命周期钩子// 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>
// 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 - 可重用逻辑:
// 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>
shallowRef()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() - 带有解构的组件属性$bindable() - 属性的双向绑定$inspect() - 调试响应式值// 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>
// 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 端点$state() 而不是隐式的 let// 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() }}
}
// 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(['/'])
}
}
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)
}
}
Comprehensive guide for modern frontend development across React, Vue 3, Svelte 5, and Angular. Covers framework-specific patterns, common architectural principles, and cross-framework best practices.
Choose your framework based on project requirements:
| Framework | Best For | Learning Curve | Performance | Ecosystem |
|---|---|---|---|---|
| React | Large apps, strong typing, enterprise | Medium | Good | Largest |
| Vue 3 | Progressive adoption, approachable | Low | Excellent | Growing |
| Svelte 5 | Small/medium apps, native feel | Low | Best | Smaller |
| Angular | Enterprise, full-featured | Steep | Good | Complete |
Quick Decision:
See framework-specific sections below for detailed patterns.
| Feature | React | Vue 3 | Svelte 5 | Angular |
|---|---|---|---|---|
| Reactivity | Hooks (useState) | ref/reactive | Runes ($state) | Signals |
| Components | JSX/TSX | SFC (.vue) | SFC (.svelte) | Decorators/Class |
| State Mgmt | Zustand/Context | Pinia | Stores | Services |
| Routing | React Router/TanStack | Vue Router | SvelteKit | Angular Router |
| Data Fetching | TanStack Query | Composables/VueQuery | Load functions |
Jump to:
Creating a component? Follow this checklist:
React.FC<Props> pattern with TypeScriptReact.lazy(() => import())<SuspenseLoader> for loading statesuseSuspenseQuery for data fetching@/, ~types, ~components, ~featuresuseCallback for event handlers passed to childrenCreating a feature? Set up this structure:
features/{feature-name}/ directoryapi/, components/, hooks/, helpers/, types/api/{feature}Api.tstypes/routes/{feature-name}/index.tsxindex.ts| Alias | Resolves To | Example |
|---|---|---|
@/ | src/ | import { apiClient } from '@/lib/apiClient' |
~types | src/types | import type { User } from '~types/user' |
~components | src/components |
Defined in: vite.config.ts lines 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';
Modern React components use:
React.FC<Props> for type safetyReact.lazy() for code splittingSuspenseLoader for loading statesKey Concepts:
📖 Complete Guide: resources/component-patterns.md
PRIMARY PATTERN: useSuspenseQuery
isLoading checksAPI Service Layer:
features/{feature}/api/{feature}Api.tsapiClient axios instance/form/route (NOT /api/form/route)📖 Complete Guide: resources/data-fetching.md
features/ vs components/:
features/: Domain-specific (posts, comments, auth)components/: Truly reusable (SuspenseLoader, CustomAppBar)Feature Subdirectories:
features/
my-feature/
api/ # API service layer
components/ # Feature components
hooks/ # Custom hooks
helpers/ # Utility functions
types/ # TypeScript types
📖 Complete Guide: resources/file-organization.md
Inline vs Separate:
const styles: Record<string, SxProps<Theme>>100 lines: Separate
.styles.tsfile
Primary Method:
sx prop for MUI componentsSxProps<Theme>(theme) => theme.palette.primary.mainMUI v7 Grid:
<Grid size={{ xs: 12, md: 6 }}> // ✅ v7 syntax
<Grid xs={12} md={6}> // ❌ Old syntax
📖 Complete Guide: resources/styling-guide.md
TanStack Router - Folder-Based:
routes/my-route/index.tsxcreateFileRouteExample:
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' }),
});
📖 Complete Guide: resources/routing-guide.md
CRITICAL RULE: No Early Returns
// ❌ NEVER - Causes layout shift
if (isLoading) {
return <LoadingSpinner />;
}
// ✅ ALWAYS - Consistent layout
<SuspenseLoader>
<Content />
</SuspenseLoader>
Why: Prevents Cumulative Layout Shift (CLS), better UX
Error Handling:
useMuiSnackbar for user feedbackreact-toastifyonError callbacks📖 Complete Guide: resources/loading-and-error-states.md
Optimization Patterns:
useMemo: Expensive computations (filter, sort, map)useCallback: Event handlers passed to childrenReact.memo: Expensive components📖 Complete Guide: resources/performance.md
Standards:
any typeimport type { User } from '~types/user'📖 Complete Guide: resources/typescript-standards.md
Covered Topics:
useAuth hook for current user📖 Complete Guide: resources/common-patterns.md
Full working examples:
📖 Complete Guide: resources/complete-examples.md
| Need to... | Read this resource |
|---|---|
| Create a React component | component-patterns.md |
| Fetch data with TanStack Query | data-fetching.md |
| Organize files/folders | file-organization.md |
| Style with MUI v7 | styling-guide.md |
| Set up TanStack Router | routing-guide.md |
| Handle loading/errors | loading-and-error-states.md |
| Optimize React performance | performance.md |
Note: Resources above are React-specific. For Vue/Svelte/Angular, see framework sections in this document.
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;
For complete examples, see resources/complete-examples.md
Composition API - Modern Vue 3 approach with <script setup>:
<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>
Key Patterns:
<script setup> - Concise composition API syntaxdefineProps<T>() - Type-safe props with genericsdefineEmits<T>() - Type-safe eventsref(), reactive() - Reactivity primitivescomputed() - Derived statewatch(), watchEffect() - Side effectsonMounted, onUnmounted - Lifecycle hooks// 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>
// 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 - Reusable logic:
// 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 Server-Side:
<script setup lang="ts">
// Nuxt auto-imports composables
const { data: user, pending, error } = await useFetch(`/api/users/${route.params.id}`)
</script>
shallowRef() for large objectsSvelte 5 Runes - New reactivity system:
<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() - Reactive state (replaces let for reactivity)$derived - Computed values (replaces $:)$derived.by() - Complex derived state$effect() - Side effects (replaces $: statements)$props() - Component props with destructuring$bindable() - Two-way binding for props$inspect() - Debugging reactive values// 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>
// 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 Patterns:
+page.svelte - Page component+page.ts - Page data loading (runs on server and client)+page.server.ts - Server-only load functions+layout.svelte - Shared layouts+server.ts - API endpoints$state() instead of implicit let// 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 - New reactivity system (v16+):
signal() - Writable reactive valuecomputed() - Derived stateeffect() - Side effects.set(), .update() - Modify signal values() - Read signal value (call as function)// 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() }}
}
// 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(['/'])
}
}
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)
}
}
loadComponent for routesFormGroup<T> for type safety| Pattern | React | Vue 3 | Svelte 5 | Angular |
|---|---|---|---|---|
| Local State | useState | ref() | $state() | signal() |
| Derived State | useMemo | computed() |
Props/Events Pattern (All frameworks):
Slots/Children Pattern:
// 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>
File-Based Routing:
[id] for dynamic segmentsProgrammatic Routing:
Universal Techniques:
Framework-Specific:
React.memo, useMemo, useCallback, Suspensev-memo, shallowRef, markRaw, KeepAlive$derived, minimal runtimeIMPORTANT: For UI/UX design work, invoke the specialized skill:
Skill("ui-ux-pro-max") → UI/UX design, visual hierarchy, color theory, spacing, typography
UI/Design:
Backend Integration:
Full-Stack Frameworks:
Development:
Call Skill("ui-ux-pro-max") when:
Skill Status : Multi-framework coverage with progressive disclosure
Weekly Installs
99
Repository
GitHub Stars
12
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykPass
Installed on
opencode87
gemini-cli86
codex85
cursor79
github-copilot79
amp65
Genkit JS 开发指南:AI 应用构建、错误排查与最佳实践
7,700 周安装
AI引导抗体工程优化:从先导物到临床候选物的全流程解决方案
158 周安装
iOS Core Location 问题诊断指南 - 位置更新、后台定位、授权问题排查
159 周安装
Tone.js 教程:使用 Web Audio API 在浏览器中构建交互式音乐应用
160 周安装
sciomc:AI驱动的并行研究代理,自动化分解与验证复杂研究目标
168 周安装
Playwright CLI:无需编码的浏览器自动化测试工具 - 快速上手与安全指南
161 周安装
Spec测试套件生成工具 - 自动化编排smoke/regression/targeted测试用例,提升软件质量
70 周安装
| HttpClient/RxJS |
| Styling | CSS-in-JS/Modules | Scoped CSS | Scoped CSS | Component styles |
| Full-Stack | Next.js | Nuxt | SvelteKit | Universal/SSR |
| Bundle Size | ~40KB | ~32KB | ~3KB | ~60KB |
| Compiler | Runtime | Runtime | Compile-time | AOT Compiler |
useMuiSnackbar for user notificationsimport { SuspenseLoader } from '~components/SuspenseLoader' |
~features | src/features | import { authApi } from '~features/auth' |
| TypeScript types | typescript-standards.md |
| Forms/Auth/DataGrid | common-patterns.md |
| See full React examples | complete-examples.md |
$derivedcomputed() |
| Side Effects | useEffect | watch/watchEffect | $effect() | effect() |
| Global State | Zustand/Context | Pinia | Stores | Services |
| Async State | TanStack Query | VueQuery/Composables | Stores | RxJS/Signals |