Vue3源码终于发布了!火烧眉毛的撸一下源码,看看Vue3和Vue2到底有什么区别。html
以前写过两篇Vue2响应式原理的文章:vue
Vue2的原理是:github
Vue3的响应式原理是用了proxy的方式来实现,优化了Vue2响应式存在的几个问题,今天就从源码来分析下vue3的响应式原理:typescript
(Vue3的源码是使用ts开发的,须要你们提早学习ts相关知识)api
在Vue3中咱们想要建立一个响应式数据,要怎么作呢?数组
查看下官方api咱们看到一段最基础的示例代码:app
<template>
<button @click="increment">
Count is: {{ state.count }}, double is: {{ state.double }}
</button>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
})
function increment() {
state.count++
}
return {
state,
increment
}
}
}
</script>
复制代码
示例中咱们发现使用reactive生成的state是响应对象,当state.count变化时,依赖state.count的template片断和double都会相应的变化。函数
那么reactive到底作了什么使数据变成了响应对象呢?
####解析reactive
reactive和Vue2中的Vue.observable()相似,返回一个响应对象;reactive返回的响应对象主要用于页面显示,当响应对象改变时视图会自动从新渲染,实现数据和视图的双向绑定。
此处解析代码:
代码结构很是清晰,咱们能够很明白的看到reactive函数的入参必须是一个Object类型的数据,而返回则是一个UnwrapNestedRefs类型的对象(被解嵌套之后的响应式对象,后面会分析UnwrapNestedRefs),此处能够直接简单的理解为返回了一个响应式对象。
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (readonlyToRaw.has(target)) {
return target
}
// target is explicitly marked as readonly by user
if (readonlyValues.has(target)) {
return readonly(target)
}
return createReactiveObject(
target,
// 弱引用的map结构,用于保存原始数据对应的响应式数据
rawToReactive,
// 弱引用的map结构,用于保存响应式数据对应的原始数据
reactiveToRaw,
mutableHandlers,
mutableCollectionHandlers
)
}
复制代码
reactive中关键点在于调用createReactiveObject方法,经过该方法返回传入对象的对应的响应式对象。在createReactiveObject方法中调用new proxy,生成一个代理对象,将该代理对象做为最终的响应式对象并返回。
function createReactiveObject( target: any, // 保存原始数据的weakMap toProxy: WeakMap<any, any>, // 保存响应式数据的weakMap toRaw: WeakMap<any, any>, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any> ) {
// reactive的数据只能是Object类型
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target already has corresponding Proxy
// 数据已经被转化为响应式数据了,直接返回其对应的响应式对象
let observed = toProxy.get(target)
if (observed !== void 0) {
return observed
}
// target is already a Proxy
// 数据自己就是一个响应式对象,则直接返回数据自己
if (toRaw.has(target)) {
return target
}
// only a whitelist of value types can be observed.
// 一些不可被观测的对象,直接返回
if (!canObserve(target)) {
return target
}
// 定义new proxy中的处理函数handlers
// 集合类型的对象使用 collectionHandlers.ts中的 mutableCollectionHandlers
// 其余类型的对象使用 baseHandlers.ts中的 mutableHandlers
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers
// 调用new proxy,生成一个代理对象,将该代理对象做为最终的响应式对象并返回
observed = new Proxy(target, handlers)
toProxy.set(target, observed)
toRaw.set(observed, target)
if (!targetMap.has(target)) {
targetMap.set(target, new Map())
}
return observed
}
复制代码
其中调用new Proxy传入的handler是响应式的关键,集合类型的对象使用 collectionHandlers.ts中的 mutableCollectionHandlers做为new Proxy的handler;其余类型的对象使用 baseHandlers.ts中的 mutableHandlers做为new Proxy的handler。
baseHandlers.ts中的 mutableHandlers中使用createGetter代理对象的get方法、set代理对象的set方法。
其中createGetter方法中作了四件事:一、获取数据的值;二、判断数据是否已经进行过响应式处理;三、使用track方法进行依赖收集;四、对数据的每个Object类型的属性进行reactive递归
function createGetter(isReadonly: boolean) {
return function get(target: any, key: string | symbol, receiver: any) {
// 获取数据的值
const res = Reflect.get(target, key, receiver)
// 已经通过响应式处理的ref数据则直接返回
if (typeof key === 'symbol' && builtInSymbols.has(key)) {
return res
}
if (isRef(res)) {
return res.value
}
// 未通过响应式处理的数据收集其依赖
track(target, OperationTypes.GET, key)
return isObject(res)
? isReadonly
? // need to lazy access readonly and reactive here to avoid
// circular dependency
readonly(res)
: reactive(res)
: res
}
}
复制代码
set方法中作了三件事:一、将set行为更新到原属数据对象上;二、判断代理数据中有没有相应的key,没有则作新增处理,有则作修改处理;三、调用trigger方法,触发其依赖;
function set( target: any, key: string | symbol, value: any, receiver: any ): boolean {
value = toRaw(value)
// 判断代理对象中是否有这个key。如有的话就进行修改,若没有则新增
const hadKey = hasOwn(target, key)
const oldValue = target[key]
// 原值是ref类型,新值不是,则直接赋值,由于原值已经被监听了set触发trigger。此处避免重复触发。
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
// 将set行为更新到原属数据对象上
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
// 若是target是该代理数据相对应的原始数据才作处理,若是targer只是代理数据相对应的原始数据原型链上的数据则不作操做
if (target === toRaw(receiver)) {
/* istanbul ignore else */
if (__DEV__) {
const extraInfo = { oldValue, newValue: value }
if (!hadKey) {
trigger(target, OperationTypes.ADD, key, extraInfo)
} else if (value !== oldValue) {
trigger(target, OperationTypes.SET, key, extraInfo)
}
} else {
if (!hadKey) {
// 代理数据中没有响应的key,则作新增处理,并触发其依赖
trigger(target, OperationTypes.ADD, key)
} else if (value !== oldValue) {
// 代理数据中有响应的key,则作修改处理,并触发其依赖
trigger(target, OperationTypes.SET, key)
}
}
}
return result
}
复制代码
不作过多解释,你们直接看这里吧: vue3响应式源码解析-Reactive篇-collectionHandlers
从上述解析中咱们一直看到一个概念就是ref,那么在vue3中ref究竟是什么呢?vue3中主要的是ref()函数,ref()函数接受一个基本类型的数据,并返回一个响应式的ref对象。了解ref()函数以前须要了解下面两个基本概念:Ref接口、UnwrapNestedRefs。
对Ref接口的定义如 ref.ts:
// Ref接口
export interface Ref<T> {
// 惟一标识位,标识对象是一个ref对象
[refSymbol]: true
// 存放数据,是基本类型数据真正存在的地方,UnwrapNestedRefs表示被接嵌套之后的ref类型的对象
value: UnwrapNestedRefs<T>
}
复制代码
以前分析reactive时又讲到过reactive返回的是一个UnwrapNestedRefs类型的数据,且上面说Ref接口中的value也是UnwrapNestedRefs类型的数据。
export type UnwrapNestedRefs<T> = T extends Ref<any> ? T : UnwrapRef<T>
复制代码
由上面代码可见UnwrapNestedRefs是Ref类型的数据,或者通过UnwrapRef接嵌套之后的数据。 UnwrapRef的定义以下:
// Recursively unwraps nested value bindings.
export type UnwrapRef<T> = {
ref: T extends Ref<infer V> ? UnwrapRef<V> : T
array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T
object: { [K in keyof T]: UnwrapRef<T[K]> }
stop: T
}[T extends Ref<any>
? 'ref'
: T extends Array<any>
? 'array'
: T extends BailTypes
? 'stop' // bail out on types that shouldn't be unwrapped
: T extends object ? 'object' : 'stop']
复制代码
这个接嵌套写的很是巧妙,其中经过infer V推断出类型进行进一步的递归解构(其中infer语句表示在 extends
条件语句中待推断的类型变量:infer定义),也就是说UnwrapNestedRefs只能是ref类型或者其余任意类型的的对象,可是不能是嵌套了ref类型的对象,即不能是这样Ref<Ref>
这样Array<Ref>
或者这样 { [key]: Ref }
嵌套型的ref类型对象。
// 判断数据是否是对象,是对象的话调用reactive()函数将其变为响应式数据,不是对象的话直接返回
const convert = (val: any): any => (isObject(val) ? reactive(val) : val)
// ref函数,接受一个原始数据,返回其响应式的Ref类型的对象
export function ref<T>(raw: T): Ref<T> {
raw = convert(raw)
const v = {
// 添加惟一标识位,标识对象是一个ref对象
[refSymbol]: true,
get value() {
// 依赖收集
track(v, OperationTypes.GET, '')
// 返回get结果
return raw
},
set value(newVal) {
// 对新值进行响应式处理
raw = convert(newVal)
// 依赖触发
trigger(v, OperationTypes.SET, '')
}
}
return v as Ref<T>
}
复制代码
此处ref()主要解决基本类型的数据没法变成响应式数据的问题。 ref和reactive的功能类似,ref用于将基本类型的数据转为响应式数据;reactive用于将对象类型的数据转为响应式数据