什么是ref?
ref和reactive一样,也是用来实现响应式数据的方法,由于reactive必须传递一个对象,所以导致在企业开发中如果我们只想让某个变量实现响应式的时候会非常麻烦,所以Vue 3 就给我们提供了ref方法,实现对简单值的监听
ref 本质
ref 底层的本质其实还是reactive,系统会自动根据我们给ref传入的值将它转换成ref(xx)->reactive({value:xx})
ref 注意
在Vue 中使用ref的值不用通过value获取,在JS中使用ref的值必须通过value
ref 和 reactive 的区别
如果在template 里使用的是ref类型的数据,那么Vue会自动帮我们添加.value
如果在template 里使用的是reactive类型的数据,那么Vue不会添加.value
vue是如何决定是否需要自动添加.value的
Vue 在解析数据之前,会自动判断这个数据是否是ref类型的,如果是就自动添加.value,如果不是就不会添加.value
Vue 是如何判断当前的数据是否是ref类型的
通过当前数据的__v_ref
来判断的,如果有这个私有属性,并且值为true,那么就代表是一个ref类型的数据
递归监听
1.默认情况下,无论是通过ref还是reactive都是递归监听
2.递归监听存在的问题:如果数据量比较大非常消耗性能
非递归监听
shallowReactive 监听第一层的变化
shallowRef 监听是.value的变化,并不是第一层的变化
如何触发非递归监听属性更新界面?
如果是shallowRef类型数据,可以通过tiggerRef来触发
应用场景
一般情况下我们使用ref和reactive,只有需要监听的数据量比较大的时候,才使用shallowRef和shallowReactive
let state = shallowRef({
a:{
b:{
c:{
d:1
}
}
}
})
state.vlaue.a.b.c.d =2
triggerRef(state)
toRaw、markRaw、toRef、toRefs、customRef
toRaw :从Reactive 或 Ref中得到原始数据
不需要更新UI界面,通过toRaw方法对原始数据修改
注意点:通过toRaw获取ref类型的原始数据,要获取的是.value的值,因为经过Vue处理之后,.value中保存的才是原始数据
markRaw :不会追踪数据的变化
toRef:引用原始数据,改变原始数据,不会触发UI更新
ref 和toRef 区别
ref -> 复制,修改响应式数据不会影响原始数据,
toRef -> 引用,修改响应式数据会影响原始数据
ref-> 数据发生改变,界面就会自动更新
toRef-> 数据发生改变,界面不会自动更新
// ref
let obj = {name:"frank"}
let stateRef = ref(obj.name)
stateRef.value = "Bob"
// toRef
let stateToRef = toRef(obj,"name")
stateToRef.value = "Bob"
toRef 应用场景
如果想让响应式数据和原始数据关联起来,并且更新响应式数据之后不想更新UI,那么可以使用toRef
toRefs :把对象的所有属性都变成响应式数据,不会更新UI
let obj = {name:"frank",age:18}
let stateToRefs = toRefs(obj)
stateToRefs.name.value = "Bob"
stateToRefs.age.value = "20"
customRef:返回一个ref对象,可以显式地控制依赖追踪和触发响应
function myRef(value){
return customRef((track,trigger)=>{
return {
get(){
track() // 追踪变化
return value
},
set(newValue){
value= newValue
trigger() // 触发界面更新
}
}
})
}
let state = myRef(18)
state.value = 19
应用场景
发送网络请求
function myRef(value){
return customRef((track,trigger)=>{
fetch(value).then(res=>{
return res.json()
}).then((data)=>{
value = data
trigger()
}).catch((err)=>{
console.log(err)
})
return {
get(){
track() // 追踪变化
return value
},
set(newValue){
value= newValue
trigger() // 触发界面更新
}
}
})
}
let state = myRef('http://api.xxx.com')
获取元素
在Vue 2 中我们通过给元素添加ref=“xxx”,然后再代码中通过this.$refs.xxx
的方式获取元素
在Vue 3 中我们也可以通过ref来获取元素
// html
// <div ref="box">ref</div>
setup(){
let box = ref(null)
onMounted(()=>{
console.log(box.value)
})
return {box}
}
readonly、shallowReadonly、isReadonly
readonly:用于创建一个只读数据,并且是递归只读
shallowReadonly:用于创建一个只读数据,并且只有第一个数据是只读的
isReadonly:判断是不是只读数据
const 和 readonly 的区别
const:赋值保护,不能给变量重新赋值
readonly:属性保护,不能给属性重新赋值
响应式数据本质
在Vue 2中是通过defineProperty 来实现响应式数据的
在Vue3 中通过Proxy 来实现响应式数据的
let obj={name:"frank",age:18}
let state = new Proxy(obj,{
get(obj,key){
return obj[key]
},
set(obj,key,value){
obj[key] = value
return true
}
})
state.name = "Bob"
手写实现
function shallowReactive(obj){
return new Proxy(obj,{
get(obj,key){
return obj[key]
},
set(obj,key,value){
obj[key]=value
return true
}
})
}
function shallowRef(val){
return shallowReactive({value:val})
}
function reactive(obj){
if(typeof obj === "object"){
if(obj instanceof Array){
obj.forEach((item,index)=>{
if(typeof item === "object"){
obj[index]=reactive(item)
}
})
}else{
for(let key in obj){
let item = obj[key]
if(typeof item === "object"){
obj[key]=reactive(item)
}
}
}
return new Proxy(obj,{
get(obj,key){
return obj[key]
},
set(obj,key,value){
obj[key]=value
return true
}
})
}else{
console.wran(`${obj} is not object`)
}
}
function ref(val){
return reactive({value:val})
}
function shallowReadonly(obj){
return new Proxy(obj,{
get(obj,key){
return obj[key]
},
set(obj,key,value){
console.wran(`${key} 是只读的,不能赋值`)
}
})
}
function readonly(obj){
if(typeof obj === "object"){
if(obj instanceof Array){
obj.forEach((item,index)=>{
if(typeof item === "object"){
obj[index]=reactive(item)
}
})
}else{
for(let key in obj){
let item = obj[key]
if(typeof item === "object"){
obj[key]=reactive(item)
}
}
}
return new Proxy(obj,{
get(obj,key){
return obj[key]
},
set(obj,key,value){
console.wran(`${key} 是只读的,不能赋值`)
}
})
}else{
console.wran(`${obj} is not object`)
}
}
组件
属性绑定
- 默认所有属性都绑定到根元素
- 使用
inheritAttrs:false
可以取消默认绑定 - 使用
$attrs
或者context.attrs
获取所有属性 - 使用
v-bind=$attrs
批量绑定属性 - 使用
const {size,...rest} = context.attrs
将属性分开
<template>
<div :size="size">
<button v-bind="rest">
<slot />
</button>
</div>
</template>
<script>
export default {
inheritAttrs: false,
setup(props, context) {
const { size, ...rest } = context.attrs;
console.log(size, rest);
return {
size,
rest,
};
},
};
</script>
props 和 attrs 区别
- props 要先声明才能取值,attrs 不用先声明
- props 不包含事件,attrs 包含
- props 没有声明的属性,会跑到 attrs 里
- props 支持 string 以外的类型,attrs 只有string 类型
内部数据和父子数据
内部数据
setup(){
const checked = ref(false)
const toggle = ()=>{
checke.value = !check.value
}
return {
checked,
toggle
}
}
父子数据
setup(props,context){
const toggle = ()=>{
context.emit('input',!props.value)
}
return {
toggle
}
}
Vue 3 v-model
新写法:
<Switch :value="checked" @update:value="checked = $event" />
// 等同于
<Switch v-model:value="checked" />
// Switch
setup(props,context){
const toggle = context.emit('update:value',!props.value)
return {
toggle
}
}
如何确认子组件的类型
检查 context.slots.default()
数组
//Tabs
setup(props,context){
const defaults = context.slots.default()
defaults.forEach(tag=>{
if(tag.type.name !== Tab.name){
throw new Error('Tabs 子组件必须是Tab')
}
})
}
// Tab
<template>
<div>
<slot />
</div>
</template>
// App
<Tabs>
<Tab title="导航一">内容一</Tab>
<Tab title="导航二">内容二</Tab>
</Tabs>
网友评论