专业的编程技术博客社区

网站首页 > 博客文章 正文

Vue 3.0 了解一下 - Proxy(vue3.0api)

baijin 2024-08-13 00:44:15 博客文章 18 ℃ 0 评论

原创声明:本文首发于公众号:前端琐话(qianduansuohua),欢迎关注

前言

4 月 17 日,尤大在微博上宣布 Vue 3.0 beta 版本正式发布。

在尤大发布的《 Vue3 设计过程》文章中提到之所以重构 Vue 一个考量就是JavaScript新的语言特性在主流浏览器中的支持程度,其中最值得一提的就是Proxy,它为框架提供了拦截对于object的操作的能力。Vue 的一项核心能力就是监听用户定义的状态变化并相应式刷新DOM。Vue 2是通过替换状态对象属性的getter和setter来实现这一特性的。改为Proxy后,可以突破Vue当前的限制,比如无法监听新增属性,还能提供更好的性能表现。

Two key considerations led us to the new major version (and rewrite) of Vue: First, the general availability of new JavaScript language features in mainstream browsers. Second, design and architectural issues in the current codebase that had been exposed over time.

作为一名高级前端猿,我们要知其然,更要知其所以然,那就让我们来看一下到底什么是 Proxy?

什么是 Proxy?

Proxy 这个词翻译过来就是“代理”,用在这里表示由它来“代理”某些操作。 Proxy 会在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

先来看下 proxy 的基本语法

const proxy = new Proxy(target, handler)

我们看一个简单的例子:

const person = {

name: 'muyao',

age: 27

};

const proxyPerson = new Proxy(person, {

get: function(target, propKey) {

return 35;

}

});

proxyPerson.name // 35

proxyPerson.age // 35

proxyPerson.sex // 35 不存在的属性同样起作用

person.name // muyao 原对象未改变

上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35

注意,Proxy 并没有改变原有对象 而是生成一个新的对象,要使得 Proxy 起作用,必须针对 Proxy 实例(上例是 proxyPerson)进行操作,而不是针对目标对象(上例是 person)进行操作

Proxy 支持的拦截操作一共 13 种:

为什么要用 Proxy?

vue2 变更检测

Vue2 中是递归遍历 data 中的所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter,在getter 中做数据依赖收集处理,在 setter 中 监听数据的变化,并通知订阅当前数据的地方。

// 对 data中的数据进行深度遍历,给对象的每个属性添加响应式

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get: function reactiveGetter () {

const value = getter ? getter.call(obj) : val

if (Dep.target) {

// 进行依赖收集

dep.depend()

if (childOb) {

childOb.dep.depend()

if (Array.isArray(value)) {

// 是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。

dependArray(value)

}

}

}

return value

},

set: function reactiveSetter (newVal) {

const value = getter ? getter.call(obj) : val

/* eslint-disable no-self-compare */

if (newVal === value || (newVal !== newVal && value !== value)) {

return

}

/* eslint-enable no-self-compare */

if (process.env.NODE_ENV !== 'production' && customSetter) {

customSetter()

}

if (getter && !setter) return

if (setter) {

setter.call(obj, newVal)

} else {

val = newVal

}

// 新的值需要重新进行observe,保证数据响应式

childOb = !shallow && observe(newVal)

// 将数据变化通知所有的观察者

dep.notify()

}

})

但由于 JavaScript 的限制,这种实现有几个问题:

Vue3 改进

Vue3 进行了全新改进,使用 Proxy 代理的作为全新的变更检测,不再使用 Object.defineProperty

在 Vue3 中,可以使用 reactive() 创建一个响应状态

import { reactive } from 'vue'

// reactive state

const state = reactive({

desc: 'Hello Vue 3!',

count: 0

});

我们在源码 vue-next/packages/reactivity/src/reactive.ts 文件中看到了如下的实现:

//reactive f => createReactiveObject()

function createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) {

...

// 设置拦截器

const handlers = collectionTypes.has(target.constructor)

? collectionHandlers

: baseHandlers;

observed = new Proxy(target, handlers);

...

return observed;

}

