vue-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vue.js Patterns and Best Practices

Vue.js 模式与最佳实践

Comprehensive guide for Vue.js 3 development using Composition API (
<script setup>
), covering component design, reactivity, state management, routing, testing, and SSR patterns. Nuxt-specific guidance is included where it differs from vanilla Vue.
这是一份使用 Composition API(
<script setup>
)进行 Vue.js 3 开发的综合指南,涵盖组件设计、响应式、状态管理、路由、测试以及 SSR 模式。针对 Nuxt 与原生 Vue 不同的地方,也提供了专属指导。

When to Activate

适用场景

Activate this skill when:
  • The project uses Vue.js (any version), Nuxt, Vite + Vue, or Pinia.
  • The user asks about Vue component architecture, composables, reactivity, or state management.
  • Reviewing Vue Single-File Components (
    .vue
    files).
  • Setting up Vue Router, Pinia stores, or Vite/Vitest configuration.
  • Discussing Vue-specific performance, security, or SSR patterns.

在以下场景启用该技能:
  • 项目使用 Vue.js(任意版本)、Nuxt、Vite + Vue 或 Pinia。
  • 用户询问 Vue 组件架构、组合式函数、响应式或状态管理相关问题。
  • 评审 Vue 单文件组件(
    .vue
    文件)。
  • 配置 Vue Router、Pinia 状态仓库或 Vite/Vitest 环境。
  • 讨论 Vue 专属的性能、安全或 SSR 模式。

1. Project Structure

1. 项目结构

Recommended Layout (Feature-First)

推荐目录结构(以功能为核心)

src/
├── api/              # API client and endpoint definitions
├── assets/           # Static assets (images, fonts, icons)
├── components/       # Shared/reusable components
│   ├── base/         # Base UI primitives (Button, Input, Modal)
│   └── features/     # Feature-specific shared components
├── composables/      # Reusable Composition API logic
├── layouts/          # Page layouts (optional)
├── pages/            # Route-level page components
├── router/           # Vue Router configuration
├── stores/           # Pinia stores
├── types/            # TypeScript type definitions
├── utils/            # Pure utility functions
└── App.vue           # Root component
src/
├── api/              # API 客户端与端点定义
├── assets/           # 静态资源(图片、字体、图标)
├── components/       # 共享/可复用组件
│   ├── base/         # 基础 UI 原语(Button、Input、Modal)
│   └── features/     # 功能专属的共享组件
├── composables/      # 可复用的 Composition API 逻辑
├── layouts/          # 页面布局(可选)
├── pages/            # 路由级页面组件
├── router/           # Vue Router 配置
├── stores/           # Pinia 状态仓库
├── types/            # TypeScript 类型定义
├── utils/            # 纯工具函数
└── App.vue           # 根组件

File Naming

文件命名规范

ConventionWhen to Use
PascalCase.vue
All components (enforced by
vue/multi-word-component-names
)
useCamelCase.ts
Composables
camelCase.ts
Utilities, API clients, types
kebab-case
directories
Route segments, feature folders

命名规则使用场景
PascalCase.vue
所有组件(由
vue/multi-word-component-names
规则强制要求)
useCamelCase.ts
组合式函数
camelCase.ts
工具函数、API 客户端、类型定义
kebab-case
目录
路由片段、功能文件夹

2. Component Architecture

2. 组件架构

Single-File Component Order

单文件组件代码顺序

vue
<script setup lang="ts">
// 1. Imports (vue → ecosystem → absolute → relative)
// 2. Props & Emits & Slots
// 3. Composables
// 4. Local state (ref/reactive)
// 5. Computed properties
// 6. Methods
// 7. Watchers
// 8. Lifecycle hooks
</script>

<template>
  <!-- Template content -->
</template>

<style scoped>
  /* Scoped styles */
</style>
vue
<script setup lang="ts">
// 1. 导入(vue → 生态库 → 绝对路径 → 相对路径)
// 2. Props & Emits & Slots
// 3. 组合式函数
// 4. 本地状态(ref/reactive)
// 5. 计算属性
// 6. 方法
// 7. 监听器
// 8. 生命周期钩子
</script>

<template>
  <!-- 模板内容 -->
</template>

<style scoped>
  /* 作用域样式 */
</style>

Presentational vs Container

