Vue3面试知识点

287 阅读8分钟

一、Composition APIOptions API有什么区别

1.Composition API几乎是函数,会有更好的类型推断

2.对tree-shaking友好,代码也更容易压缩

3.见不到this的使用,减少了this指向不明的情况

4.Vue2的Object.defineProperty不能检测对象的添加和删除(通过setdelete的实例方法解决,在嵌套层级较深的情况下有性能问题),Vue3是通过proxy监听整个对象,在getter中去递归响应式

二、refreactive

1.ref可以使用于任何数据类型,而reactive只适用于对象类型。

2.reactive进行重新赋值一个对象的话会丢失响应式,解决办法为:使用Object.assign()

     let 变量名=reactive({key:value,key:value})
     Object.assign(变量名,{
         key:value,
         key:value,
         ...
         key:value

3.ref进行赋值时应该使用变量名.value = 新值,而reactive赋值时直接变量名=新值

4.ref一般情况下可以适用于所有场景,reactive适合使用于嵌套层级较深的对象类型中

三、组件通信

1.父子传参:通过使用porps给在子组件中传递参数,子组件中直接使用宏defineProps可以直接接受该值,就可以直接在结构上使用

    <script setup>
        import {ref} from 'vue'
        let msg=ref('父组件给子组件传的值')
    </script>
    
    <template>
        <div>
            <div>父组件</div>
            <Child :msg/>
        </div>
    </template>
----------------------------------------
  <script setup>
    defineprops(['msg'])
  </script>

  <template>
    <div>
        <div>子组件</div>
        <div>父组件传的值:{{msg}}</div>
    </div>
  </template>

2.子父传参:(不使用defineEmits)父组件创建一个函数handleGetChild传递给子组件

子组件利用defineProps接受该函数后,在事件中直接调用并传递值toy

父组件中该函数通过value接受到该值后,使用toy.value=value可以直接接受值

<script setup>
    import {ref} from 'vue'
    let toy = ref(null)
    function handleGetChild(value){
        toy.value = value
        //前面的toy是上面创建的,因为使用ref创建响应式所以要toy.value,后面的value是
        接受到从子组件传递过来的值
    }
</script>

<template>
    <div>
        <div>父组件</div>
        <div>子组件给父组件的玩具:{{toy}}</div>
        <Child :handleGetChild/>
    </div>
</template>
-------------------------------------
<script setup>
    import {ref} from 'vue'
    let toy = ref('给父亲的玩具')
    defineProps(['handleGetChild'])
</script>

<template>
    <div>
        <div>子组件</div>
        <button @click='handleGetChild(toy)'>点我把玩具给父组件</button>
    </div>
</template>

3.子父传参:(使用defineEmits):子组件绑定事件A,然后使用defineEmits声明需要发送给父组件的事件B,在绑定事件A中利用defineEmits发送B事件及需要传递的数据, 父组件中接收该事件后接收该值

<template>
    <div>
        //子组件
        玩具:{{toy}}
        <button @click='handleSendFather'>点我发送给父组件</button>
    </div>
</template>
<script setup>
    import { ref } from 'vue'
    let toy = ref('奥特曼')
    let emit = defineEmits(['handleSendToy'])
    let handleSendFather = () =>{
        emit('handleSendToy',toy.value)
    }
</script>
-------------------------------------------------------------------
<template>
    <div>
        //父组件
        子组件给的玩具:{{getChildToy}}
        <Child @handleSendToy='handleGetToy'/>
    </div>
</template>
<script setup>
    import { ref } from 'vue'
    let getChildToy = ref()
    let handleGetToy = value=>{
        getChildToy.value = value
    }

</script>

4.mitt兄弟传参:自定义hooks mitt然后在两个组件中分别引入mitt

在需要发送数据的组件里使用mitt.emit('事件名',数据),

在需要接收的组件里使用mitt.on('事件名',value=>{接收的变量名.value=vlaue}),

性能优化:记得在组件卸载时解绑该事件,在生命周期onUnmounted中使用mitt.off,

onUnmounted(()=>{mitt.off('事件名')})

先自定义hooks:
import mitt from 'mitt'
const emitter = mitt()
export default emitter


<script setup>
    import emitter from '../../utils/emitter'
    import {ref} from 'vue'
    let computer = ref(null)
    //使用mitt接收数据
    emitter.on('send-computer',value=>{
        computer.value = value
    })
    //在组件卸载时解绑send-computer事件
    onUnmounted(()=>{
        emitter.off('send-computer')
    })
</script>
<template>
    <div>
        <div>兄弟组件1</div>
        <div>兄弟组件2发来的电脑:{{computer}}</div>
    </div>
</template>
-----------------------------
<script setup>
    import emitter from '../../utils/emitter'
    import {ref} from 'vue'
    let computer = ref('华硕')
</script>
<template>
    <div>
        <div>兄弟组件2</div>
        //使用mitt发送数据
        <button @click='emitter.emit('send-computer',computer)'></button>
    </div>
</template>

5.父组件给孙组件传参:父组件使用provide('事件名',数据),孙组件用let 变量名= inject('事件名')可以直接获取到对应的数据,同样父组件传递函数给后代组件,后代组件通过inject接收后传参,父组件中该函数就可以进行数据的更改

6.attrs:父组件传递给后代组件中的值用defineProps接收,如果传递了3个值,只接收了1个值,那么剩余的两个值会自动存储在$attrs

<div>
    父组件
    <Child :a :b :c/>
</div>
-------------
<script setup>
    defineProps(['a'])
</script>
<div>
    子组件
    <GrandChild v-bind='$attrs' />
</div>
------------
<script setup>
    defineProps(['b','c'])
</script>
<div>
    孙组件
</div>

7.集中式状态管理器Pinia Vue3中的Pinia相对于Vue2中的Vuex简洁了不少:在store文件夹中创建Top.js

1.第一步在控制台中安装pinia yarn add pinia

2.第二步在main.js中注入pinia

```
import {createPinia} from 'pinia'
const pinia = createPinia()
app.use(pinia)
```

3.第三步编写store文件夹中的js文件

```
    import {defineStore} from 'pinia'
    import {computed ref} from 'vue'
    export let useTopStore = defineStore('Top',()=>{
        let sum = ref(6)
        let bigSum = computed(()=>{sum.value*100})
        return {sum,bigSum}
    })

```

4.第四步使用引入useTopStore,再对其包裹使用storeToRefs

然后解构赋值let {sum,bigSum} = storeToRefs(topStore)

最后如果要修改pinia里的值只需要topStore.sum = sum.value直接赋值即可

<script setup>
    import {useTopStore} from '../../store/Top'
    import {storeToRefs} from 'pinia'
    let topStore = useTopStore()
    let {sum,bigSum} = storeToRefs(topStore)
    function handleChangeSum(){
        sum.value +=1
        topStore.sum = sum.value
    }
</script>
<template>
    <div>
        {{sum}}
        {{bigSum}}
        <button @click='handleChangeSum'>修改sum</button>
    </div>
</template>

四、watch和watchEffect

watch要明确监视的数据

watch(['监视对象'],(newValue,oldValue)=>{})

watchEffect用到哪些数据,就监听哪些数据

watchEffect(()=>{})

<script setup>
 import { ref, watch, watchEffect } from 'vue'
 let temp = ref(10)
 function handleSumAdd() {
     temp.value += 10;
 }
 let height = ref(0)
 function handleHeightAdd() {
     height.value += 10;
 }
 //watch要明确指出监视的数据
 watch([temp,height],(newValue,oldValue)=>{
     if(newValue[0]>=60||newValue[1]>=80){
         console.log('给服务器发请求');
     }
  }}
//watchEffect不需要明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)
 watchEffect(() => {
     if (temp.value >= 60 || height.value >= 80) {
         console.log('给服务器发请求');
     }
 })
</script>

五、插槽

1.具名插槽(默认插槽也有个名字:default),其中v-solt的语法糖为#

            父组件
            <Child>
                <template #content>
                    <div v-for="item in games" :key="item.id">{{ item.name }}</div>
                </template>
                <template #title>
                    <div>标题<div>
                </template>
            </Child> 
--------------------------
            子组件
            <slot name='title'>标题</slot>
            <slot name='content'>内容</slot>

2.作用域插槽:特点为数据存储在子组件中,结构在父组件中

            父组件
            <Child>
                <!-- params是子组件中slot组件中传给父组件的所有props -->
                <template #games="params">
                    <ul>
                        <li v-for="item in params.games" :key="item.id">{{ item.name }}</li>
                    </ul>
                </template>
            </Child>
      -----------------------------
      子组件
      let games = ref([
        { id: 1, name: '原神' },
        { id: 2, name: '梦幻西游' },
        { id: 3, name: '绝区零' },
        { id: 4, name: '星穹铁道' }
    ])
      
      <div>标题</div>
      <solt :games name='games'>内容</solt>

六、生命周期

1.setup

2.挂载前onBeforeMount

3.挂载完毕onMounted

4.更新前onBeforeUpdate

5.更新完毕onUpdated

6.卸载前onBeforeUnmount

7.卸载完毕onUnmounted

//创建
console.log('创建');

//挂载前
onBeforeMount(() => {
    console.log('挂载前');
})

//挂载完毕
onMounted(() => {
    console.log('挂载完毕');
})

//更新前
onBeforeUpdate(() => {
    console.log('更新前'); 
})

//更新完毕
onUpdated(() => {
    console.log('更新完毕');
})

//卸载前
onBeforeUnmount(() => {
    console.log('销毁前');
})

//卸载完毕
onUnmounted(() => {
    console.log('销毁完毕');
})

七、路由Router

1.第一步安装路由依赖yarn add vue-router

2.第二步创建router文件夹index.js文件

3.第三步在index.js文件中编写router代码

import {createRouter,crateWebHistory} from 'vue-router'

//引入组件
let router = createRouter({
    history:createWebHistory(),
    routes:[
        {
            path:'/props',
            component:Props
        },
        {
            path:'/news',
            component:News,
            children:[
                {
                    name:'detail',
                    path:'detail',
                    component:Detail                    
                }
            ]
        }
    ]

})

4.第四步在main.js中注入

import router from './router'
app.use(router)

5.第五步在路由组件中编写代码

<script setup>
    import{RouterLink,RouterView} from 'vue-router'
</script>
<template>
    <RouterLink :to='{path:'/news/detail'}'>内容</RouterLink>
    <RouterView />
</template>

6.路由跳转除<RouterLink to=''></RouterLink>外还可以在script中使用useRouter().push('/')

八、shallowRefreadonlyshallowReadonlytoRawmarkRaw

shallowRef:浅引用只能修改第一层的数据

let sum = shallowRef(0)
function a(){
    sum.value +=1
}
let person = shallowRef({
    name:'joy',
    age:18
})
function b(){
    person.value= {
        name:'ken',
        age:19
    }
}
//person.value或者sum.value属于第一层,person.value.name或者person.value.age属于第二层不能被修改

readonly:只读

let sum = readonly(0)
function a(){
    sum.value +=`1 //不会被修改
}

shallowReadonly:浅只读

toRaw:只能对reactive使用,将一个响应式对象变为原始对象,以此来保证哪怕代码修改了,视图不会发生变化以此来保护变量不被修改,使用场景:将响应式数据传递给非响应式的库或框架时使用

markRaw:标记一个对象为原始对象且永远不会被响应式处理

九、defineProps()defineEmits()defineExpose()是什么

1.defineProps():父组件给子组件传值后,子组件通过defineProps()进行接收

<script setup>
    //父组件
    import {ref} from 'vue'
    let sum = ref(0)
    <Child :sum />
</script>
-------------------------------
<script setup>
    //子组件
    defineProps(['sum'])
</script>
<template>
    <div>{{sum}}</div>
</template>

2.defineEmits():在子组件中声明一个事件后触发事件,在父组件中通过该事件名监听该事件

<script setup>
    //子组件
    let emit = defineEmits(['event-name'])
</script>
<template>
    <div>
        <button @click='emit('event-name')'>触发事件</button>
    </div>
</template>
------------------------------------------------------------
<script setup>
    //父组件
    let handleEvent = ()=>{
        console.log('事件已触发')
    }
</script>
<template>
    <div>
        <Child @event-name='handleEvent'/>
    </div>
</template>

3.defineExpose():子组件通过defineExpose()暴露属性与方法,父组件通过ref引用子组件,可以访问子组件已暴露的属性和方法

<script setup>
    //子组件
    import {ref} from 'vue'
    let sum = ref(0)
    function handleText(){
        console.log('被父组件调用')
    }
    defineExpose({
        sum,
        handleText
   })
</script>
---------------------------------
<script setup>
    //父组件
    import {ref} from 'vue'
    let ChildRef = ref(null)
    function handleChildMethod(){
        ChildRef.value.handleText
    } 
</script>
<template>
    <div>
        <Child ref='ChildRef'/>
        <button @click='handleChildMethod'>访问子组件的属性和方法</button>
    </div>
</template>

十、Teleport:将组件的HTML结构移动到指定位置

eg:
css中使用了position:fixed,又使用了filter:saturate(100%).即使用了定位以及滤镜,原本的视图定位由整个屏幕变成了父组件,这个时候使用Teleport可以解决模态框问题
    <button @click="isShow = true">展示弹窗</button>
    //to为移动到的目标位置
    <Teleport to="body">
        <div class="modal" v-show="isShow">
            <div class="titles">我是弹窗的标题</div>
            <div>我是弹窗的内容</div>
            <button @click="isShow = false">关闭弹窗</button>
        </div>
    </Teleport>

十一、路由守卫

路由守卫看这篇帖子:Vue3路由守卫详解:掌握导航控制的6个核心钩子路由守卫是Vue Router的核心功能之一,它允许开发者在路由导航的不 - 掘金

十二 、v-cloak:一个小小的性能优化

在最近的项目中,通常会在渲染出页面之前会出现以下原始模型标签
```
    <iamge />
    <view></view>
    <div></div>
    ...
```

解决办法就是在页面的最外层标签上绑定一个v-cloak就可以解决这个问题

eg:
<template v-cloak>
    <view></view>
</template>

v-cloak是Vue3的一个内置指令,它可以让样式会在 Vue 实例编译结束时,从绑定的 HTML 元素上被移除。

当网络较慢,网页还在加载 Vue.js ,而导致 Vue 来不及渲染,这时页面就会显示出 Vue 源代码。我们可以使用 v-cloak 指令来解决这一问题。