字节面试题:请你谈谈vue的响应式原理(二)

前言

在之前的文章字节面试题:请你谈谈vue的响应式原理(一)- 掘金中我为各位讲解了一下vue3中reactive的原理并带大家手写了一个简易版的reactive。既然都分了一和二,那么大家肯定也能猜到这篇文章写什么了。相信看完这篇文章之后,各位读者老爷就能理解为什么在vue3中会有两个函数用于创建响应式数据,而为什么ref能把原始类型和引用类型的值都转变为响应式。

正文

在讲明白ref将数据变为响应式原理之前,我们先来简单看看这东西是怎么用的。

js 复制代码
<template>
  <div> </div>
</template>

<script setup>
const name = '浦东彭于晏'
const refName = ref(name)
const obj = {
  name: '南昌吴彦祖',
  age: 22
}
const refObj = ref(obj)
console.log(refName);
console.log(refObj);

</script>

在代码中我们可以看到,如果我们试图将包装过后的数据直接拿来使用,不管原来的数据是引用类型还是原始类型,我们能拿到的并不是原数据,而是被包裹了一层奇奇怪怪的东西,只有在后面接上.value之后才能拿到"原来的"数据。如果我们展开这个"奇奇怪怪"的东西,会发现上面除了我们给的数据,还有一个名为__v_isRef和_rawValue的键值对,_rawValue很好理解,就是被加工前的数据,而__v_isRef正是ref响应式的标签。代表这个值已经是响应式了。

原始类型

在大致了解的ref的用法之后,我们就可以开始聊聊ref的响应式原理了。这里还是和之前讲reactive一样,会通过手写一个ref 的方式去给各位读者姥爷讲清楚。

废话不多说,直接上代码。首先,来一个函数

js 复制代码
function createRef(value) {
 
}

刚刚也提到__v_isRef是ref响应式的标签,而在reactive中我们也讲到过关于避免重复代理的问题。所以这里我们就可以用__v_isRef去做一个判断

js 复制代码
function createRef(value) {
    // 将value变为响应式,同样需要判断是否已经是响应式数据
    if (value.__v_isRef) {
        return value
    }

    return new RefImpl(value)
}

RefImpl相信大家也能看出来,首字母大写是一个构造函数。在构造的过程中,首先就是打上__v_isRef的标签用于声明这个值已经是响应式了。随后就是_value赋值。这也正是为什么我们需要通过._value才能看到数据。

js 复制代码
class RefImpl {
    constructor(val) {
        // 响应标记,有标记的就是响应式数据
        this.__v_isRef = true
        this._value = convert(val)
    }
}

新语法

接下来的新语法是讲原始类型的值转变为响应式的关键点了。之前我们讲reactive的时候聊到了引用类型是通过proxy代理去做响应式,这里则是用class语法中的"访问器属性"去给原始类型做响应式

首先我们要知道,写在constructor外面的属性是在实例对象的隐式原型上,对原型链不太熟悉的同学可以看看我之前的文章。编程小白的福音:轻松学会JavaScript原型链

js 复制代码
class RefImpl {
    constructor(val) {
        // 响应标记,有标记的就是响应式数据
        this.__v_isRef = true
        this._value = convert(val)
    }

    // 这一步是在构造函数的现实原型,也就是实例对象的隐式原型上
    get value() {
       
    }

    set value(newValue) {
       
        }
    }
}

这里在函数前面加get的行为属于是在使用js中对象的自读取能力,这种行为就类似下面这种代码。

js 复制代码
let obj = {
    name: 'zhangsan',
    age() {
        return 18;
    },
}

console.log(obj.age());

而加"_"的目的则在于将属性标记为内部使用,避免在实例化对象的时候,属性因为没有setter而导致的程序报错。如此一来,当我们再次访问或读取这个值的时候,就能像被代理过的函数那样触发某些特定需求了。

