从这篇文章起,我们看看Vue3.0的响应式部分源码。这部分reactive方法是核心,reactive方法如下:
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 (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
首先判断传给reactive方法的参数是否已经被readonly方法处理过,如果已经被处理过,则直接返回。我们先看看被readonly处理过的例子:
<html>
<head>
</head>
<body>
<div id="app">
<div>hello {{state.student.name}}</div>
</div>
<script src="../dist/vue.global.js"></script>
<script>
const {createApp, reactive, toRef, toRefs, ref, readonly} = Vue
const app = createApp({
setup(){
let obj = {
msg: 'hello',
student: {
name: 'liubbc',
age: 20
}
}
let objReadonly = readonly(obj)
//传给reactive方法的参数已被readonly处理过
let state = reactive(objReadonly)
return {state}
}
}).mount('#app')
setTimeout(() => {
console.log(`name is: ${app.state.student.name}`)
app.state.student.name = 'world'
}, 2000)
</script>
</body>
</html>
继续往下走,看createReactiveObject方法:
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only a whitelist of value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
1. 首先判断要进入reactive的对象是否为object,如果不是object则直接返回,在开发环境下给个警告。通过例子看看哪些类型会直接返回:
let num = 10
console.log(`num will be directly return: ${reactive(num)}`)
let str = 'abc'
console.log(`str will be directly return: ${reactive(str)}`)
let bool = true
console.log(`bool will be directly return: ${reactive(bool)}`)
let undef = undefined
console.log(`undef will be directly return: ${reactive(undef)}`)
let symbol = Symbol()
reactive(symbol)
看下打印:
这几种基本类型传给reactive 都没有作用,直接返回了。
2. 如果传个reactive的是个Proxy对象,那么也会直接返回:
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
// 对已经进行reactive的对象,取ReactiveFlags.RAW 属性会返回true,因为进行reactive的过程
// 中会用weakMap进行保存,通过target能判断出是否有ReactiveFlags.RAW属性
// 这里有个例外就是,对reactive对象进行readonly是合法的。
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
我们也通过例子看下这种情况:
setup(){
let obj = {
msg: 'hello',
student: {
name: 'liubbc',
age: 20
}
}
let objReactive = reactive(obj)
let state = reactive(objReactive)
return {state}
}
setup(){
let obj = {
msg: 'hello',
student: {
name: 'liubbc',
age: 20
}
}
let objReadonly = readonly(obj)
let state = readonly(objReadonly)
return {state}
}
也就是重复进行reactive 进行 reactive是没有意义的,直接返回。但有一种例外就是:
setup(){
let obj = {
msg: 'hello',
student: {
name: 'liubbc',
age: 20
}
}
let objReactive = reactive(obj)
let state = readonly(objReactive)
return {state}
}
先进行reactive,再对reactive对象进行readonly 是可以的。
3. 接着往下看,
export const reactiveMap = new WeakMap<Target, any>()
export const readonlyMap = new WeakMap<Target, any>()
// target already has corresponding Proxy
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
这个地方挺关键的,对已经Proxy的,则直接从WeakMap数据结构中取出这个Proxy对象,这是提升性能的一种方法。从代码中可以看出后面会对Proxy对象会进行proxyMap.set(target, proxy) 操作。
4. 接着往下走,后面还对可以reactive的对象加了个白名单,只有Object,Array,Map,Set,WeakMap,WeakSet对象可以进行reactive操作
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
function getTargetType(value: Target) {
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
// only a whitelist of value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
5. 看看最后一段代码:
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
最后进行了 Proxy,注意不管target有多深,在初次进行reactive的时候只对第一层进行Proxy。我们看下reactive后的Proxy对象:
对比vue2.0,可以看出Proxy只对第一层进行reactive操作,也就是进行了延迟reactive操作,而不是无脑进行全部reactive操作,提升了性能。
我们看看readonly部分,
export function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target,
true,
readonlyHandlers,
readonlyCollectionHandlers
)
}
readonly直接调用createReactiveObject,只是传的参数不一样。经过readonly处理过的对象,对象的所有属性都是不可更改的。看个简单例子:
<html>
<head>
</head>
<body>
<div id="app">
<div>hello {{state.student.name}}</div>
</div>
<script src="../dist/vue.global.js"></script>
<script>
const {createApp, reactive, toRef, toRefs, ref, readonly} = Vue
const app = createApp({
setup(){
let obj = {
msg: 'hello',
student: {
name: 'liubbc',
age: 20
}
}
let state = readonly(obj)
return {state}
}
}).mount('#app')
setTimeout(() => {
console.log(`name is: ${app.state.student.name}`)
app.state.student.name = 'world'
}, 2000)
</script>
</body>
</html>
修改app.state.student.name = 'world' 不起作用,在开发环境下给了个警告:
感觉在实际开发中readonly应该不常用。