赞
踩
之前写过一段vue2.x响应式缺陷,现在来写一写vue3.0中是否解决了2.x存在的问题。
写一个模板,来试一试vue3.0新增属性并且打印
<template> <p>个人信息</p> <p>姓名:{{ person.name }}</p> <p>年龄:{{ person.age }}</p> <p>妈妈:{{ person.family.mother }}</p> <p>爸爸:{{ person.family.father }}</p> <p>兄弟:{{ person.family.brother ? person.family.brother : "还没有兄弟" }}</p> <p>爱好:{{ person.hobby }}</p> <button @click="add">新增家庭成员</button> <button @click="edit">修改个人爱好</button> <button @click="del">删除爱好</button> </template> <script lang="ts"> import { defineComponent, ref, reactive } from "vue"; interface Person<T> { age: number; name: string; family: T; hobby?: string[]; } interface Family { mother: string; father: string; brother?: string; } export default defineComponent({ name: "Home", components: {}, setup() { const person: Person<Family> = reactive({ name: "张三", age: 18, family: { mother: "李四", father: "王五", }, hobby: ["足球", "篮球", "羽毛球"], }); const add = () => { person.family.brother = "赵六"; console.log("person add", person); }; const edit = () => { person.hobby![0] = "学习"; console.log("person edit", person); }; const del = () => { delete person.hobby; console.log("person del", person); }; return { person, add, edit, del, }; }, }); </script>
通过打印发现,数据发生变化,页面同步更新,完美解决了vue2.x数据响应式的问题。
现在我们来看一下vue3.0中的响应式proxy
先写一个模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script> // 源数据 let person = { name: "张三", age: 18, }; </script> </body> </html>
写一段vue2.x的简单数据响应式,方便对比
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script> // 源数据 let person = { name: "张三", age: 18 }; // vue2.x 数据响应式 name let a = {}; Object.defineProperty(a, "name", { // 读 a.name get() { return person.name; }, // 写a.name set(val) { console.log("set name", val); person.name = val; }, }); // age 方便写 就不遍历person key了 Object.defineProperty(a, "age", { // 读 a.name get() { return person.age; }, // 写a.name set(val) { console.log("set age", val); person.age = val; }, }); </script> </body> </html>
打印正常
修改a.name值为李四,触发setter,正常打印数据(vue复杂响应式逻辑,这里就不写了,简单模拟一下),模拟响应式。
然后正常添加一个数据(非响应式数据)
再删除一个数据,显示false,a上仍有属性name,(这是因为没有配置configurable)
配置完就可以正常删除,但是可以发现,不同于读和写,虽然可以删除/添加,但是无法捕获到删除的操作,从以上逻辑及打印可以看得出,vue2.x还是有点小缺陷的。
再来看一下vue3中响应式
首先了解一个window.Porxy,Porxy属于window上内置的一个构造函数
先简单的使用proxy,打印(查看)一下属性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script> // 源数据 let person = { name: "张三", age: 18, }; /* vue3 window.Proxy 代理 1、a 可以映射对person的操作,并且是可监测的 2、第一个参数是监测的对象,第二个参数是操作(配置) */ const a = new Proxy(person,{}); </script> </body> </html>
接着我们尝试一下增删改的操作,发现proxy都可以捕获到,做到了响应式的第一层:数据代理(修改了p的数据,person发生变化),但是未实现响应式的操作,就比如之前vue2.x打印的那个位置,实际上是数据响应式的逻辑,如删除xx,更新xx,只不过现在方便实现,就用console.log代替)
接着我们来捕获一下数据的修改,并打印
<script> // 源数据 let person = { name: "张三", age: 18, }; /* vue3 window.Proxy 代理 1、a 可以映射对person的操作,并且是可监测的 2、第一个参数是监测的对象,第二个参数是操作(配置) */ const a = new Proxy(person, { get(a, b) { console.log("a", a); console.log("b", b); // 先return 1 return 1; }, set() {}, }); </script>
这里的a,指的是源对象person,也就是传入的对象,b是读取的属性,这里就可以发现,区别于Object.defineProperty()单独处理数据,Proxy可以批量处理数据,无论person中有多少属性,只需要一个Proxy就可以处理
<script> // 源数据 let person = { name: "张三", age: 18, }; /* vue3 window.Proxy 代理 1、a 可以映射对person的操作,并且是可监测的 2、第一个参数是监测的对象,第二个参数是操作(配置) */ const a = new Proxy(person, { get(target, propName) { console.log(`读取a中${target[propName]}属性`); return target[propName]; }, set() {}, }); </script>
再来看一下setter的操作
<script> // 源数据 let person = { name: "张三", age: 18, }; /* vue3 window.Proxy 代理 1、a 可以映射对person的操作,并且是可监测的 2、第一个参数是监测的对象,第二个参数是操作(配置) */ const a = new Proxy(person, { get(target, propName) { console.log(`读取a中${target[propName]}属性`); return target[propName]; }, // val是修改的值 set(target, propName, value) { console.log("target", target); console.log("propName", propName); console.log("val", value); }, }); </script>
完整的Proxy读/写代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script> // 源数据 let person = { name: "张三", age: 18, }; /* vue3 window.Proxy 代理 1、a 可以映射对person的操作,并且是可监测的 2、第一个参数是监测的对象,第二个参数是操作(配置) */ const a = new Proxy(person, { get(target, propName) { console.log(`读取a中${target[propName]}属性`); return target[propName]; }, // val是修改的值 set(target, propName, value) { // 这段console.log 模拟vue3数据响应式 console.log(`写a中${target[propName]}属性,更新页面`); target[propName] = value; }, }); </script> </body> </html>
接着看一下区别于Object.defineProperty(),Porxy上有的删除的操作,打印如下
<script> // 源数据 let person = { name: "张三", age: 18, }; /* vue3 window.Proxy 代理 1、a 可以映射对person的操作,并且是可监测的 2、第一个参数是监测的对象,第二个参数是操作(配置) */ const a = new Proxy(person, { get(target, propName) { console.log(`读取a中${target[propName]}属性`); return target[propName]; }, // val是修改的值 set(target, propName, value) { // 这段console.log 模拟vue3数据响应式 console.log(`写a中${target[propName]}属性,更新页面`); target[propName] = value; }, deleteProperty(target, propName) { console.log(`删a中${target[propName]}属性,更新页面`); return delete target[propName]; }, }); </script>
最后,当我们从控制台增加属性时,打印如下,触发了写的操作,也即是说,set不仅在修改时会被调用,增加时也会被调用。
完整代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script> // 源数据 let person = { name: "张三", age: 18, }; /* vue3 window.Proxy 代理 1、a 可以映射对person的操作,并且是可监测的 2、第一个参数是监测的对象,第二个参数是操作(配置) */ const a = new Proxy(person, { // 读取 get(target, propName) { console.log(`读取a中${target[propName]}属性`); return target[propName]; }, // 修改/新增 set(target, propName, value) { // 这段console.log 模拟vue3数据响应式 console.log(`写a中${target[propName]}属性,更新页面`); target[propName] = value; }, // 删除 deleteProperty(target, propName) { console.log(`删a中${target[propName]}属性,更新页面`); return delete target[propName]; }, }); </script> </body> </html>
vue3.0实现数据响应式代理还有一个关键对象,Reflect,来处理一些Object.defineProperty()带来的一些问题,如重名
再来看一下Reflect和打印结果
<script>
let num = { a: 1, b: 2 };
const no1 = Reflect.defineProperty(num, "c", {
get() {
return 3;
},
});
console.log("1 step", no1);
const no2 = Reflect.defineProperty(num, "c", {
get() {
return 4;
},
});
console.log("2 step", no2);
</script>
Reflect通过返回值来判断是否成功,为什么vue3用Reflect而不是用defineProperty,因为与封装框架而言,defineProperty需要额外的try…catch捕获错误,Reflect不需要。
最终实现通过Proxy代理对象中任意属性的变化及通过Refleact反射对代理属性的操作。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <script> // 源数据 let person = { name: "张三", age: 18, }; /* vue3 window.Proxy 代理 1、a 可以映射对person的操作,并且是可监测的 2、第一个参数是监测的对象,第二个参数是操作(配置) */ const a = new Proxy(person, { // 读取 get(target, propName) { console.log(`读取a中${target[propName]}属性`); // return target[propName]; return Reflect.get(target, propName); }, // 修改/新增 set(target, propName, value) { // 这段console.log 模拟vue3数据响应式 console.log(`写a中${target[propName]}属性,更新页面`); // target[propName] = value; Reflect.set(target, propName, value); }, // deleteProperty(target, propName) { console.log(`删a中${target[propName]}属性,更新页面`); // return delete target[propName]; return Reflect.deleteProperty(target, propName); }, }); </script> </body> </html>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。