| name | vue3-style |
| description | Vue 3 核心源码编码风格 Skill。蒸馏自 vuejs/core 源码、Evan You 的代码审查记录、
Vue RFC 文档、Vue 核心团队成员的技术博客。
触发词:「Vue3 风格」「像 Vue 一样写 TypeScript」「vue3 style」「Evan You 风格」。
适用:前端框架开发、TypeScript 库开发、响应式系统、编译器开发。
|
Vue 3 · 编码 DNA
"The best API is the one that feels obvious the first time you see it." — Evan You
角色定义
此 Skill 激活后,你写出的 TypeScript 代码应该让 Vue 核心团队 contributor 在 code review 时
感觉「这段代码很 Vue」,而不是「这看起来像从 React 搬过来的」。
这意味着:类型显式、接口直觉、抽象有节制。
命名 DNA
5 条直觉规则:
-
动词开头的函数,名字说清楚行为
function createApp(rootComponent: Component): App
function defineComponent(options: ComponentOptions): Component
function computed<T>(getter: () => T): ComputedRef<T>
function app(root: Component): App
function component(options: any): any
-
Ref 类型加 Ref 后缀,响应式变量不加
const count = ref(0)
const countRef = ref(0)
type CountRef = Ref<number>
-
内部 API 用 _ 前缀或放 internal 模块,不暴露
export function _normalizeProps() { ... }
-
布尔值命名用 is/has/should 前缀
const isVNode = (val: unknown): val is VNode => ...
const hasOwn = (obj, key) => Object.hasOwn(obj, key)
-
泛型参数用语义化名称,不用 T/U/V
function ref<Value>(value: Value): Ref<Value>
function shallowRef<Target>(value: Target): ShallowRef<Target>
function wrap<T>(value: T): Wrapper<T>
结构偏好
模块组织:
- 一个功能一个文件,文件名就是功能名(
ref.ts、computed.ts、watch.ts)
- 相关工具函数集中到
utils.ts 或单独的工具模块,不散在各处
- 导出从顶层
index.ts 聚合,内部文件不关心谁引用它
packages/reactivity/src/
├── index.ts ← 唯一的公开出口
├── ref.ts ← ref 相关逻辑
├── computed.ts ← computed 相关逻辑
├── effect.ts ← 响应式依赖追踪
└── baseHandlers.ts ← Proxy handler 实现
函数粒度:
- 函数短而专注,副作用显式(不做隐式的事)
- 工厂函数(
createXxx)返回对象,不用 class(除非有明确理由)
- Composable 以
use 开头,只做一件事
function createReactiveEffect(fn: () => void): ReactiveEffect {
const effect = new ReactiveEffect(fn)
effect.run()
return effect
}
class ReactiveEffectFactory {
create(fn: () => void): ReactiveEffect { ... }
}
类型定义:
- 优先
interface 用于对象形状,type 用于联合/交叉类型
- 类型和实现放同一文件(除非类型要跨包共享)
- 避免
any,实在不行用 unknown + 类型收窄
interface ComputedRef<T = any> extends WritableComputedRef<T> {
readonly [RefSymbol]: true
readonly value: T
}
type ComputedRef = any
注释哲学
Vue 源码的注释非常克制,但关键地方必须解释 Why:
const targetMap = new WeakMap<object, Map<unknown, Dep>>()
if (instance.isMounted) { ... }
const map = new Map()
注释格式规范:
- issue 引用:
// #2308: 描述(对应 GitHub issue)
- TODO 格式:
// TODO: 描述,优先附上 issue 链接
- 类型注释用 JSDoc,方便 IDE 和文档生成
反模式(绝不这样写)
-
as any 甩锅 — 类型不对就该修类型,不是 as any 绕过
const el = document.querySelector('.app') as any
const el = document.querySelector('.app') as HTMLElement
-
过早抽象 — Vue 的哲学是「当你需要抽象时再抽象」,不是「以防万一先抽象」
abstract class BaseReactiveHandler { ... }
class MutableHandler extends BaseReactiveHandler { ... }
-
副作用藏在纯函数里 — 如果函数有副作用,名字/签名要体现出来
-
Ref 当 Reactive 用 — ref 用于基本值,reactive 用于对象,不混用
-
直接 mutate props — Composable 接收的响应式参数只读,不直接修改
-
过度嵌套 Composable — 超过 3 层嵌套调用是设计问题
-
类型断言替代类型设计 — as Type 是最后手段
校验测试
写完代码,问自己:
- 直觉测试:把这段代码 API 给一个 Vue 用户看,他 5 秒内能猜出怎么用吗?
- 纯度检查:有没有没有函数偷偷做了名字之外的事?
- 类型逃逸:有几个
any 或 as?超过 2 个就需要审查
来源
- vuejs/core 源码(packages/reactivity, packages/runtime-core)
- Vue 3 RFC 文档:vuejs/rfcs
- Evan You 关于 Vue 3 设计决策的演讲和博客
- Vue 核心团队 code review 记录(GitHub PR comments)