携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 25 天,点击查看活动详情
vue3 学习3 - ref函数
前言
- 之前学习了 setup 的基础使用;
- 今天记录一下学习
ref
的笔记;
开始
1. 遗留的问题
上一篇文章熟悉了 setup 的使用,我们知道了可以通过 setup 暴露数据给模板和组件实例。
但是还依旧存在一个问题:数据不是响应式的;
例如:
<template>
<div>
<div>你还有多少money :{{ num }}</div>
<button @click="add">点击我开始计数</button>
</div>
</template>
<script>
export default {
setup() {
let num = 0
function add() {
num++
console.log(num)
}
return {
num,
add,
}
},
}
</script>
不论我怎么点击按钮,可以看到控制台有输出数据,我们修改了num
,但是视图一直没有更新。
总结:
- 仅 setup 可以暴露数据,但是数据不是响应式的
2. ref()
想要 num 是响应式的,需要借助一个函数 ref()。 这里的 ref() 是一个函数,由 vue3 代码暴露出来。
官网对 ref() 的解释:”ref() 接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value
。“
2.1 基础使用
<template>
<div>
<!-- 4. 在模板中使用 -->
<div>你还有多少money :( num ){{ num }}</div>
<div>你还有多少money :( num.value ){{ num.value }}</div>
<button @click="add">点击我开始计数</button>
</div>
</template>
<script>
// 1. 从 vue 引入 ref 函数
import { ref } from 'vue'
export default {
setup() {
// 2. 参数传入的是 num.value 的默认值。
let num = ref(0)
// 3. ref返回的是一个对象,只有一个指向其内部的属性, `.value`
console.log(num) // RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 0, _value: 0}
function add() {
num.value++
console.log(num.value)
}
return {
num,
add,
}
},
}
</script>
总结:
- 想要使用 ref,需要从 vue 中引入;
- ref() 返回值是一个对象;
- ref() 可以传入参数,当做返回对象
.value
的默认值;- 返回值在 setup 中可以通过
.value
的形式去使用;- 返回值在模板 template 中,无需
.value
,模板即可解析;
2.2 ref()返回的对象
上面的示例代码,打印了 ref(0)
的返回值。官方介绍 ref()
是这样说的 :此对象只有一个指向其内部值的属性 .value
。
谷歌浏览器实际打印的:
{
"__v_isShallow": false,
"dep": {
"w": 0,
"n": 0
},
"__v_isRef": true,
"_rawValue": 0,
"_value": 0,
"value": 0
}
总结:
首先确实有 value 属性;
其他的开头带
_
的属性,可以理解它们为内部属性;
dep
可以理解它也为内部属性;
RefImpl
是什么意思?
全称
reference+implement
译为引用实现;打印示例代码的 num ,为什么展示
RefImpl
?因为 num 是 class RefImpl 的实例
console.log(num.__proto__.constructor.name) // 'RefImpl'
后续我一定会读一读
ref()
的源码;
2.3 ref()传参的类型
2.3.1 验证 ref() 传入不同数据类型的参数会如何
<script>
import { ref } from 'vue'
export default {
setup() {
/* 基础类型 */
let num = ref(0)
console.log('num', num.value) // num 0
let str = ref('tomato')
console.log('str', str.value) // str tomato
let bean = ref(false)
console.log('bean', bean.value) // bean false
let un = ref(undefined)
console.log('un', un.value) // un undefined
let nu = ref(null)
console.log('nu', nu.value) // nu null
let sym = ref(Symbol('hello'))
console.log('sym', sym.value) // sym Symbol(hello)
let bigNum = ref(10n)
console.log('bigNum', bigNum.value) // bigNum 10n
/* 引用类型 */
let obj = ref({ name: '番茄', age: '18' })
console.log('obj', obj.value) // obj Proxy {name: '番茄', age: '18'}
},
}
</script>
由示例代码可得:
- 基础类型的数据,使用
ref()
,会直接赋值给引用对象RefImpl 的.value
- 引用类型的数据,使用
ref()
,会给引用对象RefImpl 的.value
赋值一个 Proxy 的实例对象。
官方的说明:
“ref() 当值为对象类型时,会用 reactive()
自动转换它的 .value
。”
2.3.2 那如果是对象该如何获取对象中的属性。
代码
<template>
<div>
<input type="text" v-model="str" />
<div>obj.hobby:{{ obj.hobby }}</div>
<div>obj.hobby.game:{{ obj.hobby }}</div>
<button @click="addHobby">添加爱好</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
let str = ref('')
console.log('str', str.value)
let obj = ref({
name: '番茄',
age: '18',
hobby: {
game: ['荒野大镖客2'],
},
})
console.log('obj', obj.value.name)
function addHobby() {
obj.value.hobby.game.push(str)
}
return {
str,
obj,
addHobby,
}
},
}
</script>
最初展示
添加爱好后
总结
给 ref() 传入基础类型的数据,会直接赋值给 RefImpl实例对象的
.value
;给 ref() 传入引用类型的数据,通过
reactive()
转换再赋值给 RefImpl实例对象的.value
;
reactive()
后续会写,这里就理解为一个函数即可使用起来的方式也比较类似,相当于是直接替换 RefImpl实例对象的
.value
;所以在setup
中可以refImpl.value.xxx
操作数据;
2.4为什么在模板中使用 ref() 的返回值不需要 .value
刚刚的案例,在 setup 中操作数据是需要 .value 的,但是为啥模板中却不需要。
# 官方中文文档的解释是:
当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value。
# 我又去查看了英文的官方文档对应的原话是:
When refs are accessed as top-level properties in the template, they are automatically "unwrapped" so there is no need to use .value.
# 解包 英文官方是用的这个单词 unwrapped(无包装的)
我自己的理解:可能是模板引擎对这里做了处理,省略
.value
方便我们使用数据,后续看到具体的源码再做验证。
初次之外,还有一个细节需要注意,顶层属性:
举个例子:
<template>
<div>
<!-- 10011 -->
<div>str:{{ str + 1 }}</div>
<!-- 10086 -->
<div>obj.name:{{ obj.name }}</div>
<!-- [object Object]1 -->
<div>obj.name+1:{{ obj.name + 1 }}</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
let str = ref(10010)
let str2 = ref(10086)
return {
str,
obj: {
name: str2,
},
}
},
}
</script>
总结
综上所述:
- 需要数据响应式,可以借助 ref();
- ref() 返回值是一个 RefImpl 的实例对象;
- ref() 如果传入基础类型的数据,会直接赋值给 RefImpl 实例对象的 .value;
- ref() 如果传入引用类型的数据,会利用
reactive()
处理之后再赋值给 RefImpl 实例对象的 .value; - 在模板中读取顶层属性不需要
.value
疑惑
- ref() 实现原理,class RefImpl 中做了什么?
reactive()
是什么?
end
- 初步学习总结的笔记,表达不当请见谅。