今天开始总结学习Vue3.0的基本原理。(reactive笔记)
在Vue3.0中将响应式处理放到reactivity文件夹中,然后将其中的reactive,effect,computed, ref各自抽离分模块编写。首先记录一下创建相应数据的reactive方法。
首先在使用reactive方法创建响应式对象时,需要传入一个目标对象:
1const state = reactive({name : "DreamYI",age : 22})
所以在实现reactive(target)方法时应该基于目标对象进行响应式对象的创建,但是目标对象可能不仅仅是一个普通对象、数组还有可能是set map,所以在处理普通的对象和数组时可以在reactive中自定义并返回一个创建响应式数据的方法createReactiveObject(target,mutableHandler)。
1export function reactive(target){
2 // 创建一个响应式的对象 目标对象可能不一定是数组或者对象 可能还有 set map
3 return createReactiveObject(target,mutableHandler);
4}
下面来实现这个创建响应式数据的方法:
1function createReactiveObject(target,baseHandler){
2 if(!isObject(target)){ // 不是对象直接返回即可 isObject为自定义的工具函数判断参数是否为对象
3 return target;
4 }
5 const observed = new Proxy(target,baseHandler);
6 return observed;
7}
这个函数中返回了代理的对象,下面来实现代理的具体处理过程,实现最基本的访问、修改拦截。也就是填补mutableHandler参数的get set操作。在此之前可以将mutableHandler的实现抽离出来。
1//拦截普通对象的处理
2export const mutableHandler = {
3 get,
4 set,
5 // 除了代理这些方法之外 可能还有很多逻辑 deleteProperty has...
6}
在这个参数对象里我们可以对目标对象做很多的拦截操作,这里只简单实现get set。为了代码简洁,将get set抽离单独编写。
1const get = createGetter();
2const set = createSetter();
3// proxy + reflect => es6 的api
4function createGetter() {
5 return function get(target, key, receiver) { // proxy + reflect
6 const res = Reflect.get(target, key, receiver); // target[key];
7 // todo..
8 console.log('用户对这个对象取值了',target,key);
9 return res
10 }
11}
12
13function createSetter() {
14 return function set(target, key, value, receiver) {
15 const result = Reflect.set(target, key, value, receiver); // target[key] = value
16 // todo...
17 console.log('用户对这个对象取值了',target,key);
18 return result;
19 }
20}
现在已经对普通对象进行了基本的数据拦截
1用户对这个对象取值了 {name: "DreamYi", age: 22} name
2用户对这个对象取值了 {name: "duan", age: 22} name
但是此时在传入target是如果有数组元素时:
1const state = reactive({ name: "DreamYi", age: 22 ,arr:[1,2,3]})
2state.arr.push(4)
并且调用数组的push方法:
1用户对这个对象取值了 {name: "duan", age: 22, arr: Array(3)} arr
结果表示:的确是取到了数组arr 但是并没有增加元素4
这是因为此时取到的arr还是一个普通的数组并不是响应式数据,所以此时需要再次对取到的对象做代理执行reactive():
1function createGetter() {
2 return function get(target, key, receiver) { // proxy + reflect
3 const res = Reflect.get(target, key, receiver); // target[key];
4 // todo..
5 console.log('用户对这个对象取值了',target,key);
6 if (isObject(res)) {
7 return reactive(res)
8 }
9 return res
10 }
11}
此时的结果:
1用户对这个对象取值了 {name: "duan", age: 22, arr: Array(3)} arr
2baseHandlers.js:13 用户对这个对象取值了 (3) [1, 2, 3]
push
3baseHandlers.js:13 用户对这个对象取值了 (3) [1, 2, 3]
length
4baseHandlers.js:29 修改操作 (4) [1, 2, 3, 4] 3
5baseHandlers.js:29 修改操作 (4) [1, 2, 3, 4]
length
但是现在会触发两次修改的操作,对于length属性的操作是没有意义的,所以希望把他屏蔽掉:
1function createSetter() {
2 return function set(target, key, value, receiver) {
3 // 需要判断是修改属性 还是增加属性 ,如果原来的值 和新设置的值一样什么都不做
4 const hadKey = hasOwn(target, key);
5 const oldValue = target[key];
6 const result = Reflect.set(target, key, value, receiver); // target[key] = value
7 // todo...
8 if (!hadKey) {
9 console.log('属性的新增操作',target,key);
10 } else if (hasChanged(value, oldValue)) {
11 console.log('修改操作',target,key);
12 }
13 // 值没有变化什么都不用做
14 return result;
15 }
16}
hadKey()、hasChanged()分别是判断对象中是否存在某个属性,与对象是否发生变化的工具函数;
此时要判断是增加属性还是修改属性,所以如果新址值与旧值一样 那就什么都不用做;此时结果:
1用户对这个对象取值了 {name: "DreamYi", age: 22, arr: Array(3)} arr
2baseHandlers.js:13 用户对这个对象取值了 (3) [1, 2, 3]
push
3baseHandlers.js:13 用户对这个对象取值了 (3) [1, 2, 3]
length
4baseHandlers.js:30 属性的新增操作 (4) [1, 2, 3, 4] 3
此时的新增操作只有索引3;
在vue3.0版本中并不是直接对传入的目标对象进行深度的递归,而是在取值的时候进行代理,也解决了vue2.x版本中不能及时对数组索引变化做响应式处理的问题,在性能上得到了很大的提升。
至此,就实现了vue3.0中简单的代理功能。