展示型组件 vs 容器型组件

  • Container components: Own data fetching, state, and side effects. Render presentational components.
  • Presentational components: Receive props, emit events. No API calls, no store access. Pure rendering.
  • 容器型组件:负责数据获取、状态管理和副作用处理,渲染展示型组件。
  • 展示型组件:接收 props,触发事件。不调用 API,不访问状态仓库,仅负责渲染。

Props Best Practices

Props 最佳实践

ts
// Type-based props with defaults
interface Props {
  label: string;
  variant?: "primary" | "secondary";
  disabled?: boolean;
  items: Item[];
}

const props = withDefaults(defineProps<Props>(), {
  variant: "primary",
  disabled: false,
});
  • Always provide
    type
    , and
    required
    /
    default
    where appropriate.
  • Boolean props:
    isXxx
    ,
    hasXxx
    ,
    canXxx
    .
  • Never mutate props — emit events instead.
  • For v-model binding, use
    defineModel()
    (Vue 3.4+) or
    modelValue
    +
    update:modelValue
    .
ts
// 基于类型的 Props 与默认值
interface Props {
  label: string;
  variant?: "primary" | "secondary";
  disabled?: boolean;
  items: Item[];
}

const props = withDefaults(defineProps<Props>(), {
  variant: "primary",
  disabled: false,
});
  • 始终提供
    type
    ,并在合适时设置
    required
    /
    default
  • 布尔类型 props 使用
    isXxx
    hasXxx
    canXxx
    命名。
  • 绝不要直接修改 props —— 应触发事件通知父组件。
  • 双向绑定使用
    defineModel()
    (Vue 3.4+)或
    modelValue
    +
    update:modelValue

Events

事件规范

ts
const emit = defineEmits<{
  submit: [];
  "update:modelValue": [value: string];
  select: [id: string, index: number];
}>();
  • Use kebab-case in templates (
    @update:model-value
    ).
  • Use camelCase in script (
    emit("update:modelValue", val)
    ).

ts
const emit = defineEmits<{
  submit: [];
  "update:modelValue": [value: string];
  select: [id: string, index: number];
}>();
  • 模板中使用短横线命名(
    @update:model-value
    )。
  • 脚本中使用驼峰命名(
    emit("update:modelValue", val)
    )。

3. Composables (Reusable Logic)

3. 组合式函数(可复用逻辑)

Structure

结构示例

ts
// composables/useDebounce.ts
export function useDebounce<T>(value: MaybeRef<T>, delay: number): Ref<T> {
  const debounced = ref(toValue(value)) as Ref<T>;

  let timer: ReturnType<typeof setTimeout>;
  watch(
    () => toValue(value),
    (newVal) => {
      clearTimeout(timer);
      timer = setTimeout(() => { debounced.value = newVal; }, delay);
    }
  );

  onUnmounted(() => clearTimeout(timer));
  return readonly(debounced);
}
ts
// composables/useDebounce.ts
export function useDebounce<T>(value: MaybeRef<T>, delay: number): Ref<T> {
  const debounced = ref(toValue(value)) as Ref<T>;

  let timer: ReturnType<typeof setTimeout>;
  watch(
    () => toValue(value),
    (newVal) => {
      clearTimeout(timer);
      timer = setTimeout(() => { debounced.value = newVal; }, delay);
    }
  );

  onUnmounted(() => clearTimeout(timer));
  return readonly(debounced);
}

Rules

规则

  • Must start with
    use
    prefix.
  • Return reactive values (
    ref
    ,
    computed
    ,
    reactive
    ), never plain primitives.
  • Accept reactive inputs via
    MaybeRef
    /
    toRef()
    /
    toValue()
    .
  • Clean up side effects in
    onUnmounted
    or watcher
    onCleanup
    .
  • No module-scope side effects.
  • 必须以
    use
    前缀开头。
  • 返回响应式值(
    ref
    computed
    reactive
    ),绝不能返回原始类型值。
  • 通过
    MaybeRef
    /
    toRef()
    /
    toValue()
    接收响应式输入。
  • onUnmounted
    或监听器的
    onCleanup
    中清理副作用。
  • 禁止在模块级产生副作用。

vs Mixins

对比 Mixins

