首页 > 其他分享 >#yyds干货盘点#Vue3的reactive

#yyds干货盘点#Vue3的reactive

时间:2022-10-07 21:03:32浏览次数:45  
标签:yyds return target get value reactive key Vue3 const

最近一阶段在学习Vue3,Vue3中用 ​​reactive​​、​​ref​​ 等方法将数据转化为响应式数据,在获取时使用 ​​track​​ 往 ​​effect​​ 中收集依赖,在值改变时,使用 ​​trigger​​ 触发依赖,执行对应的监听函数,这次就先来看一下 ​​reactive​​ 的源码。

前置知识:

在 ​​reactive​​ 中会根据传入数据的类型,做一下分类:

function getTargetType(value: Target) {
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}

const enum TargetType {
// 无效的 比如基础数据类型
INVALID = 0,
// 常见的 比如object Array
COMMON = 1,
// 集合类型比如 map set
COLLECTION = 2
}

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

比如说,如果传入的是 ​​Object​​、​​Array​​,就是常见类型(),传入 ​​Map​​、​​Set​​等就是集合类型,其他的比如传入基础数据,就是无效类型 ​​(INVALID)​​,基础数据要用 ​​ref​​。

export const enum ReactiveFlags {
SKIP = '__v_skip', // 标记一个不能转换为响应式数据的对象
IS_REACTIVE = '__v_isReactive', // 标记一个响应式对象
IS_READONLY = '__v_isReadonly', // 标记一个只读对象
IS_SHALLOW = '__v_isShallow', // 标记只有一层响应的浅可读写对象
RAW = '__v_raw' // 标记获取原始值
}

还有 Vue3 中数据会有一些标记,比如上面的 ​​getTargetType​​​ 方法,当target被标记为 ​​ReactiveFlags.SKIP​​​ 或是 不可拓展的,则会返回 ​​TargetType.INVALID​​​,无法创建代理,因为Vue需要对Target代理附加很多东西,如果是不可拓展的则会附加失败;或是用户主动调用 ​​markRaw​​ 等方法将数据标记为非响应式数据,那么也无法创建代理。

进入正题:

reactive的源码在官方源码的​​packages/reactivity/src/reactive.ts​​文件中,源码中提供了四个Api来创建reactive类对象:

  • reactive:创建可深入响应的可读写对象
  • readonly:创建可深入响应的只读对象
  • shallowReactive:创建只有第一层响应的浅可读写对象(其他层,值改变视图不更新)
  • shallowReadonly:创建只有一层响应的浅只读对象

它们都是调用 ​​createReactiveObject​​ 方法来创建响应式对象,区别在于传入不同的参数:

function reactive(target: object) {
// 如果是只读的话直接返回
if (isReadonly(target)) {
return target
}
return createReactiveObject(
// 目标对象
target,
// 标识是否是只读
false,
// 常用类型拦截器
mutableHandlers,
// 集合类型拦截器
mutableCollectionHandlers,
// 储了每个对象与代理的map关系
reactiveMap
)
}

function shallowReactive(target) {
return createReactiveObject(
target,
false,
shallowReactiveHandlers,
shallowCollectionHandlers,
shallowReactiveMap
);
}

// readonly、shallowReadonly代码这里省略,区别在于传入的拦截器等参数不同

export const reactiveMap = new WeakMap<Target, any>()

​createReactiveObject​​ 代码如下:

function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 如果代理的数据不是对象,则直接返回原对象
if (!isObject(target)) {
return target
}

// 如果传入的已经是代理了 并且 不是readonly 转换 reactive的直接返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}

// 查看当前代理对象之前是不是创建过当前代理,如果创建过直接返回之前缓存的代理对象
// proxyMap 是一个全局的缓存WeakMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}

// 如果当前对象无法创建代理,则直接返回源对象
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}

// 根据targetType 选择集合拦截器还是基础拦截器
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)

// 向全局缓存Map里存储
proxyMap.set(target, proxy)
return proxy
}

这个方法里最主要的就是使用 ​​new Proxy​​ 创建代理,根据 ​​targat​​ 类型不同使用不同的拦截器。其中的 ​​getTargetType​​方法是用来获取传入target的类型:

export function markRaw<T extends object>(value: T): T {
def(value, ReactiveFlags.SKIP, true)
return value
}

看完了入口函数,接下来就是创建Proxy对象的过程了,Vue3会根据getTargetType返回的数据类型来选择是使用collectionHandlers集合拦截器还是baseHandlers常用拦截器,原因下面讲到集合拦截器的时候再说。

