vue3的备战
虚拟dom树对比
虚拟
1通讯方式
0 props :
js
在父组件定义的响应式数据,通过在组件上写上属性进行传递
parent:
<child a="date">
child:
//子组件需要以这样的形式进行接收父组件传递的数据,同时可以接收多个数据,他返回一个对象,里面包含所有传来的数据,仍然是响应式的
let x=defineProps(['a'])
1:v-model形式
js
对于v-model的传递形式用在父子 子父都可以
用在自定义组件时
例如
parent
<fuzujian v-model='name'>
child
首先需要声明接收
defineProps(['modelValue'])
const emit=defineEmits([update:modelValue])
触发的时候需要写
emit('update:modelValue',date)
注意,在这种情况下,modelvalue无法改变,如果在父元素 v-model:qw 这样写,则后面的modelValue可以更替
2:props形式
js
父传子:
//父亲
<child :obj="obj"></child>
//子组件
let b= defineProps(["obj"]); 父亲通过props的形式传递给了子组件数据,子组件需要使用defineProps来接收
子传父:
需要父亲提前准备一个方法
父亲:
<child :name='a'></child>
function a(val){
console.log(val)
}
子:
<button @click='test'>
let s=defineProps(['name']) //注意,这里接收的名字应当为:name而非='a'里面的名字
function test(){
s.name(val)
}
自定义事件:
js
\\通过给子组件添加自定义事件来实现传参 只能实现子串夫
子传父亲:
父亲:
<child @test="test1"></child> //@test:时间名 test1 触发时调用哪个函数
function test1(val){console.log(val)}
子组件:
const emit=defineEmits(["test"]);
可以在适当的时候调用emit('test')参数作为emit的第二个参数传入
onMounted(()=>{
emit('test',666)
})
mitt 任意组件的传递
js
与vue2的 $bus一样
消息订阅与发布:
npm i mitt
新建utills文件
xx.ts
import mitt from 'mitt'
export const emitter=mitt()
main : import xx.ts
基础语法
import mitt from "mitt";
const mitter = mitt()
// 绑定事件
mitter.on('a1', (val) => {
console.log('a1')
})
//触发事件
mitter.emit('a1')
//解绑
mitter.off('a1', () => {
console.log('a1解绑')
})
export default mitter
只需要在相应的组件导入使用即可
父亲:
import mitter from "../utills/mit";
<button @click="mit">点我触发mit的子串夫</button>
mitter.on("all", (val) => {
console.log("all", val); //用于绑定一个事件all,当子组件触发all时,可以实现子组件的数据传递到父组件中来
});
function mit(val) {
mitter.emit("allchild", obj); //用于触发allchild事件,通过父组件点击触发事件,将父组件的数据传递过去
}
子组件:
<button @click="act">传递给父亲</button>
import mitter from "../utills/mit";
mitter.on("allchild", (val) => {
console.log("allchild", val); //用于绑定allchild事件,通过父组件点击触发事件,将父组件的数据传递过去
});
function act() {
fn.have(a.value);
emit("test", a.value);
mitter.emit("all", a.value); //用来触发all事件,当子组件点击时,触发all事件,并且将数据传递给父组件
}
$attrs 祖孙组件通讯
js
$attrs 其实就是props传递的另一种形式,借助子组件传递给孙子,当然也可以传递方法,让孙组件将数据传递过来
父亲:
<child :aaa="aaa" :bbb="bbb" :ccc="ccc" :test='test'></child>
function test(val){
console.log(val)
}
子组件:
<sun :attr="$attrs"></sun> //子组件一个都不接受,直接传递给孙组件
孙组件:
let attr = defineProps(["attr"]);
let active=defineProps(['test'])
onMounted(() => {
console.log("@@@@",attr.attr);
active.test('val')
});//孙组件拿到数据
$refs
$parent
js
$refs 用于父传子,$parent 用于子传父
refs类似于ref拿到子组件的实例
父组件:
<child1 ref='r1'>
<child2 ref='r2'>
<button @click='test($refs)'>
let r1=ref()
let r2=ref()
function test($refs){
console.log($refs) //拿到子组件希望父组件看到的数据
//c1:Proxy(Object) {child1: RefImpl, , __v_skip: true}
//c2:Proxy(Object) {child2a: RefImpl, __v_skip: true}
}
子组件1
let child1 = ref("child1");
let child1a=ref('child1a')
defineExpose({ child1 }); //使用此方法需要子组件进行对外暴露自身的数据,采用difineExprose的方法传入对象的形式
子组件2
let child2a = ref("child2");
defineExpose({ child2a });
$parent:
在子组件的方法中传入¥parent !!不允许写错 可以拿到父组件向外暴露的数据
子组件2
<button @click="haveParent($parent)">拿到父组件的数据</button>
let child2a = ref("child2");
defineExpose({ child2a });
function haveParent($parent) {
console.log($parent); //Proxy(Object) {aaa: RefImpl, __v_skip: true}
}
父组件:
let aaa=ref(123)
defineExprose({aaa})
provide与inject 实现爷->孙传递
js
provide 向后代(注意,后代不仅仅是儿子和孙子,向下传递)提供数据,inject,孙子接收爷爷提供的数据
爷爷组件:
let sundate = ref(4);
provide("sundate", sundate); //provide('name',date)
注意,这里不可以写成sundate.value,否则会丢失响应式 //否则会是 sundate 4
function updateSundate() {
sundate.value += 1;
}
孙子组件:
let sundate = inject("sundate"); //inject('name')
onMounted(() => {
console.log('sundate',sundate)
//RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 4, _value: 4}
});
如果需要传递多个值或者方法,则需要写成对象形式
爷爷:
provide("sundate", { sundate, updateSundate }); //第二个参数需要传递成对象的形式,名字唯一就可以
孙子:
let sundate = inject("sundate"); //当然可用结构出来
onMounted(() => {
console.log('sundate',sundate) //接收的时候仍然需要保存到变量中去
sundate.updateSundate()
console.log(sundate.sundate)
//sundate {sundate: RefImpl, updateSundate: ƒ}
//sun.vue:13 RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: 5, _value: 5}
});
生命周期:
vue
创建:setup() --->beforecreate created
挂在前:onBeforeMount(()=>{})
挂载:onMounted( async()=>{ await...})
更新:onBeforeUpdate(()=>{}) onUpdated(()=>{})
卸载{销毁} onBeforeMount(()=>{}) onUnMounted(()=>{})
路由:
js
vue3的路由区别于vue2的路由
新建 router:文件夹
路由创建与暴露
import {createRouter,createWebHistory,createWebHashHistory} from 'vue-router'
const router=createRouter({
history:createWebHistory() //工作模式 createWebHashHistory() 哈希模式
routes:[
//路由
{
path:''
component:.vue
name:
}
]
})
export default router //对外暴露
main:
//进行挂在
createApp(App).use(router).mount('#app')
放在哪:
<RouterView>//展示用的
<routerLink :to=`路径\name?a=${date}&b=${date}` active-class='激活样式'> //标签跳转 针对link组件,里面内置了一个active-class的属性,用来方便设置激活时候的样式,动态路由传参
<routerLink :to={path\name:'',query:{参数}} active-class='激活样式'>
params参数:
需要在路径里面进行占位
在路由组件中 path:'/news/:id/:text'
这样在<routerLink :to=`/news/id=${1}/text=${2}' active-class='激活样式`> 1 2为参数
而在对象方法中
<routerLink :to={ name:xxx,params:{date}} active-class='激活样式`> 使用对象的写法时,只能使用name作为导航的地址
接受参数
const route=useRoute()
//let {query}=route 会丢失响应式,需要包裹上torefs
let {query}=toRefs(route)
//使用parmas的时候
let {params}=toRefs(route)
··· 路由规则的props方法
当使用params方法时候,
{
path:''
component:.vue
name:
props:true //开启props方法
}
那么在后续接受参数时
使用defineProps(['id','text']) 通过这样的形式将传递的参数形成一个单独的prop,可以直接在模板使用
replace:
写在<routerLink replace> //无法浏览器后退
编程式路由导航
import { useRouter} from 'vue-router'
const router=useRouter()
router.push('/path') // router.replace('/path)
router.push({
path:'/path',
query:{[参数]}
})
重定向:
{
path:'/',
redirect
}
pinia
js
main:
import {createPinia} from 'pinia'
const pinia=createPinia()
app.use(pinia)
创建store文件夹
xxx.ts:
import {defineStore} from 'pinia'
export const usexxxStore=defineStore('xxx',{
state(){
return{
a:1
}
},
actions:{
function a(){
this.a=this.a+1
}
},
getters:{
big(state){
return state.a*10
}
}
})
//组合式
export const usexxxStore=defineStore('xxx',()=>{
let a=ref(1)
//方法
//actions:
function a2(){
this.a=this.a+1
},
//getters:
let b=computed(()=>{
return ???
})
return{
a,
a2,
b
}
})
vue中读取
import { usexxxStore} from ''
const store= usexxxStore()
const store2=storeToRefs(store) //保持数据!!!的响应式,store的方法不会包裹响应式
let {a,big}=store2
1 toRef与toRefs
js
toRef 与toRefs
toRef是针对单个数据做响应式处理,而toRefs是针对全部做响应式处理,对于结构时,一定要注意响应式是否丢失
例子:
let obj=ref({
a:1,
b:2
})
let {a}=obj //此时响应式就已经丢失了,你拿到的只是里面的一个值
toRef: let {a}=toRef(obj,'a')
toRefs: let {a}=toRefs(obj)
2组合式api
两张图对比:
js
对于rective定义的属性,无法整体替换
例如 let obj=rective(a:1,b:2)
let obj={a:2,b:3}
不可这样写,需要 Object,assgin(obj,{??})这样
computed在vue3中变成了组合式api
let a=computed(()=>{
return ?? 应该写成这样的形式
})
let op=watch(()=>{return??},(new,old)=>{
//watch的用法,监视的数据要注意有时需要用函数返回的形式来写
op() //达到条件可用关闭监视
})
watch([()=>{return??},()=>{return}],(new,old)=>{
//当要监视多个属性时需要这样
})
watch(rective对象,(new,old)=>{
//当监视的是rective的对象时,需要开启deep深度监视
},{
deep:ture
})
watchEffect //区别于watch,他不需要写监视谁,在里面用到的数据他默认会给你监视
watchEffect(()=>{
里面用到的数据都自动进行了监视
})
3 ref属性
js
在vue3中
在标签上写ref属性可以拿到dom操作
<button ref="a">
let a=ref()
那么这个a就是哪个标签
当ref写在组件上时,拿到的就是组件实例
例如
<BUTTON ref='btn'>
let btn=ref()
此时,就拿到了这个组件的实例,当拿到组件实例的时候可以看到组件里面的数据,当使用setup的时候
我们需要在子组件用defineExpose进行暴露出去,这样才能看到
如果没有使用setup语法糖的时候,则不需要这么做
例
child :
<template><h1>这里是stu1</h1></template>
<script lang="ts">
export default {};
</script>
<script lang="ts" setup>
import { ref } from "vue";
let ss1 = ref("asdasd");
defineExpose({ ss1 });
parent:
<child ref='ch'>
<script lang="ts" setup>
let ch=ref()
插槽:
js
默认插槽
默认插槽需要在子组件使用双标签的形式
parent:
<chacao>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</chacao>
<chacao>
<img src="https://img0.baidu.com/it/u=2565809048,1400176152&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500" alt="">
</chacao>
父组件使用了两个一样的插槽,但里面的内容是不同的,展示到页面上的效果也不同
子组件:
<h1>这里是插槽</h1> 而子组件只需要写上slot标签告诉父组件插槽的内容往哪里展示即可
<slot></slot>
具名插槽
当我们要向插槽里面添加的样式和结构不是单一的结构时,我们就需要使用具名插槽,来告诉具体的结构展示在具体的哪个位置
parent:
<template>
<chacao>
<template v-slot:s1>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</template>
<template v-slot:s2>
<h1>这里是具名插槽s2</h1>
</template>
</chacao>
<chacao>
<template v-slot:s1>
<img
src="https://img0.baidu.com/it/u=2565809048,1400176152&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500"
alt=""
/>
</template>
</chacao>
</template>
子组件:
<template>
<h1>这里是插槽</h1>
<slot name="s1"></slot>
<slot name="s2"></slot>
</template>
// 可用看到,子组件写了两个插槽,并且命名为s1和s2
//在父组件中,具体的结构使用了template进行包裹,并且在template上使用了v-slot:name 指令
//注意 v-solt只能写在组件标签或者template标签上,语法为v-slot:插槽名字
//这样,插槽就和结构一一对应起来
作用域插槽:
作用域插槽用于结构由父组件决定,但是数据在子组件身上,从而形成的尴尬局面,当然具名插槽可以和作用域插槽一起使用
例如: <template v-slot:s1="data">
父亲:
<chacao>
<template v-slot="data">
<ul>
<li v-for="(item, index) in data.youxi" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
</chacao>
子组件
<h1>这里是作用域插槽</h1>
<slot :youxi="game"></slot> //直接在slot标签上传递数据,可以传递多个数据,slot会将所有的数据打包成一个对象传递给父组件
父组件在template写语法为v-slot='??'格式的指令,拿到由子组件打包而来的数据,使用时只需要??.name使用即可
let game = reactive([
{ id: 1, name: "benghuai" },
{id: 2,name: "LOL",},
{id: 3, name: "yuanshen" },
{id: 4,name: "王者",},
]);
效果:
//benghuai
//LOL
//yuanshen
//王者
一些面试题
面试题1:为什么vue3中去掉了vue构造函数?
js
vue2的全局构造函数带来了诸多问题:
1. 调用构造函数的静态方法会对所有vue应用生效,不利于隔离不同应用
2. vue2的构造函数集成了太多功能,不利于tree shaking,vue3把这些功能使用普通函数导出,能够充分利用tree shaking优化打包体积
3. vue2没有把组件实例和vue应用两个概念区分开,在vue2中,通过new Vue创建的对象,既是一个vue应用,同时又是一个特殊的vue组件。vue3中,把两个概念区别开来,通过createApp创建的对象,是一个vue应用,它内部提供的方法是针对整个应用的,而不再是一个特殊的组件。
面试题2:谈谈你对vue3数据响应式的理解
js
vue3不再使用Object.defineProperty的方式定义完成数据响应式,而是使用Proxy。
除了Proxy本身效率比Object.defineProperty更高之外,由于不必递归遍历所有属性,而是直接得到一个Proxy。所以在vue3中,对数据的访问是动态的,当访问某个属性的时候,再动态的获取和设置,这就极大的提升了在组件初始阶段的效率。
同时,由于Proxy可以监控到成员的新增和删除,因此,在vue3中新增成员、删除成员、索引访问等均可以触发重新渲染,而这些在vue2中是难以做到的。