接下来的事情,各位看过上一篇文章的读者姥爷想必已经门清了,就是在get中做依赖收集以及在set中去触发依赖函数。而之前的依赖收集函数可以直接复用。在set中,当值被修改,我们可以通过对比修改前后的值是否一致,从而选择是否修改。

js 复制代码
import { track, trigger } from "./effect.js"

class RefImpl {
    constructor(val) {
        // 响应标记,有标记的就是响应式数据
        this.__v_isRef = true
        this._value = val
    }

    // 这一步是在构造函数的现实原型,也就是实例对象的隐式原型上
    get value() {
        // 为this对象做依赖收集
        track(this, 'value')
        return this._value
    }

    set value(newValue) {
        console.log("set" + newValue);

        // 判断是否相等,相等则不触发更新
        if (newValue !== this._value) {
            // 这里不能直接复制,要看类型
            this._value = val
            // 为this对象做依赖触发
            trigger(this, 'value')
        }
    }
}

到这一步,ref中将原始类型变为响应式对象就完成了。但问题在于,如何通过ref将引用类型变为响应式呢?实际上,还是通过我们上次讲的reactive。这里我们优化一下代码,做一个类型判断。如果是原始类型则直接返回,否则返回reactive处理过后的对象。

js 复制代码
import { track, trigger } from "./effect.js"
import { reactive } from './reactive.js'

class RefImpl {
    constructor(val) {
        // 响应标记,有标记的就是响应式数据
        this.__v_isRef = true
        this._value = convert(val)
    }

    // 这一步是在构造函数的现实原型,也就是实例对象的隐式原型上
    get value() {
        // 为this对象做依赖收集
        track(this, 'value')
        return this._value
    }

    set value(newValue) {
        console.log("set" + newValue);

        // 判断是否相等,相等则不触发更新
        if (newValue !== this._value) {
            // 这里不能直接复制,要看类型
            this._value = convert(newValue)
            // 为this对象做依赖触发
            trigger(this, 'value')
        }
    }
}

function convert(value) {
    if (typeof value !== 'object' || value === null) {
        // 不是对象,就return value
        return value
    } else {
        return reactive(value)
    }
}

总结

其实对于vue3中到底该使用ref还是reactive一直都存在一些争议,有人偏爱reactive的简洁,不用挂个.value的拖油瓶让人神清气爽,写代码都有动力不少,而有人偏爱ref的全能,通篇都是ref。实际上对于二者来说,尤大大的建议是ref,而reactive的出现在我看来更像是一种"被迫无奈"的举动。而在实际开发中,具体使用ref还是reactive属于是个见仁见智的问题,至少在我的开发过程中,统一使用ref的情况占绝大多数。

最后,希望我的文章能够帮助到各位正在学习的朋友们,祝各位读者姥爷0 warning(s),0 error(s)!

相关推荐
道不尽世间的沧桑1 小时前
第17篇:网络请求与Axios集成
开发语言·前端·javascript
diemeng11192 小时前
AI前端开发技能变革时代:效率与创新的新范式
前端·人工智能
bin91534 小时前
DeepSeek 助力 Vue 开发:打造丝滑的复制到剪贴板(Copy to Clipboard)
前端·javascript·vue.js·ecmascript·deepseek
晴空万里藏片云6 小时前
elment Table多级表头固定列后,合计行错位显示问题解决
前端·javascript·vue.js
曦月合一6 小时前
html中iframe标签 隐藏滚动条
前端·html·iframe
奶球不是球6 小时前
el-button按钮的loading状态设置
前端·javascript
kidding7236 小时前
前端VUE3的面试题
前端·typescript·compositionapi·fragment·teleport·suspense
Σίσυφος19008 小时前
halcon 条形码、二维码识别、opencv识别
前端·数据库
学代码的小前端8 小时前
0基础学前端-----CSS DAY13
前端·css
css趣多多9 小时前
案例自定义tabBar
前端