Composables replace Vue 2 mixins entirely:
  • Mixins: Opaque data flow, source-of-truth collisions, name conflicts.
  • Composables: Explicit imports, clear return values, composable and tree-shakable.

组合式函数完全替代 Vue 2 的 Mixins:
  • Mixins:数据流不透明、数据源冲突、命名冲突。
  • 组合式函数:显式导入、返回值清晰、可组合且支持 tree-shaking。

4. State Management

4. 状态管理

When to Use What

方案选择指南

PatternUse Case
ref()
/
reactive()
Local component state
Props + EmitsParent-child communication
Provide / InjectTheme, config, plugin API
Pinia storeGlobal, shared, complex state
Server state composableAPI data with caching (wrap
fetch
/TanStack Query)
模式使用场景
ref()
/
reactive()
组件本地状态
Props + Emits父子组件通信
Provide / Inject主题、配置、插件 API
Pinia 状态仓库全局共享的复杂状态
服务端状态组合式函数带缓存的 API 数据(封装
fetch
/TanStack Query)

Pinia Setup Store (Preferred)

Pinia Setup Store(推荐写法)

ts
// stores/useCartStore.ts
export const useCartStore = defineStore("cart", () => {
  const items = ref<CartItem[]>([]);
  const isLoading = ref(false);

  const totalPrice = computed(() =>
    items.value.reduce((sum, i) => sum + i.price * i.quantity, 0)
  );
  const itemCount = computed(() =>
    items.value.reduce((sum, i) => sum + i.quantity, 0)
  );

  async function addItem(productId: string) {
    isLoading.value = true;
    try {
      const item = await fetchProduct(productId);
      const existing = items.value.find(i => i.id === item.id);
      if (existing) existing.quantity++;
      else items.value.push({ ...item, quantity: 1 });
    } finally {
      isLoading.value = false;
    }
  }

  return { items, isLoading, totalPrice, itemCount, addItem };
});
  • Use Setup Store syntax (not Options Store).
  • Prefer actions for business-level mutations and
    $patch()
    for grouped updates.
  • Every async action: handle loading + success + error.

ts
// stores/useCartStore.ts
export const useCartStore = defineStore("cart", () => {
  const items = ref<CartItem[]>([]);
  const isLoading = ref(false);

  const totalPrice = computed(() =>
    items.value.reduce((sum, i) => sum + i.price * i.quantity, 0)
  );
  const itemCount = computed(() =>
    items.value.reduce((sum, i) => sum + i.quantity, 0)
  );

  async function addItem(productId: string) {
    isLoading.value = true;
    try {
      const item = await fetchProduct(productId);
      const existing = items.value.find(i => i.id === item.id);
      if (existing) existing.quantity++;
      else items.value.push({ ...item, quantity: 1 });
    } finally {
      isLoading.value = false;
    }
  }

  return { items, isLoading, totalPrice, itemCount, addItem };
});
  • 使用 Setup Store 语法(而非 Options Store)。
  • 优先使用 actions 处理业务级修改,使用
    $patch()
    处理批量更新。
  • 所有异步 action 都要处理加载、成功、失败状态。

5. Vue Router

5. Vue Router

Route Definitions

路由定义示例

ts
const routes = [
  {
    path: "/users/:id",
    name: "user-detail",
    component: () => import("@/pages/UserDetail.vue"), // lazy
    props: true, // pass params as props
    meta: { requiresAuth: true },
  },
];
ts
const routes = [
  {
    path: "/users/:id",
    name: "user-detail",
    component: () => import("@/pages/UserDetail.vue"), // 懒加载
    props: true, // 将路由参数作为 props 传递
    meta: { requiresAuth: true },
  },
];

Navigation Guards

导航守卫示例

ts
router.beforeEach((to, from) => {
  const { isLoggedIn } = useAuthStore();
  if (to.meta.requiresAuth && !isLoggedIn) {
    return { name: "login", query: { redirect: to.fullPath } };
  }
});
ts
router.beforeEach((to, from) => {
  const { isLoggedIn } = useAuthStore();
  if (to.meta.requiresAuth && !isLoggedIn) {
    return { name: "login", query: { redirect: to.fullPath } };
  }
});

Reactive Route Params

响应式路由参数

When a component stays mounted but route params change:
ts
const route = useRoute();
const id = computed(() => route.params.id as string);
watch(id, (newId) => fetchItem(newId));