下面我们看下 state 经过处理后的情况

可以看到被代理的目标对象 state 设置了 get()、set()、deleteProperty()、has()、ownKeys()这 5 个 handler,一起来看下它们都做了什么

get()

get() 会自动读取响应数据,并进行 track 调用

function createGetter(isReadonly = false, shallow = false) {

return function get(target, key, receiver) {

...

// 恢复默认行为

const res = Reflect.get(target, key, receiver)

...

// 调用 track

!isReadonly && track(target, TrackOpTypes.GET, key)

return isObject(res)

? isReadonly

? // need to lazy access readonly and reactive here to avoid

// circular dependency

readonly(res)

: reactive(res)

: res

}

set()

目标对象上不存在的属性设置值时,进行 “添加” 操作,并且会触发 trigger() 来通知响应系统的更新。解决了 Vue 2.x 中无法检测到对象属性的添加的问题

function createSetter(shallow = false) {

return function set(

target: object,

key: string | symbol,

value: unknown,

receiver: object

): boolean {

...

const hadKey = hasOwn(target, key)

// 恢复默认行为

const result = Reflect.set(target, key, value, receiver)

// 如果目标对象在原型链上,不要 trigger

if (target === toRaw(receiver)) {

// 如果设置的属性不在目标对象上 就进行 Add 这就解决了 Vue 2.x 中无法检测到对象属性的添加或删除的问题

if (!hadKey) {

trigger(target, TriggerOpTypes.ADD, key, value)

} else if (hasChanged(value, oldValue)) {

trigger(target, TriggerOpTypes.SET, key, value, oldValue)

}

}

return result

}

}

deleteProperty()

关联 delete 操作,当目标对象上的属性被删除时,会触发 trigger() 来通知响应系统的更新。这也解决了 Vue 2.x 中无法检测到对象属性的删除的问题

function deleteProperty(target, key) {

const hadKey = hasOwn(target, key)

const oldValue = (target as any)[key]

const result = Reflect.deleteProperty(target, key)

// 存在属性删除时触发 trigger

if (result && hadKey) {

trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)

}

return result

}

has() 和 ownKeys()

这两个 handler 并没有修改默认行为,但是它们都调用 track() 函数,回顾上文可以知道has() 影响 in 操作的,ownKeys() 影响 for...in 及循环

function has(target: object, key: string | symbol): boolean {

const result = Reflect.has(target, key)

track(target, TrackOpTypes.HAS, key)

return result

}

function ownKeys(target: object): (string | number | symbol)[] {

track(target, TrackOpTypes.ITERATE, ITERATE_KEY)

return Reflect.ownKeys(target)

}

通过上面的分析,我们可以看到,Vue3 借助 Proxy 的几个 Handler 拦截操作,收集依赖,实现了响应系统核心。

Proxy 还可以做什么?

我们已经看到了 Proxy 在 Vue3 中的应用场景,其实在使用了Proxy后,对象的行为基本上都是可控的,所以我们能拿来做一些之前实现起来比较复杂的事情。

实现访问日志

let api = {

getUser: function(userId) {

/* ... */

},

setUser: function(userId, config) {

/* ... */

}

};

// 打日志

function log(timestamp, method) {

console.log(`${timestamp} - Logging ${method} request.`);

}

api = new Proxy(api, {

get: function(target, key, proxy) {

var value = target[key];

return function(...arguments) {

log(new Date(), key); // 打日志

return Reflect.apply(value, target, arguments);

};

}

});

api.getUsers();

校验模块

let numObj = { count: 0, amount: 1234, total: 14 };

numObj = new Proxy(numObj, {

set(target, key, value, proxy) {

if (typeof value !== 'number') {

throw Error('Properties in numObj can only be numbers');

}

return Reflect.set(target, key, value, proxy);

}

});

// 抛出错误,因为 "foo" 不是数值

numObj.count = 'foo';

// 赋值成功

numObj.count = 333;

可以看到 Proxy 可以有很多有趣的应用,大家快快去探索吧!

Tags:

猜你喜欢

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表