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)的更多相关文章

  1. vue 源码自问自答-响应式原理

    vue 源码自问自答-响应式原理 最近看了 Vue 源码和源码分析类的文章,感觉明白了很多,但是仔细想想却说不出个所以然. 所以打算把自己掌握的知识,试着组织成自己的语言表达出来 不打算平铺直叙的写清 ...

  2. Vue 源码解析:深入响应式原理(上)

    原文链接:http://www.imooc.com/article/14466 Vue.js 最显著的功能就是响应式系统,它是一个典型的 MVVM 框架,模型(Model)只是普通的 JavaScri ...

  3. 由浅入深,带你用JavaScript实现响应式原理(Vue2、Vue3响应式原理)

    由浅入深,带你用JavaScript实现响应式原理 前言 为什么前端框架Vue能够做到响应式?当依赖数据发生变化时,会对页面进行自动更新,其原理还是在于对响应式数据的获取和设置进行了监听,一旦监听到数 ...

  4. 大白话Vue源码系列(05):运行时鸟瞰图

    阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...

  5. vue3响应式原理以及ref和reactive区别还有vue2/3生命周期的对比,第二天

    前言: 前天我们学了 ref 和 reactive ,提到了响应式数据和 Proxy ,那我们今天就来了解一下,vue3 的响应式 在了解之前,先复习一下之前 vue2 的响应式原理 vue2 的响应 ...

  6. 【Vue2.x源码系列07】监听器watch原理

    上一章 Vue2计算属性原理,我们介绍了计算属性是如何实现的?计算属性缓存原理?以及洋葱模型是如何应用的? 本章目标 监听器是如何实现的? 监听器选项 - immediate.deep 内部实现 初始 ...

  7. java官网门户源码 SSM框架 自适应-响应式 freemarker 静态模版引擎

    来源:http://www.fhadmin.org/webnewsdetail3.html 前台:支持(5+1[时尚单页风格])六套模版,可以在后台切换 官网:www.fhadmin.org 系统介绍 ...

  8. Mybaits 源码解析 (五)----- 面试源码系列:Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)

    刚开始使用Mybaits的同学有没有这样的疑惑,为什么我们没有编写Mapper的实现类,却能调用Mapper的方法呢?本篇文章我带大家一起来解决这个疑问 上一篇文章我们获取到了DefaultSqlSe ...

  9. vue2响应式原理与vue3响应式原理对比

    VUE2.0 核心 对象:通过Object.defineProtytype()对对象的已有属性值的读取和修改进行劫持 数组:通过重写数组更新数组一系列更新元素的方法来实现元素的修改的劫持 Object ...

  10. 深入解析vue响应式原理

    摘要:本文主要通过结合vue官方文档及源码,对vue响应式原理进行深入分析. 1.定义 作为vue最独特的特性,响应式可以说是vue的灵魂了,表面上看就是数据发生变化后,对应的界面会重新渲染,那么响应 ...

随机推荐

  1. 「hdu - 5780」gcd

    link. 钦定 \(i>j\),研究得 \((x^i-1,x^j-1)\rightleftharpoons(x^i-x^j,x^j-1)\rightleftharpoons(x^j(x^{i- ...

  2. mpi转以太网Plus模块连接300PLC实现MPI转modbus通信

    西门子200/300PLC转以太网同时实现PPI/MPI/DP转modbus通信 产品简介 MPI-ETH-XD1.0plus是在MPI-ETH-XD1.0的基础上,以太网口增加了支持与西门子带网口P ...

  3. dms

          产品解决方案文档与社区免费试用定价云市场合作伙伴支持与服务了解阿里云       备案控制台 首页关系型数据库NoSQL数据库数据仓库数据管理工具向量数据库免费试用 个人     打卡 发 ...

  4. log4j2同步日志引发的性能问题

    1 问题回顾 1.1 问题描述 在项目的性能测试中,相关的接口的随着并发数增加,接口的响应时间变长,接口吞吐不再增长,应用的CPU使用率较高. 1.2 分析思路 谁导致的CPU较高,阻塞接口TPS的增 ...

  5. db-cdc之mysql 深入了解并使用binlog

    1.什么是binlog? 2.binlog可以用来干什么? 3.怎么样使用binlog? binlog是记录所有数据库表结构变更(例如CREATE.ALTER TABLE-)以及表数据修改(INSER ...

  6. GameFramework摘录 - 1. ReferencePool

    GameFramework是一个结构很优秀的Unity游戏框架,但意图似乎在构建可跨引擎的框架?对要求不高的小型个人(不专业)开发来说有些设计过度了,但其中的设计精华很值得学习. 首先来说一下其中的R ...

  7. [转]深入HBase架构解析

    HBase架构讲解非常清晰的一篇文章,转自 http://www.blogjava.net/DLevin/archive/2015/08/22/426877.htmlhttp://www.blogja ...

  8. Hyper-V中的虚拟机(Centos)安装FTP服务

    linux上是否装上了ftp服务命令: rpm -qa | grep vsftpd ,若没有安装(无显示版本号)则进行下一步 安装ftp服务,命令: yum -y install ftp vsftpd ...

  9. reverse--[HZNUCTF 2023 preliminary]easyAPK

    首先这是一个apk文件,一开始我是用jadx打开的,发现要aes加密啥的,后面我用jeb打开,发现账号和密码都已经解密出来了 真的很方便,然后根据代码逻辑判断,这应该是安卓程序的一个登录界面,接下来我 ...

  10. c#中责任链模式详解

    基本介绍:   "责任链"顾名思义,是指一个需要负责处理请求的链条.   每个链条节点都是一个单独的责任者,由责任者自己决定是否处理请求或交给下一个节点.   在设计模式中的解释则 ...