当组件保持挂载但路由参数变化时:
ts
const route = useRoute();
const id = computed(() => route.params.id as string);
watch(id, (newId) => fetchItem(newId));

6. Template Patterns

6. 模板模式

Template Syntax

模板语法示例

vue
<!-- v-if/v-else-if/v-else -->
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>{{ content }}</div>

<!-- v-show for frequent toggles -->
<div v-show="isOpen">Toggled content</div>

<!-- v-for with stable keys -->
<div v-for="item in items" :key="item.id">{{ item.name }}</div>

<!-- Computed filtered list (not v-if + v-for on same element) -->
<div v-for="item in activeItems" :key="item.id">{{ item.name }}</div>

<!-- Event handling -->
<form @submit.prevent="handleSubmit">
  <button type="submit">Save</button>
</form>

<!-- v-model -->
<input v-model="name" />
<CustomInput v-model="value" v-model:title="title" />

vue
<!-- v-if/v-else-if/v-else -->
<div v-if="isLoading">加载中...</div>
<div v-else-if="error">错误:{{ error }}</div>
<div v-else>{{ content }}</div>

<!-- 频繁切换使用 v-show -->
<div v-show="isOpen">可切换内容</div>

<!-- v-for 使用稳定 key -->
<div v-for="item in items" :key="item.id">{{ item.name }}</div>

<!-- 使用计算属性过滤列表(避免同一元素上同时使用 v-if + v-for) -->
<div v-for="item in activeItems" :key="item.id">{{ item.name }}</div>

<!-- 事件处理 -->
<form @submit.prevent="handleSubmit">
  <button type="submit">保存</button>
</form>

<!-- v-model 双向绑定 -->
<input v-model="name" />
<CustomInput v-model="value" v-model:title="title" />

7. Performance

7. 性能优化

TechniqueWhen to Use
v-memo
List items that rarely change
v-once
Content rendered once and static forever
shallowRef()
Large data structures replaced wholesale
shallowReactive()
Only top-level properties are reactive
v-show
over
v-if
Frequent visibility toggles
<KeepAlive :max="10">
Cache toggled views
Lazy routes
() => import(...)
for non-critical routes
Suspense
Async component loading with fallback

技巧使用场景
v-memo
极少变化的列表项
v-once
仅渲染一次且永久静态的内容
shallowRef()
整体替换的大型数据结构
shallowReactive()
仅需顶层属性响应式的对象
优先使用
v-show
频繁切换可见性的元素
<KeepAlive :max="10">
缓存切换的视图
路由懒加载非核心路由使用
() => import(...)
Suspense
异步组件加载时显示 fallback 内容

8. Testing

8. 测试方案

Stack

技术栈

  • Vitest for unit and component tests
  • Vue Test Utils for mounting and interaction
  • @pinia/testing for store mocking
  • Playwright for E2E
  • Vitest:单元测试与组件测试
  • Vue Test Utils:组件挂载与交互测试
  • @pinia/testing:状态仓库 mocking
  • Playwright:端到端测试

Component Test Pattern

组件测试示例

ts
import { mount } from "@vue/test-utils";
import { createPinia, setActivePinia } from "pinia";
import UserCard from "./UserCard.vue";

beforeEach(() => { setActivePinia(createPinia()); });

it("renders and emits", async () => {
  const wrapper = mount(UserCard, {
    props: { user: { id: "1", name: "Alice" } },
  });
  expect(wrapper.text()).toContain("Alice");
  await wrapper.find("button").trigger("click");
  expect(wrapper.emitted("select")![0]).toEqual(["1"]);
});

ts
import { mount } from "@vue/test-utils";
import { createPinia, setActivePinia } from "pinia";
import UserCard from "./UserCard.vue";

beforeEach(() => { setActivePinia(createPinia()); });

it("渲染并触发事件", async () => {
  const wrapper = mount(UserCard, {
    props: { user: { id: "1", name: "Alice" } },
  });
  expect(wrapper.text()).toContain("Alice");
  await wrapper.find("button").trigger("click");
  expect(wrapper.emitted("select")![0]).toEqual(["1"]);
});

9. Nuxt-Specific Patterns

9. Nuxt 专属模式

Auto-Imports

自动导入

