前端时间一直在研究Vue3,最近总算把笔记整理完了,在此记录下来,以备查阅。
虽然这篇内容不多,但是私以为看完这篇,对着代码写写,基本上也就入门Vue3了。
Vue3学习笔记:
1. setup生命周期等
在Vue3中,有一个setup函数,是新的组件选项,作为composition API的入口。
1. setup的调用时机:
-
创建组件实例,然后初始化props,接着调用setup函数。从生命周期来看,它是在beforeCreate之前被调用的。所有的组合API函数都在此使用,并且只被执行一次。
-
setup中的this是undefined,不能通过this来访问data/computed/methods/props,包括所有的组合式API中的相关回调函数都不可以
2. setup函数返回的是一个对象,对象中的属性或方法,可以直接使用。
如下代码中,setup返回了两个属性,一个是count变量,一个是updateCount函数,可以直接使用。
<template>
{{count}}
<br/>
<button @click="updateCount">更新数据</button>
</template>
<script lang='ts'>
import {defineComponent, ref} from 'vue';
export default defineComponent ({
name: 'App',
setup(){
// html 模板中不需要.value属性的写法
let count = ref(11);
function updateCount(){
count.value++ ;
}
return {
count, // 返回的count属性可以直接使用
updateCount, // 函数也在此返回
}
}
})
</script>
另外,vue2中的data返回的配置项,也可以用在vue3中,用法与在Vue2中一致。但是并不推荐这样做。
同样的,vue2中定义method的方式,也可以用在vue3中,官方也不推荐这样使用。
methods中可以访问setup提供的属性和方法,但是setup中 无法访问data和methods。
setup不能是一个async函数,因为返回值不再是return对象,而是promise,模板看不到return对象中的属性数据。
3. setup参数:
写法:setup(props,context){} 或 setup(props,{attrs,slots,emit}){}
props: 包含了props配置中声明并传入的所有属性对象
attrs:包含没有在props中声明的所有属性对象,相当于this.$attrs
slots:包含所有传入的插槽对象,相当于this.$slots
emit:用来分发自定义事件函数,相当于this.$emit
2. 异步引入组件
异步引入组件的方法很多,在这里记录下3个:
- defineAsyncComponent异步加载组件
<template>
<AsyncCom />
</template>
import {defineComponent, defineAsyncComponent} from 'vue';
// Vue3中静态引入组件的方法
const AsyncCom = defineAsyncComponent(()=>import('./AsyncCom.vue'));
export default defineComponent({
components:{ AsyncCom },
setup(){
return {}
},
});
- setup中返回promise对象
export default defineComponent({
setup(){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve({
msg:'Hello world!'
});
},2000);
})
}
});
- setup和async/await结合使用
前面说过,setup不能是async函数,那是因为返回的是一个promise对象,模板看不到返回对象中的数据结构,所以需要把结果处理成模板可识别的结构,方能使用。
如下代码所示:
export default defineComponent({
// setup(){
// return axios.get('/data/address.json').then(response=>{
// return {
// data: response.data
// }
// });
// }
async setup(){
const result = await axios.get('/data/address.json');
return {
data: result.data
}
}
})
3. 数据代理源码分析
Vue2中数据代理用的是Object.defineProperty方法,Vue3中是通过Proxy和Reflect结合,实现数据代理的。proxy比Object.defineProperty高效,响应也更快。
代码见下:
// 模拟Vue3中的数据代理
let user = {
name: '路飞',
age: 20,
wife:{
name: '小樱',
age: 19,
};
const proxyUser = new Proxy(user,{
get(target,prop){
console.log('get方法调用了!');
return Reflect.get(target,prop);
},
// 修改目标对象的属性值/为目标对象添加新的属性
set(target,prop,value){
return Reflect.set(target,prop, value);
},
// 删除目标对象上的某个属性
deleteProperty(target,prop){
console.log('delete方法被调用了!');
return Reflect.deleteProperty(target, prop);
}
});
proxyUser.name = '路飞';
proxyUser.gender = '男';
delete proxyUser.gender;
proxyUser.wife.name = '小红';
4. ref和reactive的基本使用
Vue3中有两个新的API,ref和reactive都是用来把数据转换成响应式数据,ref用来处理基本数据,reactive用来处理对象(递归深度响应式数据)。
ref也可以传入对象类型的数据,其内部会自动调用reactive,把对象转为响应式数据。
ref:内部会给value添加getter和setter来实现数据劫持,在js/ts中,ref对象的值,需通过.value操作,而在模板中则不需要添加.value。
reactive:内部通过Proxy来实现对象内部所有属性的劫持,并通过Reflect操作对象内部数据。
<template>
<h1>用户1</h1>
<h2>姓名:{{ user.name }}</h2>
<h2>年龄:{{user.age}}</h2>
<h2>性别:{{user.gender}}</h2>
<h2>wife: {{user.wife}}</h2>
<button @click="updateUser">更改数据</button>
<h2>App父级组件</h2>
<h3>{{msg}}</h3>
<button @click="msg+='='">Update</button> // 模板中不用调用msg.value即可操作ref中的数据
</template>
import { defineComponent,reactive,ref } from 'vue';
export default defineComponent({
name: 'App',
setup(){
const obj: any = { // 为了在使用obj.gender = "男"的时候不出现这种错误提示
name:"香吉士",
age: 25,
wife:{
age: 22,
gender: 'female',
name: '娜美',
car: ['法拉利','奔驰'],
}
};
const user = reactive(obj);
const msg = ref('How are you,today?');
const updateUser = ()=>{
// 通过当前的代理对象找到该对象中的某个属性,更改该属性中的某个数组的数据
user.wife.age=23;
user.wife.car[1]='玛莎拉蒂';
user.wife.car[2]='奥拓';
// 总结:如果操作代理对象,目标对象中的数据也会随之变化,同时如果想要在操作数据的时候,界面也要跟着重新更新渲染,那么也是操作代理对象
};
return {
user,
msg,
updateUser
}
}
});
5. computed计算属性
Vue3中的计算属性依然依赖于缓存,有getter和setter函数,也有简写形式,但是不用像Vue2中那样写在computed配置项中,而是写在setup中,作为组合式API之一返回,如下所示:
<template>
<div class="demo">
<h1>我是computed演示组件</h1>
用户1FirstName:<input v-model="person1.firstName" /><br />
用户1LastName:<input v-model="person1.lastName" />
全名<h1>{{person1.fullName}}</h1>
<hr/>
用户2FirstName:<input v-model="person2.firstName" /><br />
用户2LastName:<input v-model="person2.lastName" />
全名<h1>{{person2.fullName}}</h1>
</div>
</template>
<script>
import {reactive,computed} from 'vue';
export default {
name: 'Demo',
props:['msg','school'],
emits:['hello'],
setup(props, context){
let person1 = reactive({
firstName:'尼古拉斯',
lastName:'赵四',
age: 18,
});
let person2 = reactive({
firstName:'郭达',
lastName:'斯坦森',
age: 19,
});
// 1.计算属性 简写形式的computed
person1.fullName = computed(()=>{
return person1.firstName + person1.lastName;
});
// 2.计算属性 完整形式的computed
person2.fullName = computed({
get(){
return person2.firstName +'-'+ person2.lastName;
},
set(value){
const nameArr = value.split('-');
person2.firstName = nameArr[0];
person2.lastName = nameArr[1];
}
});
return {person1,person2}
},
}
</script>
6. watch和watchEffect
Vue3中的 watch,也是写在setup中,监视输出对象。
watch在页面首次渲染的时候不会调用,需指定immediate属性的值,才能执行首次监听。
watch可以取到更新前后的值。
另外,当watch监听reactive定义的数据时,会强制开启深度监听,也就是说,此时设置deep参数无效,无论是true或false,都会进行深度监听。
上代码:
<template>
<div class="demo">
<h1>sum:{{sum}}</h1><button @click="sum++">加一</button>
<hr/>
<h1>消息:{{msg}}</h1><button @click="msg+='!'">修改信息</button>
<hr/>
<h1>姓名:{{person.name}}</h1><button @click="person.name+='~'">修改姓名</button>
<h1>年龄:{{person.age}}</h1><button @click="person.age+=1">修改年龄</button>
<h1>薪资:{{person.job.j1.salary}}K</h1><button @click="person.job.j1.salary+=1">修改薪资</button>
<hr/>
<h1>{{person1.name}}</h1>
<h1>{{person1.age}}</h1>
<h1>{{person1.job.j2.salary}}</h1>
<button @click="person1.job.j2.salary++">加薪</button>
</div>
</template>
<script >
import {reactive,ref,watch} from 'vue';
export default {
name: 'Demo',
setup(props, context){
let sum = ref(0);
let msg = ref('你好啊');
let person = reactive({
name: '张三',
age: 18,
job:{
j1:{salary: 20}
}
});
let person1 = ref({
name:'李四',
age:25,
job:{j2: {salary: 20}}
})
// 情况一:监视ref所定义的一个响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变了',newValue, oldValue);
},{immediate: true})
// 情况二:监视ref所定义的多个响应式数据
watch([sum,msg],(newValue, oldValue)=>{
console.log('修改了sum 或 msg',newValue, oldValue);
},{immediate: true})
// 情况三:监视reactive所定义的一个响应式数据,
// 注意:
// 1.这里无法正确得获取oldValue
// 2.强制开启了深度监视(deep配置无效)
watch(person,(newValue, oldValue)=>{
console.log('修改了person',newValue, oldValue);
},{deep: false}); //此处的深度监视无效
// 情况四:监视reactive所定义的一个响应式数据中的某个属性
watch(()=>person.name, (newValue, oldValue)=>{
console.log('修改了person中的name属性',newValue,oldValue);
})
// 情况五:监视reactive所定义的一个响应式数据中的某些属性
watch([()=>person.name,()=>person.age],(newValue, oldValue)=>{
console.log('修改了person中的name或age属性',newValue, oldValue);
});
// 特殊情况
watch(()=>person.job,(newValue, oldValue)=>{
console.log('person的job变化了,',newValue, oldValue);
}, {deep: true}) // 此处用于深度监视的是reactive定义的对象中的某个属性,所以deep配置有效
// watch
watch(person1,(newValue, oldValue)=>{
console.log('person1发生了变化',newValue, oldValue);
},{deep: true})
return {sum,msg,person,person1}
},
}
</script>
watchEffect不需要添加immediate参数即可实现首次监听,只要数据有更新便执行,但其无法取到更新前的原值。用法如下代码所示:
import {reactive,computed,ref,watchEffect} from 'vue';
export default {
name: 'Demo',
emits:['hello'],
setup(){
let sum = ref(0);
let msg = ref('你好啊');
let person = reactive({
name: '张三',
age: 18,
job:{
j1:{salary: 20}
}
});
watchEffect(()=>{
let x = person.name;
let y = person.job.j1.salary;
})
return {sum,msg,person}
},
}
7. props和context
props属性传值和context上下文,与Vue2中用法略有不同,props须在setup里作为参数传递给组合式API,上代码:
// Parent组件
<template>
<div class="app">
<h1>Computed</h1>
<Demo msg="你好啊" school="南加州大学" @hello="sayHello">
</Demo>
</div>
</template>
<script>
import Demo from './components/Demo.vue';
export default {
name: 'App',
components:{Demo},
setup(){
function sayHello(value){
alert(`你好啊,你触发了hello事件,收到的参数是${value}`);
}
return{
sayHello
}
}
}
</script>
// Child组件
<template>
<div class="demo">
<h1>我是Demo组件</h1>
<h1>{{person.name}}</h1>
<h1>{{person.age}}</h1>
<h1>{{props.school}}</h1>
<button @click="testEmit">触发hello事件</button>
</div>
</template>
<script>
import {reactive} from 'vue';
export default {
name: 'Child',
props:['msg','school'],
emits:['hello'],
setup(props, context){ // 此处需声明props,context,下文才能引用
let person = reactive({
name:'张三',
age: 18,
msg: props.msg
});
console.log(context.slots);
console.log(context.attrs);
console.log(context.emits);
function testEmit(value){
context.emit('hello',6666); // 触发父级传来的自定义事件
}
return {person, testEmit}
},
}
</script>
8. hooks钩子
hooks钩子就是使用Vue3中组合式API封装的可复用的功能函数,类似于Vue2中的mixins。钩子的命名通常以use+‘XXX’开头,所以更方便识别其用途。
<template>
<h2>自定义hook函数操作</h2>
<h2>x:{{x}},y:{{ y}}</h2>
<h3 v-if="loading">正在加载中……</h3>
<h3 v-else-if="errorMsg">错误信息:{{errorMsg}}</h3>
<!-- <ul v-else>
<li>id:{{data.id}}</li>
<li>address:{{data.address}}</li>
<li>distance:{{data.distance}}</li>
</ul>
<hr /> -->
<!--数组数据-->
<ul v-for="item in data" :key="item.id">
<li>id:{{item.id}}</li>
<li>title:{{item.title}}</li>
<li>price:{{item.price}}</li>
</ul>
</template>
<script lang="ts">
import { defineComponent, onMounted,onBeforeUnmount, ref} from 'vue';
import useMousePosition from './hooks/useMousePosition';
import useRequest from './hooks/useRequest';
// 定义接口,约束对象的类型
interface AddressData{
id: number,
address: string,
distance: string,
}
interface ProductData{
id: string,
title: string,
price: number,
}
export default defineComponent({
name: 'App',
// 需求1:用户在页面中点击页面,把点击的位置的横纵坐标收集起来并展示出来
setup(){
const {x,y} = useMousePosition();
// 发送请求
const {loading, data, errorMsg} = useRequest<AddressData>('/data/address.json'); // 获取对象数据
const {loading, data, errorMsg} = useRequest<ProductData[]>('/data/products.json'); // 获取对象数据
return {
x,y,
loading, data, errorMsg
}
}
});
</script>
钩子 useRequest,用于通过axios请求数据,并把数据传递返回出来。
import {ref} from 'vue';
import axios from 'axios';
// 发送ajax请求
export default function<T> (url: string){
// 加载的状态
const loading = ref(true);
// 请求成功的数据
const data = ref<T | null>(null);
// 错误信息
const errorMsg = ref('');
axios.get(url).then(response =>{
// 改变加载状态
loading.value = false;
data.value = response.data;
}).catch((error:any)=>{
// 改变加载状态
loading.value =false;
errorMsg.value = error.msg || '未知错误';
});
return {
loading,
data,
errorMsg
}
}
钩子userMousePosition,用于获取鼠标点击时的屏幕坐标。
import {onMounted,onBeforeUnmount, ref} from 'vue';
export default function() {
// 需求1:用户在页面中点击页面,把点击的位置的横纵坐标收集起来并展示出来
const x = ref(0);
const y = ref(0);
const clickHandler = (event:MouseEvent)=>{
x.value = event.pageX;
y.value = event.pageY;
};
// 页面已经加载完成了,再进行点击的操作
// 页面加载完毕的声明周期组合API
onMounted(()=>{
window.addEventListener('click', clickHandler);
})
// 页面卸载之前的声明周期组合API
onBeforeUnmount(()=>{
window.removeEventListener('click',clickHandler);
})
return {
x,y
}
}
9. toRefs和toRef
- toRefs的作用是,把一个响应式对象转换成普通对象,该对象的每一个property都是ref。
应用场景:当从合成函数返回响应式对象的时候,toRefs非常有用,可以在不丢失响应式的情况下对返回的对象进行分解应用。
如下代码:state返回一个包含name和age的对象,如果直接同reactive解构,数据是无法实现动态响应的,但是toRefs可以。
<template>
<h2>reactive解构</h2>
<h3>name:{{name}}</h3>
<h3>age: {{age}}</h3>
<h3>toRefs返回的</h3>
<h3>name1:{{name1}}</h3>
<h3>age1:{{age1}}</h3>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive} from 'vue';
export default defineComponent({
name: 'App',
setup(){
const state = reactive({
name: '小龙人',
age: 500
});
const {name, age} = toRefs(state);
// 定时器,更新数据,如果数据变化了,界面也会随之变化,肯定是响应式的数据
setInterval(()=>{
state.name += '==='
},2000);
return {
// state
...state // 不是响应式的数据了
name1:name,
age1:age,
}
}
});
</script>
进一步对比,toRefs和reactive。toRefs可以生成属性也是响应式的对象,而reactive本身是响应式,当其属性解构以后就不再是响应式了。
<template>
<h2>toRefs的使用</h2>
<h3>name:{{name}}</h3>
<h3>age: {{age}}</h3>
<hr/>
<h3>name2:{{name2}}</h3>
<h3>age2: {{age2}}</h3>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive} from 'vue';
function useFeatureX(){
const state =reactive({
name2: '小飞侠',
age2:23
});
return {
...toRefs(state)
}
}
export default defineComponent({
name: 'App',
setup(){
const state = reactive({
name: '小龙人',
age: 500
});
// toRef可以把一个响应式对象转换成普通的对象,该普通对象的每个property都是一个ref
// const state2 = toRefs(state)
const {name, age} = toRefs(state);
// 定时器,更新数据,如果数据变化了,界面也会随之变化,肯定是响应式的数据
setInterval(()=>{
// state.name += '==='
// state2.name.value += '=='
name.value += '='
},2000);
const {name2, age2} =useFeatureX();
return {
// state
// ...state // 不是响应式的数据了
// ...state2
name,
age,
name2,
age2
}
}
});
</script>
- toRef
toRef顾名思义,就是把对象里的属性转为ref对象,即保留了响应式也保留了引用。代码如下所示:
<template>
<h2>toRaw 和 markRow</h2>
<hr/>
{{ state }}<br/>
<hr/>
年龄:{{age}}<br/>
存款:{{money}}<br/>
<button @click="update">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, ref, reactive, toRef} from 'vue';
export default defineComponent({
name: 'App',
setup(){
const state = reactive({
age:5,
money: 20
});
// 把响应式数据state对象中的属性age变成了ref对象了
const age = toRef(state,'age');
// 把响应式对象中的属性使用ref进行包装,变成了一个ref对象
const money = ref(state.money);
const update = ()=>{
// 更新数据
age.value += 2; // 效果等同于 state.age += 2;
money.value +=4;
}
return {
state,
age,
money,
update
}
}
});
</script>
10. shallowReactive和shallowRef
shallow意为浅层,shallowReactive和shallowRef顾名思义,就是只监听对象的浅层属性,浅层属性为响应式数据,深层数据非响应式。
shallowReactive只处理对象的最外层属性。
shallowRef只处理.value的响应式,不进行对象的reactive处理。
什么情况下使用shallowReactive和shallowRef呢?
如果有一个对象数据,结构较深,但是变化只是外层属性变化,可以使用shallowReactive。
如果一个对象数据,后面会产生新的对象来替换,可以使用shallowRef。
看下面的代码:
<template>
<h2>shallowReactive 和 shallowRef</h2>
<h3>m1:{{m1}}</h3>
<h3>m2:{{m2}}</h3>
<h3>m3:{{m3}}</h3>
<h3>m4:{{m4}}</h3>
<hr/>
<button @click="update">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive, ref,shallowReactive, shallowRef} from 'vue';
export default defineComponent({
name: 'App',
setup(){
// 深度劫持(深监视)---深度响应式
const m1 = reactive({
name: '小红',
age: 20,
car:{name: '奔驰', color: 'red' }
});
// 浅劫持(浅监视)--- 浅响应式
const m2 = shallowReactive({
name: '小黄',
age: 22,
car: { name: '奔驰', color: 'yellow'}
});
// 深度劫持(深监视)---深度响应式---做了reactive的处理
const m3 = ref({
name: '小兰',
age: 22,
car: {name: '宝马',color:'blue'}
});
// 浅接触(浅监视)---浅响应式
const m4 = shallowRef({
name: '小白',
age: 22,
car:{name: '宝马', color:'white'}
});
const update=()=>{
// 更改m1数据 --- reactive方式
m1.name +='==';
m1.car.name += '=='
// 更改m2的数据---shallowReactive
m2.name += '==';
m2.car.name += '==' // 此处无法改变其值
// 更改m3的方式---ref方式
m3.value.name+= '='
// 更改m4的方式---shallowRef方式
m4.value.name +='='
m4.value.car.name += '='; // 此处无法改变其值。
console.log(m3,m4);
}
return {
m1,
m2,
m3,
m4,
update
}
}
});
</script>
11. readonly和shallowReadonly
readonly顾名思义为只读,读取一个对象(响应式或纯对象)或ref并返回原始代理的只读代理。只读代理是深层的,嵌套的任何property都是只读的。
shallowReadonly则是把浅层属性转为只读,对于深层数据仍然可修改。
应用场景:
在某些特定情况下,我们可能不希望对数据进行更新的操作,那就可以包装生成一个只读代理对象来读取数据,而不能修改或删除
<template>
<h2>readonly 和 shallowReadonly</h2>
<h3>m1 原数据:{{m1}}</h3>
<h3>m2 readonly:{{m2}}</h3>
<h3>m3 shallowReadonly:{{m3}}</h3>
<hr/>
<button @click="update">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, readonly,shallowReadonly, reactive, ref} from 'vue';
export default defineComponent({
name: 'App',
setup(){
const m1 = reactive({
name: '小红',
age: 20,
car:{name: '奔驰', color: 'red' }
});
const m2 = readonly(m1);
const m3 = shallowReadonly(m1);
const update=()=>{
m1.name += '=='
m2.value.car.name += '==='
}
return {
m1,
m2,
m3
update
}
}
});
</script>
12. toRaw和markRaw
toRaw和markRaw的作用,都是把响应式代理数据,转为普通对象,对于这个对象的所有操作,不会引起页面的更新。
toRaw用于接收一个由reactive创建的响应式对象,并将这个响应式数据转为普通对象。
toRaw()可以返回由reactive(),readonly(),shallowReactive()或shallowReadonly()创建的代理对应的原始对象
适用于用于临时读取,改变数据,但不引起界面更新的场景。
markRaw 标记一个对象,使其永远不会成为响应式对象,当其中的属性发生改变时,不会引起界面更新。
实际上markRaw是把代理对象又返回了普通对象本身。
适用于:
1.有些值不应该被设置为响应式的对象,例如复杂的第三方类库或Vue组件对象
2.当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
以下为代码示例:
<template>
<h2>toRaw 和 markRow</h2>
<h3>{{state}}</h3>
<hr>
<button @click="testToRaw">测试toRaw</button>
<button @click="testMarkRaw">测试MarkRaw</button>
</template>
<script lang="ts">
import { defineComponent, markRaw, reactive, toRaw} from 'vue';
interface userInfo{
name: string;
age: number;
hobby?: string[];
};
export default defineComponent({
name: 'App',
setup(){
const state = reactive<userInfo>({
name: '小花',
age: 20
});
const testToRaw = ()=>{
const user = toRaw(state);
user.age ++;
}
const testMarkRaw = ()=>{
const hobby = ['吃','喝'];
state.hobby = markRaw(hobby);
setInterval(()=>{
state &&state.hobby &&state.hobby[0]+= '=='
},1000);
}
return {
state,
testToRaw,
testMarkRaw
}
}
});
</script>
13. customRef的使用
customRef用于创建一个自定义ref,并对其依赖跟踪和更新触发进行显示控制。
接收一个工厂函数,其中两个参数:trace用于追踪,trigger用于触发,并返回一个带有get和set属性的对象。
上代码:
<template>
<h2>CustomRef的使用</h2>
<input type="text" v-model="keyword"/>
<p>{{ keyword }}</p>
</template>
<script lang="ts">
import { customRef, defineComponent} from 'vue';
// 自定义hook防抖函数
// value传入的数据,将来数据的类型不确定,所以,用泛型,delay防抖的间隔时间是,200毫秒。
function useDebouncedRef<T>(value:T, delay=200){
console.log(value,delay);
let timeOutId: number;
return customRef((track, trigger)=>{
return {
// 返回数据
get(){
// 告诉数据追踪
track();
return value;
},
// 设置数据
set(newValue:T){
// 清理定时器
clearTimeout(timeOutId);
// 开启定时器
timeOutId =setTimeout(()=>{
value = newValue;
// 告诉界面更新
trigger();
}, delay)
}
}
})
}
export default defineComponent({
name: 'App',
setup(){
const keyword = useDebouncedRef('abc',500);
return {
keyword
}
}
});
</script>
14. isRef、isReactive、isReadonly、isProxy
isRef:检查一个值是否为ref,
isReactive:检查一个对象是否是由reactive创建的响应式代理
isReadonly:检查一个对象是否是readonly创建的只读代理
isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理
15. provide和inject
provide和inject需配合使用,可以不分级别传递参数,类似于Vue2中的全局事件总线Bus。具体方法为:通过provide提供数据输出,然后由inject接收。
话不多说,上代码:
祖级组件 App.vue
// App.vue
<template>
<h2>provide 和 inject</h2>
<p>当前的颜色:{{ color }}</p>
<button @click="color= 'red'">红色</button>
<button @click="color= 'yellow'">黄色</button>
<button @click="color= 'green'">绿色</button>
<hr>
<Child />
</template>
<script lang="ts">
import { defineComponent, ref,provide} from 'vue';
import Child from './components/Child.vue';
export default defineComponent({
name: 'App',
components:{ Child },
setup(){
const color = ref('red');
provide('color',color); // 此处的provide函数把color传递出去
return {
color,
}
}
});
</script>
父级组件:Child.vue
// Child.vue
<template>
<h3>Child 子级组件</h3>
<hr>
<Grandchild :color="color"/>
</template>
<script lang="ts">
import {defineComponent} from 'vue';
import Grandchild from './Grandchild.vue';
export default defineComponent({ // 此处并未传递任何props
name: 'Child',
components:{Grandchild},
})
</script>
子级组件:Grandchild
// GrandChild.vue
<template>
<h3 :style="{color}">Grandchild 子级组件</h3>
<hr>
</template>
<script lang="ts">
import {defineComponent, inject} from 'vue';
export default defineComponent({
name: 'Grandchild',
setup(){
const color = inject('color'); // 通过inject接收到的函数,可以直接使用
return {
color
}
}
})
</script>
16. teleport
teleport译为“瞬移”,是Vue3中一个很使用的方法,可以把组件转移到在父组件外想要插入的标签(通常是body)内。
话不多说,上代码:
父组件:
// APP.vue
<template>
<h2>响应式数据的判断</h2>
<h2>响应式数据</h2>
<hr/>
<Child />
</template>
<script lang="'ts">
import {defineComponent, isReactive, isRef, reactive,ref,readonly,isReadonly,isProxy} from 'vue';
import Child from './Child.vue';
export default defineComponent({
components:{Child},
});
子组件:标签teleport中的内容将出现在body里
<template>
<button @click="modalOpen = true">Open full screen modal</button>
<teleport to="body">
<div class='modal' v-if="modalOpen">
<div>这是modal对话框
<button @click="modalOpen =false">关闭对话框</button>
</div>
</div>
</teleport>
</template>
<script langt="ts">
import {defineComponent, ref} from 'vue';
export default defineComponent({
name:'Modal',
setup(){
const modalOpen = ref(false);
return {
modalOpen,
}
}
});
</script>
17. Fragment
fragment名为碎片,在Vue3中提供了一种虚拟的fragment元素,使得在template中不再需要用一个根节点把所有元素都包裹起来。减少了渲染时的内存消耗。
仔细观察上面的所有代码实例里,是不是大多数template里都没有包含根节点?那就是fragment的作用。
18.写在最后:这篇文章内容不多,但是我写了快一个星期,期间翻了很多之前学习的视频和代码, 为的是每个点都附带上一段代码。有些内容看完即忘,但是如果之前写过,再次看到就会立马想起来。正所谓:纸上得来终觉浅,绝知此事要躬行。希望以后能有更多案例可以记录下来。