【源码系列#05】Vue3响应式原理(Ref)
Ref & ShallowRef
ref:接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value
可以将 ref 看成 reactive 的一个变形版本,这是由于 reactive 内部采用 Proxy 来实现,而 Proxy 只接受对象作为入参,这才有了 ref 来解决值类型的数据响应,如果传入 ref 的是一个对象,内部也会调用 reactive 方法进行深层响应转换
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
shallowRef:ref() 的浅层作用形式。和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。
const state = shallowRef({ count: 1 })
// 不会触发更改
state.value.count = 2
// 会触发更改
state.value = { count: 2 }
源码实现
- @issue1 如果是对象和数组,则调用 reactive方法 转化为响应式对象(ref会转换,shallowRef不会转换)
- @issue2 getter 取值的时候收集依赖
- @issue3 setter 设置值的时候触发依赖
/**
* @desc 如果是对象和数组,则转化为响应式对象
*/
function toReactive(value) {
return isObject(value) ? reactive(value) : value
}
/**
* @desc RefImpl
* @issue1 如果是对象和数组,则转化为响应式对象
*/
class RefImpl {
// ref标识
public __v_isRef = true
// 存储effect
public dep = new Set()
public _value
constructor(public rawValue, public _shallow) {
// @issue1
this._value = _shallow ? rawValue : toReactive(rawValue)
}
get value() {
// 取值的时候收集依赖
trackEffects(this.dep)
return this._value
}
set value(newValue) {
// 新旧值不相等
if (newValue !== this.rawValue) {
// @issue1
this._value = this._shallow ? newValue : toReactive(newValue)
this.rawValue = newValue
// 设置值的时候触发依赖
triggerEffects(this.dep)
}
}
}
// 接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。
export function ref(value) {
return new RefImpl(value)
}
测试代码
/**
* 1. Ref
**/
const person = ref({
name: '柏成',
age: 25,
})
effect(() => {
app.innerHTML = person.value.name
})
setTimeout(() => {
person.value.name = '柏成2号' // 会触发更改
}, 1000)
/**
* 2. shallowRef
*/
const person = shallowRef({
name: '柏成',
age: 25,
})
effect(() => {
app.innerHTML = person.value.name
})
setTimeout(() => {
person.value.name = '柏成2号' // 不会触发更改
}, 1000)
setTimeout(() => {
person.value = {
name: '柏成9号' // 会触发更改
}
}, 2000)
toRef & toRefs
toRef:基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
// 更改该 ref 会更新源属性
fooRef.value++
console.log(state.foo) // 2
// 更改源属性也会更新该 ref
state.foo++
console.log(fooRef.value) // 3
toRefs:将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
// 这个 ref 和源属性已经 “链接上了”
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
源码实现
class ObjectRefImpl {
// 只是将.value属性代理到原始类型上
constructor(public object, public key) {}
get value() {
return this.object[this.key]
}
set value(newValue) {
this.object[this.key] = newValue
}
}
// 基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。
export function toRef(object, key) {
return new ObjectRefImpl(object, key)
}
// 将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
export function toRefs(object) {
const result = isArray(object) ? new Array(object.length) : {}
for (let key in object) {
result[key] = toRef(object, key)
}
return result
}
测试代码
// 对象
const person = reactive({
name: '柏成',
age: 18
})
// 数组
const numbers = reactive([1, 2, 3, 4, 5])
// 注意!直接解构后会丢失响应性的特点!!!
// let { name, age } = person
const {
name,
age
} = toRefs(person)
const [first] = toRefs(numbers)
effect(() => {
app.innerHTML = `${name.value},${age.value}岁。第一个数字为${first.value}。`
})
setTimeout(() => {
name.value = '柏成9号'
first.value = 999
}, 1000)
自动脱ref
在js中访问ref时需要.value获取,但是在模版中却可以直接取值,不需要加.value!这里就用到了 proxyRefs 自动脱ref方法
源码实现
export function proxyRefs(object) {
return new Proxy(object, {
// 代理的思想,如果是ref 则取ref.value
get(target, key, recevier) {
let r = Reflect.get(target, key, recevier)
return r.__v_isRef ? r.value : r
},
// 设置的时候如果是ref,则给ref.value赋值
set(target, key, value, recevier) {
let oldValue = target[key]
if (oldValue.__v_isRef) {
oldValue.value = value
return true
} else {
return Reflect.set(target, key, value, recevier)
}
},
})
}
测试代码
const name = ref('柏成')
const age = ref('24')
const person = proxyRefs({
name,
age,
sex: '男'
})
effect(() => {
app.innerHTML = `${person.name},${person.age}岁。性别${person.sex}。`
})
setTimeout(() => {
name.value = '柏成9号'
}, 1000)
【源码系列#05】Vue3响应式原理(Ref)的更多相关文章
- vue 源码自问自答-响应式原理
vue 源码自问自答-响应式原理 最近看了 Vue 源码和源码分析类的文章,感觉明白了很多,但是仔细想想却说不出个所以然. 所以打算把自己掌握的知识,试着组织成自己的语言表达出来 不打算平铺直叙的写清 ...
- Vue 源码解析:深入响应式原理(上)
原文链接:http://www.imooc.com/article/14466 Vue.js 最显著的功能就是响应式系统,它是一个典型的 MVVM 框架,模型(Model)只是普通的 JavaScri ...
- 由浅入深,带你用JavaScript实现响应式原理(Vue2、Vue3响应式原理)
由浅入深,带你用JavaScript实现响应式原理 前言 为什么前端框架Vue能够做到响应式?当依赖数据发生变化时,会对页面进行自动更新,其原理还是在于对响应式数据的获取和设置进行了监听,一旦监听到数 ...
- 大白话Vue源码系列(05):运行时鸟瞰图
阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...
- vue3响应式原理以及ref和reactive区别还有vue2/3生命周期的对比,第二天
前言: 前天我们学了 ref 和 reactive ,提到了响应式数据和 Proxy ,那我们今天就来了解一下,vue3 的响应式 在了解之前,先复习一下之前 vue2 的响应式原理 vue2 的响应 ...
- 【Vue2.x源码系列07】监听器watch原理
上一章 Vue2计算属性原理,我们介绍了计算属性是如何实现的?计算属性缓存原理?以及洋葱模型是如何应用的? 本章目标 监听器是如何实现的? 监听器选项 - immediate.deep 内部实现 初始 ...
- java官网门户源码 SSM框架 自适应-响应式 freemarker 静态模版引擎
来源:http://www.fhadmin.org/webnewsdetail3.html 前台:支持(5+1[时尚单页风格])六套模版,可以在后台切换 官网:www.fhadmin.org 系统介绍 ...
- Mybaits 源码解析 (五)----- 面试源码系列:Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
刚开始使用Mybaits的同学有没有这样的疑惑,为什么我们没有编写Mapper的实现类,却能调用Mapper的方法呢?本篇文章我带大家一起来解决这个疑问 上一篇文章我们获取到了DefaultSqlSe ...
- vue2响应式原理与vue3响应式原理对比
VUE2.0 核心 对象:通过Object.defineProtytype()对对象的已有属性值的读取和修改进行劫持 数组:通过重写数组更新数组一系列更新元素的方法来实现元素的修改的劫持 Object ...
- 深入解析vue响应式原理
摘要:本文主要通过结合vue官方文档及源码,对vue响应式原理进行深入分析. 1.定义 作为vue最独特的特性,响应式可以说是vue的灵魂了,表面上看就是数据发生变化后,对应的界面会重新渲染,那么响应 ...
随机推荐
- Avalonia开发(一)环境搭建
一.介绍 开源 GitHub:https://github.com/AvaloniaUI/Avalonia/ 多平台支持,包括Windows.mac OS.Linux.iOS.Android.Sams ...
- Docker部署中间件
Docker 安装 1. 卸载旧版本 sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ d ...
- MPI转以太网模块连接300PLC与DCS modbus通信
MPI转以太网模块连接300PLC与DCS modbus通信 由300PLC通过MPI转以太网Plus模块作为modbus从站与DCS主站通信实现MPI转RTU与DCS通信 打开兴达易控提供的MPI转 ...
- Teamcenter RAC 开发之《PlaceHolder》
背景 做个swing表单,有时候想实现一些网页input标签的placeHolder提示,可能本人写vue or html写多,对某些细节有强迫症,所以找小下资料 实现方法(Swingx) 看源码
- C# 12 中的新增功能
新的 C# 12 功能在预览版中已经引入. 您可以使用最新的 Visual Studio 预览版或最新的 .NET 8 预览版 SDK 来尝试这些功能.以下是一些新引入的功能: 主构造函数 集合表达式 ...
- Android Studio3.2.1升级刨坑记录
Android Studio出了3.2.1,我用的是2.3,所有决定升级一下,看看如何 为了保险一点,下载了官方的解压版本,也就是说不含sdk,下载android-studio-ide-181.501 ...
- webwork学习
学习了H5中的webworker 主机 > 程序 > 进程 > 线程 > 纤程 多进程(重) 多线程(轻) 开销 创建.销毁开销大 创建.销毁开销小 安全性 进程之间是隔离 线 ...
- 源码搭建zabbix平台
1.基于lnmp部署zabbix监控平台; zabbix优点: 1.支持自动发现服务器和网络设备: 2.分布式的监控体系和集中式的WEB管理: 3.支持主动监控和被动监控模式: 4.基于SNMP.IP ...
- 系统RAM几乎爆满与解决方法
先说一遍,遇事不决就重启! 在电脑长时间运转下某些无良应用程序会产生大量的临时文件(目前我怀疑是有道云笔记) 最终导致系统爆炸 附图 在这种情况下,我下载了RAM实时监测我内存占用情况 结果发现pag ...
- 搭建LNMP
搭建LNMP 准备(关闭防火墙,selinux) systemctl stop firewalld systemctl disable firewalld setenforce 0 安装依赖包( ...