Nuxt auto-imports
ref
,
computed
,
watch
,
useFetch
,
useAsyncData
, etc. Use them directly without importing. For non-Nuxt projects, always import explicitly.
Nuxt 会自动导入
ref
computed
watch
useFetch
useAsyncData
等 API,可直接使用无需手动导入。非 Nuxt 项目需显式导入。

useAsyncData / useFetch

useAsyncData / useFetch 示例

ts
const { data: user, pending, error, refresh } = await useAsyncData(
  "user", // unique key for caching
  () => $fetch(`/api/users/${id}`),
);

const { data: posts } = await useFetch("/api/posts", {
  query: { page: 1 },
  key: "posts-page-1", // dedupes requests
});
ts
const { data: user, pending, error, refresh } = await useAsyncData(
  "user", // 缓存唯一标识
  () => $fetch(`/api/users/${id}`),
);

const { data: posts } = await useFetch("/api/posts", {
  query: { page: 1 },
  key: "posts-page-1", // 避免重复请求
});

Server Routes

服务端路由示例

ts
// server/api/users/[id].ts
export default defineEventHandler(async (event) => {
  const { id } = await getValidatedRouterParams(event, z.object({
    id: z.string().uuid(),
  }).parse);
  // ... fetch and return
});
ts
// server/api/users/[id].ts
export default defineEventHandler(async (event) => {
  const { id } = await getValidatedRouterParams(event, z.object({
    id: z.string().uuid(),
  }).parse);
  // ... 获取数据并返回
});

Runtime Config

运行时配置

ts
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // server-only
    apiSecret: "",
    // public (exposed to client)
    public: {
      apiBase: "https://api.example.com",
    },
  },
});

ts
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // 仅服务端可见
    apiSecret: "",
    // 客户端可见(暴露给前端)
    public: {
      apiBase: "https://api.example.com",
    },
  },
});

10. Vue 3.5+ New APIs

10. Vue 3.5+ 新 API

Reactive Props Destructure

响应式 Props 解构

Vue 3.5 stabilized reactive props destructure — destructured variables from
defineProps()
are automatically reactive:
ts
// Vue 3.5+: destructured props are reactive (no need for toRefs)
const { count = 0, msg = "hello" } = defineProps<{
  count?: number;
  msg?: string;
}>();

// Limitation: cannot watch destructured prop directly
watch(() => count, (newVal) => { ... }); // PASS getter required
Vue 3.5 稳定了响应式 Props 解构功能——从
defineProps()
解构的变量会自动保持响应式:
ts
// Vue 3.5+:解构后的 props 保持响应式(无需 toRefs)
const { count = 0, msg = "hello" } = defineProps<{
  count?: number;
  msg?: string;
}>();

// 限制:无法直接监听解构后的 prop
watch(() => count, (newVal) => { ... }); // 必须使用 getter 包装

useTemplateRef()

useTemplateRef()

Replace name-matched plain refs with
useTemplateRef()
for template references:
ts
import { useTemplateRef } from "vue";
const inputEl = useTemplateRef<HTMLInputElement>("input");
// "input" matches the ref="input" attribute in template, not the variable name
Supports dynamic ref IDs:
useTemplateRef(dynamicRefId)
.
使用
useTemplateRef()
替代名称匹配的普通 ref 来获取模板引用:
ts
import { useTemplateRef } from "vue";
const inputEl = useTemplateRef<HTMLInputElement>("input");
// "input" 与模板中的 ref="input" 属性匹配,而非变量名
支持动态 ref ID:
useTemplateRef(dynamicRefId)

onWatcherCleanup()

onWatcherCleanup()

Globally importable watcher cleanup API (Vue 3.5+). It must be called synchronously inside the watcher callback:
ts
import { watch, onWatcherCleanup } from "vue";

watch(userId, async (newId) => {
  const controller = new AbortController();
  onWatcherCleanup(() => controller.abort());
  // ... fetch with signal
});
全局可导入的监听器清理 API(Vue 3.5+),必须在监听器回调中同步调用:
ts
import { watch, onWatcherCleanup } from "vue";

watch(userId, async (newId) => {
  const controller = new AbortController();
  onWatcherCleanup(() => controller.abort());
  // ... 使用 signal 发起请求
});

useId()

useId()

