目录
一、shallowReactive 与 shallowRef
一、shallowReactive 与 shallowRef
1.1 简介
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)
- shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
- 使用场景:
- 如果一个对象数据,结构比较深,但变化时知识外层属性变化,使用shallowReactive
- 如果一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换,使用shallowRef
1.2 简单案例练习
只允许修改name 和 age属性
<template>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>零用钱:{{ item.wallet.money }}</h2>
<button @click="name += '!'">姓名添加上!</button>
<button @click="age++">年龄增长</button>
<button @click="item.wallet.money++">零花钱增加</button>
</template>
<script>
import { reactive, toRef, toRefs, shallowReactive } from "vue";
export default {
name: "App",
setup() {
// 当只允许修改name和age属性时,使用shallowReactive
let person = shallowReactive({
name: "周星星",
age: 25,
item: {
wallet: {
money: 100,
},
},
});
return {
...toRefs(person),
};
},
};
</script>
二、readonly 与 shallowReadonly
2.1 简介
- readonly;让一个响应式数据变为只读的(深只读)
- shallowReadonly:让一个响应式数据变为只读的(浅只读)
- 应用场景:不希望数据被修改时
2.2 简单案例练习
- 将person响应式数据定义为深只读
<template>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>零用钱:{{ item.wallet.money }}</h2>
<button @click="name += '!'">姓名添加上!</button>
<button @click="age++">年龄增长</button>
<button @click="item.wallet.money++">零花钱增加</button>
</template>
<script>
import { reactive, toRef, toRefs, shallowReactive } from "vue";
export default {
name: "App",
setup() {
// 当只允许修改name和age属性时,使用shallowReactive
let person = reactive({
name: "周星星",
age: 25,
item: {
wallet: {
money: 100,
},
},
});
// 使用readonly - 深只读
person = readonly(person);
return {
...toRefs(person),
};
},
};
</script>
- 将person响应式数据定义为深只读
// 使用readonly - 深只读
// person = readonly(person);
// 使用shallowReadonly - 浅只读
person = shallowReadonly(person);
三、toRaw 与 markRaw
3.1 toRaw
- 作用:将一个由 reactive 生成的 响应式对象 转为 普通对象
- 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
简单案例练习
<template>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>零用钱:{{ item.wallet.money }}</h2>
<button @click="name += '!'">姓名添加上!</button>
<button @click="age++">年龄增长</button>
<button @click="item.wallet.money++">零花钱增加</button>
<button @click="showRawPerson">输出最原始的pserson</button>
</template>
<script>
import { reactive, toRefs, toRaw } from "vue";
export default {
name: "App",
setup() {
// 当只允许修改name和age属性时,使用shallowReactive
let person = reactive({
name: "周星星",
age: 25,
item: {
wallet: {
money: 100,
},
},
});
function showRawPerson() {
const p = toRaw(person);
p.age++;
console.log(p);
}
return {
...toRefs(person),
showRawPerson,
};
},
};
</script>
由图示可看出控制台输出了原始的person对象,且person对象内的属性发生改变时,并不会更新页面数据(即非响应式的)
3.2 markRaw
- 作用:标记一个对象,使其永远不会再成为响应式对象
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
案例练习
- 设置按钮以及点击事件为person对象添加game属性,并设置name 和 price
- 设置点击事件修改name和price
- 未使用markRaw
<template>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>零用钱:{{ item.wallet.money }}</h2>
<h2 v-show="person.game">游戏卡带:{{ person.game }}</h2>
<button @click="name += '!'">姓名添加上!</button>
<button @click="age++">年龄增长</button>
<button @click="item.wallet.money++">零花钱增加</button>
<button @click="showRawPerson">输出最原始的pserson</button>
<button @click="addGame">添加游戏卡带信息</button><br />
<button v-show="person.game" @click="person.game.name += '!'">
游戏名添加!
</button>
<button v-show="person.game" @click="changePrice">游戏价格上涨</button>
</template>
<script>
import { reactive, toRefs, toRaw, markRaw } from "vue";
export default {
name: "App",
setup() {
// 当只允许修改name和age属性时,使用shallowReactive
let person = reactive({
name: "周星星",
age: 25,
item: {
wallet: {
money: 100,
},
},
});
function showRawPerson() {
const p = toRaw(person);
p.age++;
console.log(p);
}
function addGame() {
let game = { name: "Splatoon3", price: 199 };
person.game = game;
}
function changePrice() {
person.game.price++;
console.log(person.game.price);
}
return {
person,
...toRefs(person),
showRawPerson,
addGame,
changePrice,
};
},
};
</script>
由图示可以看出来person.game内的 name 和 price 是可以被修改的,且界面会作出相应的变化,是响应式的
使用 markRaw 标记 game
function addGame() {
let game = { name: "Splatoon3", price: 199 };
// person.game = game;
person.game = markRaw(game);
}
由图示可看出,game对象被markRaw标记后,点击修改name以及price按钮后,name属性 和 price属性在界面都未更新,而控制台上有显示price被修改的信息
四、customRef
作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制
4.1 案例练习
实现防抖效果 - Vue3官方文档案例练习
首先看看无防抖效果时会出现的现象 - 点击频率超过定时器所定义的等待的毫秒数
由图示可以看到修改频率高于定时器所定义的等待的毫秒数时,数据会有个回退现象,导致数据积压在一起
<template> <input type="text" v-model="keyWorld" /> <h2>{{ keyWorld }}</h2> </template> <script> import { customRef } from "vue"; export default { name: "App", setup() { // 自定义一个ref - 名为:myRef function myRef(value, delay) { return customRef((track, trigger) => { return { get() { console.log(`从myRef容器读取数据,将${value}赋值给他`); track(); // 通知Vue追踪Value的变化 return value; }, set(newValue) { console.log(`把myRef容器中数据修改为:${newValue}`); setTimeout(() => { value = newValue; trigger(); // 通知Value去重写解析模板 }, delay); }, }; }); } let keyWorld = myRef("Hello World", 500); // 自定义的ref return { keyWorld }; }, }; </script>
防抖效果实现
<script> import { customRef } from "vue"; export default { name: "App", setup() { let timer; // 自定义一个ref - 名为:myRef function myRef(value, delay) { return customRef((track, trigger) => { return { get() { console.log(`从myRef容器读取数据,将${value}赋值给他`); track(); // 通知Vue追踪Value的变化 return value; }, set(newValue) { console.log(`把myRef容器中数据修改为:${newValue}`); clearTimeout(timer); timer = setTimeout(() => { value = newValue; trigger(); // 通知Value去重写解析模板 }, delay); }, }; }); } let keyWorld = myRef("Hello World", 500); // 自定义的ref return { keyWorld }; }, }; </script>
由图示可看出,只有在停止修改数据后,才会去重新解析模板
五、provide 与 inject
官方图示
作用:实现父组件与后代组件间通信
使用:父组件由一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据(一般在父组件与深层子组件间使用)
具体写法:
// ###父组件中
setup(){
.....
let data = reactive({属性1:'...',属性2:'...'})
provide('data',data)
.....
}
// ###后代组件中
setup(){
.....
const data = inject('data') // inject()内填写provide('自定义命名',data)中自定义命名
return(data)
.....
}
案例练习
- App.vue作为父组件,并设置一组数据,将其通过provide 传给深层子组件
- 子组件:Child.vue
- 深层子组件:DeepChild.vue
App.vue
<template> <div class="app"> <h3>App组件 - 父组件, {{ name }} -- {{ date }}</h3> <Child></Child> </div> </template> <script> import { provide, reactive, toRefs } from "vue"; import Child from "./components/Child.vue"; export default { name: "App", components: { Child }, setup() { let game = reactive({ name: "Splatoon3", date: "2022.7.29", }); provide("game", game); return { ...toRefs(game) }; }, }; </script> <style> .app { background-color: gray; padding: 10px; } </style>
Child.vue
<template> <div class="child"> <h3>Child组件 - 子组件</h3> <DeepChild></DeepChild> </div> </template> <script> import DeepChild from "./DeepChild.vue"; export default { name: "Child", components: { DeepChild }, }; </script> <style> .child { background-color: skyblue; padding: 10px; } </style>
DeepChild.vue
<template> <div class="DeepChild"> <h3>deepChild组件 - 深层子组件, {{ game.name }} -- {{ game.date }}</h3> </div> </template> <script> import { inject } from "vue"; export default { name: "DeepChild", setup() { const game = inject("game"); return { game }; }, }; </script> <style> .DeepChild { background-color: orange; padding: 10px; } </style>
六、响应式数据的判断
Vue3官方文档 - isRef、isReactive、isReadonly、isProxy
- isRef:检查一个值是否为ref对象
- isReactive:检查一个值是否由 reactive 创建的响应式代理
- isReadonly:检查一个对象是否由 readonly 创建的只读代理
- isProxy:检查一个对象是否由 reactive 或者 readonly 方法创建的代理