您现在的位置是:网站首页> 编程资料编程资料
Vue3 源码分析reactive readonly实例_vue.js_
2023-05-24
530人已围观
简介 Vue3 源码分析reactive readonly实例_vue.js_
引言
上次一起阅读了watch和computed的源码,其实应该先看副作用effect,因为各个响应式的API里基本都用到了,等结束了reactive和readonly和ref,就一起看看effect。这次要说的是reactive和readonly,两者在实现上流程大体一致。尤其是对Map和Set的方法的代理拦截,多少有点妙。
一、reactive 和 readonly
Vue3使用Proxy来替代Vue2中Object.defineProperty。
const target = { name: 'onlyy~' } // 创建一个对target的代理 const proxy = new Proxy(target, { // ...各种handler,例如get,set... get(target, property, receiver){ // 其它操作 // ... return Reflect.get(target, property, receiver) } }) 1. reactive相关类型
reactive利用Proxy来定义一个响应式对象。
- Target:目标对象,包含几个标志,以及__v_raw字段,该字段表示它原本的非响应式状态的值;
export interface Target { [ReactiveFlags.SKIP]?: boolean [ReactiveFlags.IS_REACTIVE]?: boolean [ReactiveFlags.IS_READONLY]?: boolean [ReactiveFlags.IS_SHALLOW]?: boolean [ReactiveFlags.RAW]?: any } export const reactiveMap = new WeakMap() export const shallowReactiveMap = new WeakMap() export const readonlyMap = new WeakMap() export const shallowReadonlyMap = new WeakMap() const enum TargetType { INVALID = 0, COMMON = 1, COLLECTION = 2 } 2. 相关全局变量与方法
- ReactiveFlags:定义了各种标志对应的字符串(作为reactive对象的属性)的枚举;
- reactiveMap
- shallowReactiveMap
- readonlyMap
- shallowReadonlyMap:这几个Map分别用于存放对应API生成的响应式对象(以目标对象为key,代理对象为value),便于后续判断某个对象是否存在已创建的响应式对象;
- TargetType:枚举成员的内容分别用于区分代理目标是否校验合法、普通对象、Set或Map;
// 各个标志枚举 export const enum ReactiveFlags { SKIP = '__v_skip', IS_REACTIVE = '__v_isReactive', IS_READONLY = '__v_isReadonly', IS_SHALLOW = '__v_isShallow', RAW = '__v_raw' } // ... export const reactiveMap = new WeakMap() export const shallowReactiveMap = new WeakMap() export const readonlyMap = new WeakMap() export const shallowReadonlyMap = new WeakMap() const enum TargetType { INVALID = 0, COMMON = 1, COLLECTION = 2 } 然后是两个函数:targetTypeMap用于判断各种JS类型属于TargetType中的哪种;getTargetType用于获取target对应的TargetType类型。
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)) } 3. reactive函数
reactive入参类型为object,返回值类型是UnwrapNestedRefs,对嵌套的Ref进行了解包。意味着即使reactive接收一个Ref,其返回值也不用再像Ref那样通过.value来读取值。源码的注释中也给出了示例。
/* * const count = ref(0) * const obj = reactive({ * count * }) * * obj.count++ * obj.count // -> 1 * count.value // -> 1 */ reactive内部调用createReactiveObject来创建响应式对象。瞄一眼入参有五个:
- target:代理目标;
- false:对应createReactiveObject的isReadonly参数;
- mutableHandlers:普通对象和数组的代理处理程序;
- mutableCollectionHandlers:Set和Map的代理处理程序;
- reactiveMap:之前定义的全局变量,收集reactive对应的依赖。
export function reactive(target: T): UnwrapNestedRefs export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. if (isReadonly(target)) { return target } return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap ) }
4. 造物主createReactiveObject
不论是reactive,还是shallowReactive、readonly和shallowReadonly,都是内部调用createReactiveObject来创建代理的。createReactiveObject也没什么操作,主要判断了下target的类型,再决定是直接返回target还是返回一个新建的proxy。
以下情况直接返回target:
- target不是对象;
- target已经是一个响应式的对象,即由createReactiveObject创建的proxy;
- target类型校验不合法,例如RegExp、Date等;
当参数proxyMap对应的实参(可能为reactiveMap、shallowReactiveMap、readonlyMap或shallowReadonlyMap,分别对应ractive、shallowReactive、readonly和shallowReadonly四个API)里已经存在了target的响应式对象时,直接取出并返回该响应式对象;
否则,创建一个target的响应式对象proxy,将proxy加入到proxyMap中,然后返回该proxy。
function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler, collectionHandlers: ProxyHandler , proxyMap: WeakMap ) { 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 existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy } // only specific 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 }
我们知道,代理的重点其实在与代理的处理程序,createReactiveObject根据普通对象和数组类型、Set和Map类型来区分baseHandlers和collectionHandlers。
5. shallowReactive、readonly和shallowReadonly
事实上,ractive、shallowReactive、readonly和shallowReadonly这几个函数形式上基本一致,都是通过createReactiveObject来创建响应式对象,存储在对应的proxyMap里,但是对应的baseHandlers和collectionHandlers有区别。
// shallowReactive export function shallowReactive( target: T ): ShallowReactive { return createReactiveObject( target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap ) } // raedonly // 注意readonly不是响应式的,而是一个原对象的只读的拷贝 // 具体实现在对应的handlers里 export function readonly ( target: T ): DeepReadonly > { return createReactiveObject( target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap ) } // shallowReadonly // 是响应式的 // 只有最外层是只读的 export function shallowReadonly (target: T): Readonly { return createReactiveObject( target, true, shallowReadonlyHandlers, shallowReadonlyCollectionHandlers, shallowReadonlyMap ) }
事实上,ractive、shallowReactive、readonly和shallowReadonly这几个函数形式上基本一致,都是通过createReactiveObject来创建响应式对象,存储在对应的proxyMap里,但是对应的baseHandlers和collectionHandlers有区别。那么我们就知道了,其实重点都在各种handlers里。
二、对应的 Handlers
baseHandlers用于普通对象和数组的代理,collectionHandlers用于Set、Map等的代理。对应ractive、shallowReactive、readonly和shallowReadonly四个API,每一个都有自己的baseHandlers和collectionHandlers。
1. baseHandlers
在packages/reactivity/src/baseHandlers.ts文件中。分别导出了这4个API对应的baseHandlers。
1.1 reactive
reactive的baseHandlers中有5个代理程序。
// reactive export const mutableHandlers: ProxyHandler
在拦截过程中,在get、has和ownKey这几个访问程序中进行依赖捕获(track),在set和deleteProperty这俩用于更改的程序中触发更新(trigger) 。
get和set分别由函数createGetter和createSetter创建,这俩函数根据入参的不同,返回不同的get和set,readonly等API的baseHandlers中的get和set也大都源于此,除了两种readonly中用于告警的set。
(1) get
createGetter两个入参:isReadonly和isShallow,两两组合正好对应四个API。
- shallow:为true时不会进入递归环节,因此是浅层的处理;
- isReadonly:在createGetter中影响proxyMap的选择和递归时API的选择,它主要发挥作用是在set中。
function createGetter(isReadonly = false, shallow = false) { return function get(target: Target, key: string | symbol, receiver: object) { // 以下几个if分支判断target是否已经是由这几个API创建的代理对象,代理得到的proxy才具有这些key if (key === ReactiveFlags.IS_REACTIVE) { // 是否是响应式对象 return !isReadonly } else if (key === ReactiveFlags.IS_READONLY) { // 是否是只读对象 return isReadonly } else if (key === ReactiveFlags.IS_SHALLOW) { // 是否是浅层的 响应式/只读 对象 return shallow } else if ( // __v_raw 属性对应 代理对象的目标对象 // 当该属性有值,且在相应的proxyMap中存在代理对象时,说明target已经是一个proxy了 // __v_raw 属性对应的值为target本身 key === ReactiveFlags.RAW && receiver === (isReadonly ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap ).get(target) ) { return target } const targetIsArray = isArray(target) // 对数组的几个方法进行代理,在'includes', 'indexOf', 'lastIndexOf'等方法中进行track捕获依赖 if (!isReadonly && targetIsArray && hasOwn(arrayInstrument
相关内容
- vue中Echarts使用动态数据的两种实现方式_vue.js_
- JS获取对象属性API汇总枚举symbol_javascript技巧_
- Vue中v-bind原理深入探究_vue.js_
- Vue3源码解析watch函数实例_vue.js_
- antDesign 自定义分页样式的实现代码_javascript技巧_
- Vue watch原理源码层深入讲解_vue.js_
- Vue3源码分析组件挂载初始化props与slots_vue.js_
- Vue3源码分析组件挂载创建虚拟节点_vue.js_
- Vue computed实现原理深入讲解_vue.js_
- Nodejs处理Json文件并将处理后的数据写入新文件中_node.js_
点击排行
本栏推荐