SSR-stable unique ID generation for form elements and accessibility:
ts
import { useId } from "vue";
const id = useId();
SSR 稳定的唯一 ID 生成工具,适用于表单元素与无障碍场景:
ts
import { useId } from "vue";
const id = useId();

defer
Teleport

defer
Teleport

<Teleport defer>
allows teleporting to targets rendered in the same cycle:
vue
<Teleport defer to="#container">Content</Teleport>
<div id="container"></div>
<Teleport defer>
允许 teleport 到同一渲染周期内渲染的目标元素:
vue
<Teleport defer to="#container">内容</Teleport>
<div id="container"></div>

Lazy Hydration (SSR)

懒水化(SSR)

defineAsyncComponent()
now supports
hydrate
strategy:
ts
import { defineAsyncComponent, hydrateOnVisible } from "vue";
const AsyncComp = defineAsyncComponent({
  loader: () => import("./Comp.vue"),
  hydrate: hydrateOnVisible(),
});

defineAsyncComponent()
现在支持
hydrate
策略:
ts
import { defineAsyncComponent, hydrateOnVisible } from "vue";
const AsyncComp = defineAsyncComponent({
  loader: () => import("./Comp.vue"),
  hydrate: hydrateOnVisible(),
});

Anti-Patterns

反模式

Anti-PatternWhy It's WrongThe Fix
Destructuring
defineProps()
(Vue < 3.5)
Captures snapshot, loses reactivityAccess via
props.xxx
or use
toRefs()
watch()
on destructured prop (Vue 3.5+)
Compile-time error — destructured props can't be watched directlyUse getter wrapper:
watch(() => count, ...)
v-if
+
v-for
on same element
Ambiguous execution orderUse computed filtered array
v-for
key = index
Broken state on reorderUse stable database IDs
Mutating propsViolates one-way data flowEmit events or use
v-model
v-html
with user content
XSS vulnerabilitySanitize with DOMPurify
Mixins in Vue 3Opaque, collision-proneReplace with composables
Module-scope side effects in composableShared across instancesScope in
onMounted
+
onUnmounted
reactive()
for replaceable state
Replacement breaks reactivityUse
ref()
instead
Watcher without cleanupMemory leaks, race conditionsUse
onCleanup
or
onWatcherCleanup()
(Vue 3.5+)
Options API in new Vue 3 codeEcosystem move to Composition APIUse
<script setup>
Plain ref for template referencesNo dynamic ref support, name-matching fragileUse
useTemplateRef()
(Vue 3.5+)
反模式问题所在修复方案
Vue < 3.5 中解构
defineProps()
仅捕获快照,丢失响应式通过
props.xxx
访问或使用
toRefs()
Vue 3.5+ 中直接监听解构后的 prop编译错误——解构后的 prop 无法直接监听使用 getter 包装:
watch(() => count, ...)
同一元素上同时使用
v-if
+
v-for
执行顺序模糊使用计算属性过滤数组
v-for
的 key 使用索引
重排时状态异常使用稳定的数据库 ID
修改 props违背单向数据流触发事件或使用
v-model
对用户内容使用
v-html
XSS 漏洞使用 DOMPurify 进行 sanitize
Vue 3 中使用 Mixins不透明、易冲突替换为组合式函数
组合式函数中存在模块级副作用实例间共享副作用限制在
onMounted
+
onUnmounted
对可替换状态使用
reactive()
替换后丢失响应式使用
ref()
替代
监听器未清理内存泄漏、竞态条件使用
onCleanup
onWatcherCleanup()
(Vue 3.5+)
新项目中使用 Options API生态已转向 Composition API使用
<script setup>
模板引用使用普通 ref不支持动态 ref、名称匹配脆弱使用
useTemplateRef()
(Vue 3.5+)

Related Skills

相关技能

  • accessibility
    — ARIA, semantic HTML, focus management
  • frontend-patterns
    — Cross-framework frontend architecture
  • typescript
    — TypeScript best practices applied to Vue projects
  • coding-standards
    — General code quality standards
  • accessibility
    —— ARIA、语义化 HTML、焦点管理
  • frontend-patterns
    —— 跨框架前端架构
  • typescript
    —— 应用于 Vue 项目的 TypeScript 最佳实践
  • coding-standards
    —— 通用代码质量标准