【源码系列#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的灵魂了,表面上看就是数据发生变化后,对应的界面会重新渲染,那么响应 ...
随机推荐
- 一个颜值功能双在线的Zookeeper可视化工具
大家好,我是 Java陈序员,今天给大家介绍一个颜值功能双在线的 Zookeeper 可视化工具. 项目介绍 PrettyZoo 是一个基于 Apache Curator 和 JavaFX 实现的 Z ...
- Solution -「CF 1477A」Nezzar and Board
Description Link. $ n $ distinct integers $ x_1,x_2,\ldots,x_n $ are written on the board. Nezzar ca ...
- ⭐volatile⭐ 用volatile关键字则会从内存中直接读取变量的值
- Util应用框架 7.x 来了
什么是Util应用框架? Util是一个.Net平台下的应用框架,旨在提升中小团队的开发能力,由工具类.分层架构基类.Ui组件,配套代码生成模板,权限等组成. Util应用框架 7.x介绍 Util应 ...
- 从零用VitePress搭建博客教程(1) – VitePress的安装和运行
1.从零用VitePress搭建博客说明(1) – VitePress的安装和运行 一.写在前面 最近在想更新一把自己的前端吧小博客,但发现wordPress版本停留在了5年之前,发现变化挺大,不支持 ...
- IDEA工具第二篇:自定义Java方法注释模板
网上搜索类似的文章有很多,但是一味的复制粘贴总会出现各种奇葩问题,最后然并卵!今天特意自己研究琢磨一下,将最终结果分享给大家,100%亲测可用. 一.说明 想必大家都用过Eclipse的方法注释生成, ...
- [ABC276Ex] Construct a Matrix
没有题解,所以来写一篇. Description 构造一个 \(N\times N\) 的矩阵 \(A\),其中 \(A_{i,j}\in {0,1,2}\),要求同时满足 \(Q\) 条限制. 每条 ...
- YbtOJ 质数与约数 4.统计元素
搜不到题解,乐. 由题意,\(a_i\) 对答案有贡献当且仅当 \(i\in{[l,r]}\) 且对于 \(\forall a_j| {a_i},j\notin[l,r]\). 则对于每个 \(a_i ...
- http协议与apache
http协议与apache 1.httpd协议 两台主机通信需要socket文件 yum insatll -y nc [root@localhost ~]#nc -l 8000 #主机1 ...
- sql server 截断和收缩所有用户数据库日志--用游标循环所有正常状态的用户数据库
在服务器运维活动中,我们经常需要做一项工具就是将好多年的用户数据库日志文件截断并收缩为最小,以节省大量的磁盘空间.当数据库只有一两个时可以手动操作,但数据库数量众多时,就需要采用sql脚本,批量化执行 ...