前言
ref相信大家在使用vue3的时候会用的很多, 因为这个是定义基本类型响应式的一个方法,那么你对ref的了解又有多少呢?如果你想了解ref底层, 此文章将帮助你了解它的底层
问题
首先,想问一下大家2个问题
ref和reactive有什么区别?ref中可以放对象作为参数吗?reactive可以放基础值作为参数吗?答案将在文末展示
ref实现
本次实现通过例子 + vue3源码 逐步实现
例子
<!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>
<div id="app"></div>
<script src="../vue/dist/vue.global.js"></script>
<script>
let { ref, effect } = Vue
let name= ref('vvv')
effect(() => {
app.innerHTML = name.value
})
console.log(name);
setTimeout(() => {
name.value = 'vvv2'
}, 1000)
</script>
</body>
</html>
打开浏览器
从上面的例子中可以看到
ref后,返回的是一个RefImpl实例, 并且具有
__v_isRef、 __v_isShallow、 _rawValue、_value等等的一系列属性
大概了解下ref, 下面就让我们来去实现一下ref
ref有俩种形式, 分别是ref 和 shallowRef
export const ref = (value) => {}
export const shallowRef = (value) => {}
上面的参数value 就是我们传进入的值啦
为了不重复代码,我们用用一个函数createRef, 通过不同参数来实现俩种不一样的ref
function createRef (rawValue, shallow = false) {
}
我们上面的代码变化一下, 就变成这样子了
export const ref = (value) => {
return createRef(value)
}
export const shallowRef = (value) => {
return createRef(value, true)
}
我们下面只需要实现createRef就可以实现我们的ref了
function createRef (rawValue, shallow = false) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
isRef函数是判断这个value是否已经ref了
具体的实现方式是:
export function isRef(r) { // 判断是否已经ref过了
return !!(r && r.__v_isRef === true)
}
createRef中的代码, 为什么会还有new RefImpl()呢? 可以在看看上面的动图, 上面的动图是通过vue3源码举的例子,通过ref后会返回一个 RefImpl实例
那么接下来只需要去实现RefImpl实例即可
const convert = (val) => isObject(val) ? reactive(val): val
class RefImpl { // 创建refImpl类
public _value
public _rawValue
public readonly __v_isRef = true // ref标识
constructor(value, public readonly __v_isShallow) {
this._value = value // 获取到旧值
this._rawValue = value
}
get value() { // track(依赖收集)
track(this, trackOpTypes.GET, 'value')
return this._value
}
set value(newValue) { // trigger(触发依赖更新)
if(hasChange(newValue, this._rawValue)) {
this._rawValue = newValue
this._value = this.__v_isShallow ? newValue : convert(newValue)
trigger(this, TriggerOrTypes.SET, 'value', newValue)
}
}
}
上面的track 和 trigger 代码是依赖收集和依赖触发(更新), 具体的详细过程可以看vue3源码 - effect依赖收集触发更新篇, 还有一些utils函数 可以从这里看到喔
此外, ref对象对象的处理是用过了reactive去处理
偷偷告诉你哟: 上面这个实现ref的代码是通过Object.defineProperty去实例的
测试
新建一个ref.html文件
<!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>
<div id="app"></div>
<script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
<script>
let { ref, effect } = VueReactivity
let name = ref('vvv')
console.log(name);
effect(() => {
app.innerHTML = name.value
})
setTimeout(() => {
name.value = 'vvv2'
}, 1000)
</script>
</body>
</html>
打开浏览器, 看看我们实现的怎么样
为了具有辨识度, 我在
RefImpl添加了前缀My
可以看到我们的ref已经实现了, vue3源码其他相关的实现静待下一篇哟
文末
ref的实现已经完毕, 接下来回答一下上面的俩个问题
-
ref和reactive有什么区别?
通过源码我们可以看到ref是通过Object.defineProperty实现的, 而reactive是通过proxy实现的, 这时候就有同学问? 为什么ref不通过proxy实现呢, 主要是因为proxy只支持对象, 爱莫能助ya. -
ref中可以放对象作为参数吗?reactive可以放基础值作为参数吗?
ref是可以传对象的, 如果传的是对象会给reactive去实现
reactive放基础值是没什么作用的, 具体为什么可以看vue3源码 - 响应式数据reactive篇
最后
如果觉得本文对你有帮助,记得点赞👍🏻 、 收藏⭐️ 加关注➕哟