背景
vue3 github地址:https://github.com/vuejs/vue-next
特点:
- typecript
- rollup
- 拆分功能package
目录结构:
packages:拆成多个package维护
- compiler-core: 编译核心
- compiler-dom:编译dom
- compiler-sfc: 编译SFC
- compiler-ssr:编译SSR
- reactivity: 响应性
- runtime-core: 运行时核心
- runtime-dom: 运行时dom
- runtime-test: 运行时测试
- server-renderer: 服务端渲染
- shared:分享内容
- size-check: 大小检查
- template-explorer: 模板探索
- vue: vue输出包
- vue-compat: 针对vue2的兼容版本
- global.d.ts
global.d.ts
// 全局的编译时常量
declare var __DEV__: boolean
declare var __TEST__: boolean
declare var __BROWSER__: boolean
declare var __GLOBAL__: boolean
declare var __ESM_BUNDLER__: boolean
declare var __ESM_BROWSER__: boolean
declare var __NODE_JS__: boolean
declare var __COMMIT__: string
declare var __VERSION__: string
declare var __COMPAT__: boolean
// 特征flag
declare var __FEATURE_OPTIONS_API__: boolean
declare var __FEATURE_PROD_DEVTOOLS__: boolean
declare var __FEATURE_SUSPENSE__: boolean
// 测试
declare namespace jest {
interface Matchers {
toHaveBeenWarned(): R
toHaveBeenWarnedLast(): R
toHaveBeenWarnedTimes(n: number): R
}
}
declare module '*.vue' {
}
declare module '*?raw' {
const content: string
export default content
}
declare module 'file-saver' {
export function saveAs(blob: any, name: any): void
}
vnode组件实例interface定义
// packages/runtime-core/src/component.ts
export interface ComponentInternalInstance {
uid: number
type: ConcreteComponent
parent: ComponentInternalInstance | null
root: ComponentInternalInstance
appContext: AppContext
/**
* Vnode representing this component in its parent's vdom tree
*/
vnode: VNode
/**
* The pending new vnode from parent updates
* @internal
*/
next: VNode | null
/**
* Root vnode of this component's own vdom tree
*/
subTree: VNode
/**
* The reactive effect for rendering and patching the component. Callable.
*/
update: ReactiveEffect
/**
* The render function that returns vdom tree.
* @internal
*/
render: InternalRenderFunction | null
/**
* SSR render function
* @internal
*/
ssrRender?: Function | null
/**
* Object containing values this component provides for its descendents
* @internal
*/
provides: Data
/**
* Tracking reactive effects (e.g. watchers) associated with this component
* so that they can be automatically stopped on component unmount
* @internal
*/
effects: ReactiveEffect[] | null
/**
* cache for proxy access type to avoid hasOwnProperty calls
* @internal
*/
accessCache: Data | null
/**
* cache for render function values that rely on _ctx but won't need updates
* after initialized (e.g. inline handlers)
* @internal
*/
renderCache: (Function | VNode)[]
/**
* Resolved component registry, only for components with mixins or extends
* @internal
*/
components: Record | null
/**
* Resolved directive registry, only for components with mixins or extends
* @internal
*/
directives: Record | null
/**
* Resolved filters registry, v2 compat only
* @internal
*/
filters?: Record
/**
* resolved props options
* @internal
*/
propsOptions: NormalizedPropsOptions
/**
* resolved emits options
* @internal
*/
emitsOptions: ObjectEmitsOptions | null
/**
* resolved inheritAttrs options
* @internal
*/
inheritAttrs?: boolean
// the rest are only for stateful components ---------------------------------
// main proxy that serves as the public instance (`this`)
proxy: ComponentPublicInstance | null
// exposed properties via expose()
exposed: Record | null
/**
* alternative proxy used only for runtime-compiled render functions using
* `with` block
* @internal
*/
withProxy: ComponentPublicInstance | null
/**
* This is the target for the public instance proxy. It also holds properties
* injected by user options (computed, methods etc.) and user-attached
* custom properties (via `this.x = ...`)
* @internal
*/
ctx: Data
// state
data: Data
props: Data
attrs: Data
slots: InternalSlots
refs: Data
emit: EmitFn
/**
* used for keeping track of .once event handlers on components
* @internal
*/
emitted: Record | null
/**
* used for caching the value returned from props default factory functions to
* avoid unnecessary watcher trigger
* @internal
*/
propsDefaults: Data
/**
* setup related
* @internal
*/
setupState: Data
/**
* devtools access to additional info
* @internal
*/
devtoolsRawSetupState?: any
/**
* @internal
*/
setupContext: SetupContext | null
/**
* suspense related
* @internal
*/
suspense: SuspenseBoundary | null
/**
* suspense pending batch id
* @internal
*/
suspenseId: number
/**
* @internal
*/
asyncDep: Promise | null
/**
* @internal
*/
asyncResolved: boolean
// lifecycle
isMounted: boolean
isUnmounted: boolean
isDeactivated: boolean
/**
* @internal
*/
[LifecycleHooks.BEFORE_CREATE]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.CREATED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.BEFORE_MOUNT]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.MOUNTED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.BEFORE_UPDATE]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.UPDATED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.UNMOUNTED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.RENDER_TRACKED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.ACTIVATED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.DEACTIVATED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.SERVER_PREFETCH]: LifecycleHook<() => Promise>
}
Ref响应式原理
ref中相关的依赖处理基于effect来实现,其中最重要的是track和trigger方法,原理都基于es6的weakmap来存储依赖:
The main WeakMap that stores {target -> key -> dep} connections.
Conceptually, it's easier to think of a dependency as a Dep class
which maintains a Set of subscribers, but we simply store them as
raw Sets to reduce memory overhead.
// packages/reactivity/src/effect.ts
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map | Set
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
const effects = new Set()
const add = (effectsToAdd: Set | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
add(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}
reactivity 响应式原理
reactive
基于proxy深度遍历所有对象属性,但由于拷贝出的结果和原对象不同,因此在代码中推荐做分离处理:
// packages/reactivity/src/reactive.ts
/**
* Creates a reactive copy of the original object.
*
* The reactive conversion is "deep"—it affects all nested properties. In the
* ES2015 Proxy based implementation, the returned proxy is **not** equal to the
* original object. It is recommended to work exclusively with the reactive
* proxy and avoid relying on the original object.
*
* A reactive object also automatically unwraps refs contained in it, so you
* don't need to use `.value` when accessing and mutating their value:
*
* ```js
* const count = ref(0)
* const obj = reactive({
* count
* })
*
* obj.count++
* obj.count // -> 1
* count.value // -> 1
* ```
*/
export function reactive(target: T): UnwrapNestedRefs
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
接下来看看createReactiveObject的定义:
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler,
collectionHandlers: ProxyHandler,
proxyMap: WeakMap
) {
// 忽略一些边界判断
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
会发现这里的proxyMap
和Ref
中的targetMap
一致都是基于Weakmap,代理生成后会存一份到proxyMap
.那可以确定的是vue3中的响应式实现就是基于proxy来做代理,同时使用weakMap来做依赖及响应式管理,而且proxyMap
和targetMap
都是全局导出.
// packages/reactivity/src/reactive.ts
export const reactiveMap = new WeakMap()
下面我们来看看proxy中使用的baseHandlers,其对应mutableHandlers(当然有其他handler,这里先看主线)
// packages/reactivity/src/baseHandlers.ts
export const mutableHandlers: ProxyHandler
其中has
,ownKeys
,deleteProperty
比较简单,这里主要关注在删除的时候会调用trigger
来删除改响应式key值对应的依赖项,终点我们来看看get和set:
const set = /*#__PURE__*/ createSetter()
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
// 省略其他逻辑
const result = Reflect.set(target, key, value, receiver)
// 省略其他逻辑
return result
}
}
const get = /*#__PURE__*/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// 省略
const res = Reflect.get(target, key, receiver)
return res
}
}