常用拦截器baseHandlers:
  1. ​get​​ 拦截器:
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_REACTIVE) { // 获取当前是否是reactive
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) { // 获取当前是否是readonly
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) { // 获取当前是否是shallow
return shallow
} else if (
// 如果获取源对象,在全局缓存WeakMap中获取是否有被创建过,如果创建过直接返回被代理对象
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}

// 是否是数组
const targetIsArray = isArray(target)

// arrayInstrumentations相当于一个改造器,里面定义了数组需要改造的方法,进行一些依赖收集等操作
// 如果是数组,并且访问的方法在改造器中,则使用改造器获取
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}

// 获取结果
const res = Reflect.get(target, key, receiver)

if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}

// 如果不是只读则收集依赖,Vue3中用track收集依赖
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}

// shallow只有表层响应式,不需要下面去深度创建响应了
if (shallow) {
return res
}

// 如果获取的值是ref类型
if (isRef(res)) {
// 如果是数组 并且 是int类型的 key,则返回,否则返回.value属性
return targetIsArray && isIntegerKey(key) ? res : res.value
}

if (isObject(res)) {
// *获取时才创建相对应类型的代理,将访问值也转化为reactive,不是一开始就将所有子数据转换
return isReadonly ? readonly(res) : reactive(res)
}

return res
}
}

大致步骤为:
// 1. 一开始的几个 if else 是用来获取特定属性时返回特定值的
// 2. 如果是数组,用 arrayInstrumentations 特殊处理
// 3. 获取结果,如果不是 只读 的,就 track 收集依赖
// 4. 如果获取的值是 对象 ,将访问的值也转化

注意点是当代理类型是 ​​readonly​​ 时,不会收集依赖。

​Vue3对于深层次的对象是使用时才创建的​​,还有如果结果是ref类型,则需要判断是否要获取它的.value类型,举个

标签:yyds,return,target,get,value,reactive,key,Vue3,const
From: https://blog.51cto.com/u_11365839/5734963

相关文章

  • #yyds干货盘点# LeetCode 热题 HOT 100:最小路径和
    题目:给定一个包含非负整数的m x n 网格 grid,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下或者向右移动一步。 示例1:输入:grid=......
  • #yyds干货盘点# 按工程阶段划分的测试
    (1)单元测试是最小单位的测试活动,也称为模块测试。单元测试是封闭在单元内部的测试,关注一个单元是否正确地实现了规定的功能、逻辑是否正确、输入输出是否正确,从而寻找模块内......
  • vue3学习笔记
    1.官方介绍Vue(读音/vjuː/,类似于view)是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图层......
  • 如何开发一款基于 vite+vue3 的在线表格系统(下)
    在上篇内容中我们为大家分享了详细介绍Vue3和Vite的相关内容。在本篇中我们将从项目实战出发带大家了解Vite+Vue3的在线表格系统的构建。使用Vite初始化Vue3项目在这里需......
  • #yyds干货盘点# 前端歌谣的刷题之路-第一百零五题-监听对象
    前言我是歌谣我有个兄弟巅峰的时候排名c站总榜19叫前端小歌谣曾经我花了三年的时间创作了他现在我要用五年的时间超越他今天又是接近兄弟的一天人生难免坎坷大不了从......
  • #yyds干货盘点# 前端歌谣的刷题之路-第一百零七题-接口
     前言我是歌谣我有个兄弟巅峰的时候排名c站总榜19叫前端小歌谣曾经我花了三年的时间创作了他现在我要用五年的时间超越他今天又是接近兄弟的一天人生难免坎坷大不了......
  • vite创建vue3+ts项目
    vite创建vue3+ts项目为何选择vite:vite是一个基于Vue3单文件组件的非打包开发服务器,它做到了本地快速开发启动:快速的冷启动,不需要等待打包操作;即时的热模块更新,替换性能......
  • Vue3移动端组件库Varlet源码主题阅读之一:本地启动服务时都做了什么
    本文为Varlet组件库源码主题阅读系列第一篇Vue开源的组件库有很多,基本各个大厂都会做一个,所以作为个人再重复造轮子其实意义也不是很大,但是笔者对于如何设计一个Vue组件......
  • 【Vue3.x】自定义hooks
    Vue3hooksvue2里的mixins相似,但是mixins会组件的配置项覆盖。vue3使用了自定义hooks替代mixnins,hooks本质上是函数,引入调用。封装自定义的hooks将图片转化成base64im......
  • 【Vue3.x】自定义指令directive
    自定义指令directive不同于vue2指令bindinsertedupdatecomponentUpdatedunbind1.vue3指令中的钩子函数created元素初始化的时候beforeMount指令绑定到元素后......