Skip to content

🚀 拒绝平庸:像 Vue 核心团队成员那样写代码

在 GitHub 上,Anthony Fu(antfu)的名字几乎代表了 Vue 生态的最前沿。从 VueUse 到 UnoCSS,他的代码以极简、高效、类型友好著称。

很多开发者看了 antfu/skills 仓库后感叹:“原来 Vue 还能这么写?”

今天,我们就扒开大佬的“代码百宝箱”,梳理出一套能让你的 Vue 项目脱胎换骨的规范。我们将从最基础的响应式变量开始,一步步深入到逻辑复用与组件架构,带你领略什么叫“优雅”。


心智模型的大一统 —— 为什么我们只用 ref

当你开始写一个新组件,第一件事往往是定义数据。这时,经典的选择困难症出现了:“用 ref 还是 reactive?”

在 Antfu 的代码世界里,这个答案只有唯一的解:ref

1.1 告别 .value 的恐惧,拥抱一致性

很多人喜欢 reactive 是因为不想写 .value,但这种“偷懒”往往是 bug 的温床。当你尝试解构 reactive 对象时,响应性会悄无声息地丢失。

** 混乱的现状(混合双打):**

typescript
const count = ref(0) // 需要 .value
const state = reactive({ name: 'Ant', loading: false }) // 不需要 .value

// 💀 隐患:解构后 loading 不再响应
const { loading } = state 
watch(loading, () => { /* 永远不会触发 */ })

** Antfu 的 Ref-First 策略:** 统一使用 ref,即使是对象。这带来了一个巨大的认知优势:只要看到变量,就知道它需要 .value;只要解构,就知道需要 toRefs

typescript
const count = ref(0)
const state = ref({ name: 'Ant', loading: false })

// 💡 即使重置整个对象,响应性依然存在(reactive 做不到这点)
function reset() {
  state.value = { name: 'New', loading: true }
}

衔接:统一了数据定义的方式后,我们马上面临下一个问题:如何处理那些不需要深度响应的庞然大物?


第二章 精准控制响应式

Vue 的响应式系统很强,但如果不加节制地滥用,它就是性能杀手。Antfu 的代码中充满了对 shallowRef 的精准使用。

2.1 放过那些第三方实例

当你把 ECharts 实例、Mapbox 地图对象或者成千上万行的纯静态 JSON 数据塞进 ref 时,Vue 会尝试递归地给它们每一层属性穿上 Proxy 的“马甲”。这完全是算力的浪费。

** 性能黑洞:**

typescript
const chartInstance = ref(null) // Vue 试图代理整个 ECharts 实例
const bigData = ref(hugeJsonList) // 每一行数据都被代理

** 浅层响应的智慧:** 使用 shallowRef,Vue 只监听 .value 的变化,而忽略对象内部的属性。

typescript
// 🚀 只有 chartInstance.value 被重新赋值时才触发更新
const chartInstance = shallowRef(null)

// 🚀 列表数据仅作展示,不修改内部字段
const bigData = shallowRef(hugeJsonList)

// 修改数据时,直接替换引用,触发 trigger
bigData.value = [...newData]

衔接:搞定了内部状态,接下来看看组件之间如何“优雅”地传递数据。


第三章 Props 与 Model 的优解

在 Vue 3.4 之前,父子组件通信往往伴随着繁琐的样板代码。Antfu 极力推崇利用最新的宏来简化这一过程。

3.1 像写普通函数一样写 Props

忘掉 withDefaults 那又长又臭的写法吧。Vue 现在支持 Props 响应式解构,这让 Vue 组件看起来更像是一个原生 JS 函数。

** 旧时代的繁琐:**

typescript
const props = withDefaults(defineProps<{ msg?: string }>(), {
  msg: 'Hello'
})
console.log(props.msg) // 必须带着 props. 前缀

** 响应式解构(Destructure):** 需要在 vite.config.ts 中开启相关配置(Vue 3.5+ 已默认支持)。

typescript
const { msg = 'Hello', count = 0 } = defineProps<{
  msg?: string
  count?: number
}>()

// 🪄 直接使用 msg,Vue 编译器会自动处理响应式追踪
watchEffect(() => {
  console.log(msg) 
})

3.2 双向绑定的终极形态 defineModel

以前为了实现 v-model,我们需要定义 props,再 emit 事件,简直是重复劳动的地狱。Antfu 在新项目中已经全面转向 defineModel

typescript
// Child.vue
const modelValue = defineModel<string>() 
// 现在,修改 modelValue.value 会自动通知父组件,就像 ref 一样简单!

衔接:组件写好了,但业务逻辑很复杂怎么办?这时候就需要提取“组合式函数”了。


第四章:组合式函数(Composables)的黄金法则

Antfu 是 VueUse 的作者,他对封装 Hook 有着近乎偏执的标准。核心原则只有一个:灵活兼容

4.1 MaybeReftoValue 的魔法

一个优秀的工具函数,不应该强迫用户传递 ref,也不应该强迫用户传递普通值。它应该两者通吃

** 僵硬的封装:**

typescript
// 强迫用户必须传 ref,传字符串会报错
function useTitle(title: Ref<string>) { ... }

** 丝滑的兼容性:** 利用 Vue 3.3+ 的 toValue(或者 VueUse 的 unref),将输入参数规范化。

typescript
import { type MaybeRefOrGetter, toValue, watchEffect } from 'vue'

// 接收:普通值、Ref、或者 Getter 函数
function useMyHook(input: MaybeRefOrGetter<string>) {
  const data = ref('')
  
  watchEffect(() => {
    // 🪄 toValue 自动解包:
    // 如果是 'hello' -> 'hello'
    // 如果是 ref('hello') -> 'hello'
    // 如果是 () => props.msg -> 'hello' (且保持响应式)
    const val = toValue(input)
    console.log('Value changed:', val)
  })
}

逻辑复用做好了,我们最后来看看如何通过工具链和类型系统,让写代码变成一种享受。


第五章:工具链与类型的极致体验

antfu/skills 中,TypeScript 不仅仅是用来报错的,而是用来自动推导的。

5.1 让组件支持泛型(Generic Component)

不要再在组件里写 any 了。如果你的组件是一个列表渲染器,它应该知道列表项的具体结构。

** 泛型组件实战:** 在 <script setup> 上添加 generic 属性。

vue
<script setup lang="ts" generic="T extends { id: number, name: string }">
defineProps<{
  items: T[] // ✅ 自动推导类型
  selected: T
}>()
</script>

<template>
  <!-- 这里的 item 会自动拥有 T 的类型提示 -->
  <div v-for="item in items" :key="item.id">
    {{ item.name }}
  </div>
</template>

5.2 自动导入(Auto Import)的“零”负担哲学

有人说自动导入破坏了代码来源的清晰度,但 Antfu 认为:常用的 API(如 ref, computed, watch)就像全局变量一样,不需要反复 import。

通过 unplugin-auto-import,你的代码头部将不再被几十行 import 占据。

Before:

typescript
import { ref, computed, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
// ... 哪怕写个 Hello World 都要导一堆

After:

typescript
// 直接写逻辑,IDE 依然有类型提示,构建时自动注入
const count = ref(0)
const router = useRouter()

结语:从“能用”到“艺术品”

总结一下 Antfu 风格的 Vue 开发核心心法:

  1. 一致性优先:全员 ref,全员自动导入。
  2. 性能无感化:用 shallowRef 处理静态大数据,用 toValue 抹平参数差异。
  3. 类型即文档:利用泛型组件和 TS 推导。

Last updated: