2022.01.29 add vue2 Object.defineProperty
Vue2 compositon api & Vue 3
Vue2 and compositon api
references:
敲黑板!vue3重点!一文了解Composition API新特性:ref、toRef、toRefs 🌟🌟
在Vue2项目中使用@vue/composition-api 🌟🌟🌟🌟
why composition api
相对于 vue2 的 option api 逻辑复用能力较差,随着组件变大造成的可读性降低且对 ts 的支持性不好,因此 composition api 应运而生,他拥有有更好的类型支持、逻辑组织和逻辑复用
reactive vs ref
首先用很通俗的语言去介绍 ref 和 reactive 做了什么事情:创建一个响应式对象。一般认为 ref 用来接收基本数据类型的数据并为其创建响应式,但事实上它也可以接收引用数据类型。 对应的 reactive 可以用来为引用类型数据创建响应式。
- ref 包裹的值为什么使用时要用 .value
A: ref 通过创建内部状态,将值挂载 value 上,所以 ref 生成的对象都通过 value 使用。
- 什么时候用 ref 什么时候使用 reactive
A: 一般简单数据类型就使用 ref, 在 vue2 中对于 reactive 的值需要重新赋值(直接整个变量重新赋值)的场景使用 reactive 可能失效,则可以使用 ref,或直接对对象中的每个属性分开赋值。
How to get this?
我们知道在 composition api 中已经没有了 this, 那么那些挂在 this 下的属性该怎么使用?
composition api 中在 setup 的第二个入参 context 可以解构出 root 作为当前实例,以及 $refs,但事实上除了 attrs、 slot、emit 外其他属性都已标记为 deprecated,因此如果想日后项目升级 vue3,就避免不使用这些属性。
取而代之的是
const { proxy } = getCurrentInstance() as ComponentInternalInstance
遇到使用 this.$refs 的场景该怎么办呢,其实 ref 创建的对象既可以用于模板的 DOM 元素,此时我们可以用 ref 创建的对象做表单的校验如 validateFields 等
no $router and $route
references:
我们知道针对这些,vue-router4 中推出了
useRouter
,useRoute
Accessing the Router and current Route inside setup
但由于是在 vue2 项目中使用 compositon api,因此我是这样拿到 router 和 a reactive route 的
// router 是路由实例
export const useRouter = () => {
return router
}
export const useRoute = () => {
// 必须添加计算属性,这样才会跟随「响应式依赖」router.app.$route 作出相应变化
// router.app:配置了 router 的 Vue 根实例。
return computed(() => router.app.$route)
}
Object.defineProperty vs Proxy
我们已经知道了 Vue 渲染 dom 的过程中需要不停的监测应用内部状态的变化,而 js 中能够监测变化的是
Object.defineProperty
andProxy
简单描述一下过程:我将其总结为两个阶段:
阶段一:添加依赖:每一个组件实例都会对应一个 watcher 实例,在组件渲染的过程中把“接触”过的数据 property 记录为「依赖」;
阶段二:响应变化:当依赖项的 setter 触发时会通知 watcher,从而与它关联的组件都会重新渲染。
(声明:图节选自《深入浅出Vue.js》)
Object.defineProperty
使用 get 获取当前的值,使用 set 来监听值的变化时给予一些操作: 一个🌰
Vue 中通过
Object.defineProperty
将属性转换成getter/setter
的形式来追踪变化。读取数据时会触发getter
,修改数据时会触发setter
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="text" class="content" />
<div class="copy"></div>
<script>
let obj={};
Object.defineProperty(obj,'username',{
get:function(){
console.info('getting');
},
set:function(val){
console.info('setting'+val);
document.querySelector('.copy').innerHTML=val;
}
});
document.querySelector('.content').addEventListener('keyup',(e)=>{
// 直接监听键盘事件给节点赋值就可以进行双向绑定
// document.querySelector('.copy').innerHTML=e.target.value;
obj.username=e.target.value;
})
</script>
</body>
</html>
缺点:
- 只能劫持对象的属性,如果属性值是对象,还需要深度遍历。
proxy
能在不深度遍历的前提下解决这个问题吗? - 对象上新增、删除属性无法追踪到
- 无法检测数组下标的变化,所以通过数组下标设置值无法实时响应,直接给数组的
length
设置值也无法响应
vue3 的两个新使用的 es6 属性
Proxy
为了实现 getter setter 我们开始使用 es6 的 Proxy
Reflect
为了保证 getter setter 正常获取值和设定值,我们使用了 es6 的 Reflect
Function.prototype.apply.call(Math.floor, undefined, [1.75])
// equal
Math.floor.call(undefined, [1.75]) // this 指向全局
// equal
Reflect.apply(Math.floor, undefined, [1.75])
Map WeakMap
为了处理一些边界情况:如果 target 已经被代理过(变成 Proxy 对象)那么直接返回代理后的内容而不是再次 Proxy 一遍
WeakMap 和 Map 的区别
- WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。
- WeakMap 的键不属于正式引用,因此在键不存在时并不会阻止垃圾回收,因此也就避免了内存泄露的问题。
其他
vue3 本质上和 vue2 在处理响应式上是一样的,都是在 getter 上添加依赖,setter 时 trigger 视图更新
vue3 有哪些提升
- 从源码上看,vue2 中实现响应式时会将一个数组或者是对象的每个属性都递归到添加响应式(比较耗性能),而 vue3 的 reactive 则是只有使用到或者说 get 某一个属性时才将其转化为 proxy 对象。
- 从源码上看,vue3 中对对象或者说引用类型使用 ref 包裹时,实际上本质用的是 reactive
tips: 看源码的一些 tips
可以从测试用例入手,一般测试用例会包括以下两个方面。1. 核心功能 2. 边界条件