vue3核心面试题

发布时间 2023-11-22 22:27:36作者: 开源遗迹

vue3

vue3比vue2有什么优势?

性能更好

体积更小

更好的ts支持

更好的代码组织

更好的逻辑抽离

更多新功能

Composition API (vue3)和Options API(vue2)的生命周期变化

Composition API

//等于beforeCreate和created
setup() {
    console.log('setup')
    onBeforeMount(() => {
    console.log('onBeforeMount')
    })
    onMounted(() => {
    console.log('onMounted')
    })
    onBeforeUpdate(() => {
    console.log('onBeforeUpdate')
    })
    onUpdated(() => {
    console.log('onUpdated')
    })
    onBerforeUnmount(() => {
    console.log('onBerforeUnmount')
    })
    onUnmounted(() => {
    console.log('onUnmounted')
    })
}

Options API

beforeDestroy改为beforeUnmount

destoyed改为Unmouted

其他沿用Vue2的生命周期

beforeCreate() {
	console.log('beforeCreate')
},
created() {
	console.log('created')
},
beforeMount() {
	console.log('beforeMount')
},
mount() {
	console.log('mount')
},
beforeUpdate() {
	console.log('beforeUpdate')
},
Update() {
	console.log('Update')
},
//beforeDestroy 改名
beforeUnmount() {
	console.log('beforeUnmount')
}
//destroy 改名
unmounted() {
	console.log('unmounted')
}

Composition API带来了什么?

更好的代码组织

更好的逻辑复用

更好的类型推导

Composition API 和Options API如何选择?

不建议共用,会引用混乱

小型项目、业务逻辑简单,用Options API

中小型项目、逻辑复杂,用Composition API

别误解Composition API

属于高阶技巧,不是必会技能

ref toRef 和toRefs

是什么

最佳使用方式

进阶,深入理解

ref

生成值类型和响应式数据

可用于模板和reactive

通过.value修改值

<template>
	<div>{{ageRef}} {{state.value}}</div>
</template>
name: 'Ref'
setup() {
    const ageRef = ref(20) //值类型,响应式
    const nameRef = ref('张三')
const state = reactive({//响应式数据
    name: nameRef
})
    return {
        ageRef,
        state
	}
}


<template>
	<p ref= "elemRef">我是一行文字</p>
</template>
<script>
import {ref, onMounted} from 'vue'
export default {
name: 'RefTemplate',
setup() {
    const elemRef = ref(null)
    onMounted(() => {
    console.log('ref',elemRef.value.innerHTML,elemRef.value)
})
}
}
</script>

toRef

针对一个响应式对象(reactive封装)的prop

创建一个ref,具有响应式

两者保持引用关系

一个对象想实现响应式就用reactive就行了,对象的一个属性要实现响应式就用toRef(如果对于普通对象产出结果不具备响应式)

<template>
<p>{{state.age}} {{ageRef}}
</template>
<script>
setup() {

const state = reactive({
age: 20,
name: '张三'
})

coonst ageRef = toRef(state,'age')

setTimeout(() => {
state.age = 25
},1500)

setTimeout(() => {
ageRef.value = 30 //.value 修改值
},3000)

return {
state,
ageRef
}

}

toRefs

将响应式对象(reactive封装)转换为普通对象

对象的每个prop都是对应的ref

两个保持引用关系

<template>
<p>{{age}} {{name}}
</template>
<script>
import {ref, toRef, toRefs, reactive} from 'vue'

setup() {
    const state = reactive({
        age: 20,
        name: '双月'
    })
	const stateAsRefs = toRefs(state)	//将响应式对象,变成普通对象
    setTimeout(() => {
    state.age = 25
    }, 1500)
    return stateAsRefs
}

合成函数返回响应式对象

function useFeatureX() {
const state = reactive({
x: 1,
y: 2
})

return toRefs(state)//返回普通对象的所有响应式属性
}

最佳使用方式

用reactive做对象的响应式,用ref做值类型响应式

setup中返回toRefs(state),或者toRef(state,'xxx')

ref的变量命名都用xxxRef

合成函数返回响应式对象,使用toRefs

进阶,深入理解

为何需要ref?

为何需要.value?

为何需要toRef toRefs

Proxy的响应式只针对对象类型,不针对值类型

返回值类型时,会丢失响应式

如在setup、computed、合成函数,都有可能返回值类型

Vue如不定义ref,用户将自造ref,反而会更乱

为何需要.value

ref是一个对象(不丢失响应式),value存储值

通过.value属性的get和set实现响应式

function computed(getter) {
const ref = {
	value: null
}
setTimeout(() => {
	ref.value = getter()
},1500)
retrun ref
}

用于模板、reactive时,不需要.value,其他情况都需要

为何需要toRef和toRefs

初衷:不丢失响应式的情况下,把对象数据分散/扩散

前提针对的是响应式对象(reactive封装的)非普通对象

注意:不创造响应式,而是延续响应式

emits属性

//父组件定义事件
<HelloWorld :msg="msg" @onSayHello="sayHello"/>

子组件传参
emits: ['onSayHello'],
setup(props,{emit}) {
emit('onSayHello','bbb')
}

多事件处理

<button @click="one($event),two($event)">
Sumit
</button>

Fragment去掉单一模板

//vue2.xx
<template>
<div class="blog-post">
<h3>{{title}}</h3>
<div v-html="content"></div>
</div>
</template>
//vue3
<template>
<h3>{{title}}</h3>
<div v-html="content"></div>
</template>

移除.sync

//2.xx
<MyComponent v-bind:title.sync="title"/>

//3.xx
<MyComponet v-model:title="title"/>

移除filter

{{message | capitalize}}

<div v-bind:id="rawId | formatId"></div>

Suspense具名插槽

<Suspense>
<template>
</Test1>
</template>


具名插槽
<template #fallback>
Loading
</template>
</Suspense>

Teleport弹窗

data中设置modalOpen:false
<button @click="modalOpen = true">
open
</button>

<teleport to="body">
<div v-if="modalOpen" class="modal">
            <div>
            telePort弹窗(父元素是body)
            	<button @click="modalOpen = false">Close</button>
            </div>
    </div>
<teleport>

异步组件

//2.xxx
new Vue({
//..
components: {
'my-component': () => import('./my-async-component vue')}
})


//3.xx
import {createApp, defineAsyncComponent} from 'vue'

createAPP({
components: {
	AsyncComponent: defineAsyncComponent(() =>
	import('./components/AsyncComponent.vue')
	)
}
})

Composition API 实现逻辑服用

抽离逻辑代码到一个函数

函数命名约定为useXxx格式

在setup中应用useXxx

import {ref, onMounted, onUnmounted} from 'vue'

function useMousePosition() {
const x = ref(0)
const y = ref(0)

function update(e) {
    x.value = e.pageX
    y.value = e.pageY
}

onMounted(() => {
    const.log('useMousePosition mounted')
    window.addEventListener('mouse',update)
})

onUnmounted(() => {
    const.log('useMousePosition unMounted')
    window.removeEventListener('mouse',update)//移除事件绑定,防止内存泄漏
})

return {
x,
y
}
}

<template>
	<p>mouse position {{x}} {{y}}</p>
</template>

import {reactive} from 'vue'
import useMousePosition from './useMousePosition'

export default {
name: 'MousePosition',
setup() {
const {x, y} = useMousePosition()
return {
    x,
    y
}
}
}

<MousePosition v-if="flag"/>//v-if为false自动调用逻辑函数里面onUnmounted进行解绑

Object.defineProperty的缺点

深度监听需要一次性递归

无法监听新增属性/删除属性(Vue.set Vue.delete)

无法原生监听数组,需要特殊处理

Proxy基本使用

const data = {
    name: 'zhangsan',
    age: 20
}


const proxyData = new Proxy(data, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
console.log('get', key)
return result  //返回结果
},
set(target, key, val, receiver) {
const result = Reflect.set(target, key, val receiver)
console.log('set', key,val)
return result  //是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('deleteProperty', key)
return result  //是否删除成功
}
})

Proxy是什么时候get就往下递归一层,不会一次性递归完

v-model用法

//子组件
<template>
	<input :value="name" @input="$emit('update:name',$event.target.value)"//就是父组件传参数和方法,子组件返回参数
	<input :value="age" @input="$emit('update:age',$event.target.value)"
</template>

export default {
name: 'UserInfo',
props: {
name: String,
age: String
}
}

<template>
<user-info
v-model:name="name"
v-model:age="age"
>
<user-info>
</template>

setup() {
const state = reactive({
name: '双月',
age:20
})
return toRefs(state)
}

watch和watchEffect的区别

两者都可监听data属性变化

watch需要明确需要监听哪个属性

watchEffect会根据其中的属性,自动监听其变化

watchEffect初始化会自动监听,为的就是收集监听属性

watch

setup() {
const numberRef = ref(100)
const state = reactive({
name: '双月'
age: 20
})
watch(numberRef, (newNumber, oldNumber) => {
console.log('ref watch',newNumber, oldNumber)
},
{
immediate: true//初始化之前就监听,可选
}
)

watch(
//第一个参数,确定要监听哪个属性
() => state.age,

//第二个参数,回调函数
(newAge, oldAge) => {
console.log('state watch', newAge,oldAge)
},

//第三个参数,配置项
{
immediate: true,
deep: true//深度监听
}
)


watchEffect(() => {
//初始化时,一定回收集一次,收集要监听的数据
console.log('state.name',state.name)//age没有收集到,即使变化也监听不到
})
setTimeout(() => {
state.age = 25
},1500)
setTimeout(() => {
state.age = '双鱼'
},3000)

}

setup中如何获取组件实例

Options API(使用this)和Composition API(使用getCurrentInstance)

data() {
return {
x: 1,
y: 2
}
},
setup() {
console.log('this1',this)//undefined 
onMounted(() => {
console.log('this in onMunted',this)//undefined
})
const instance = getCurrentInstance()//Composition 
console.log('instance',instance)
},
mounted() {
console.log('this2', this)//Options 
console.log('y',this.y)
}

vue3为什么比vue2快

patchFlag

编译模板时,动态节点做标记

标记,分为不同的类型,如TEXT PROPS

diff算法时,可以区分静态节点,以及不同类型的动态节点

hositStatic

将静态结点的定义提升到父作用域,缓存起来

多个相邻的静态系欸但,会被合并起来

典型的拿空间换时间的优化策略

CacheHandler

缓存事件

SSR

静态节点直接输出,绕过了vdom

动态节点,还是需要动态渲染

tree shaking

编译时,根据不同情况的情况,引入不同的API,import引入减少

vite是什么

一个前端打包工具,Vue作者发起的项目

借助Vue的影响力,发展较快,和webpack竞争

优势:开发环境下无需打包、启动快

vite为何启动快

开发环境使用ES6 Module, 无需打包---------非常快

<script type="module">

import add from './src/add.js'

const res = add(10,20)
console.log('add res', res)
</script>

ESModule在浏览器中的应用

<p>基本演示</p>
<script type="module">
import add from './src/add.js'

const res = add(1, 2)
console.log('add res', res)
</script>

<script>
import {add, multi} from './src/math.js'
console.log('add res',add(10, 20))
console.log('multi res',multi(10, 20))
</script>


<body>
<p>外链</p>
<script type="module" src="./src/index.js"></script>
</body>



<p>远程引用</p>

<script type="module">
import {createStore} from 'https:xxxx'
</script>



<p>动态引入</p>
<script type="module">
document.getElementById('btn1').addEventListener('click',async () => {
const add = await import('./src/add.js')
const res = add.default(1, 2)
console.log(res)
})
document.getElementById('btn2').addEventListener('click',async () => {
const {add, multi} = await import('./src/math.js')
console.log(add(1, 2))
console.log(multi(1, 2))
}
</script>

Vue3和JSX

Vue3中基本应用

使用.js格式文件和defineComponent

引用自定义组件,传递属性

setup() {
const conRef = ref(200)
const render = () => {
return <p>dome1 {countRef.value}</p>
}
return render
}

JSX和template的区别

JSX本质就是JS代码,可以使用js的人和能力

template只能嵌入简单的js表达式,其他需要指令,如v-if

JSX已经成为ES规范,template还是Vue自家规范

JSX和template本质相同

都会被编译成js代码(render函数)

JSX和slot(体会JSX优越性)

script-setup使用方式更改

基本使用,<script>写在<template>前面易读性好

定义属性defineProps,定义事件defineEmits

defineExpose暴露数据给父组件
<script setup>
import {ref,reactive,toRefs} from 'vue'
import Child from './Child'
const countRef = ref(100)
function addCount() {
countRef.value++
}
const state = reactive({
name: '双月'
})
const {name} = toRefs(state)
</script>

<template>
<p @click="addCount">{{countRef}}</p>
<p>{{name}}</p>
<child></child>
</template>

script-setup定义属性和定义事件的方式改变

defineProps

defineEmits

<script setup>
import {defineProps, defineEmits} from 'vue'
//定义属性
const props = defineProps({
name: String,
age: Number
})

//定义事件
const emit = defineEmits(['change','delete'])
function deleteHandle() {
emit('delete' 'aaa')
}
</script>

<template>
<p>name:{{props.name}},age: {{props.age}}</p>
<button @click="$emit('change','bbb')">change</button>
<button @click="deleteHandler">delete</button>
</template>



使用
<script setup>
const { name } = toRefs(state)

function onChange(info) {
console.log('on change',info)
}
function onDelete(info) {
console.log('on delete',info)
}
</script>


<template>
<child-2 :name="name" :age="countRef" @change="onChange" @delete="onDelete">
</template>

defineExpose暴露数据给父组件

子组件
<script setup>
import { res, defineExpose} from 'vue'
const a = ref(101)
const b = 201
defineExpose{
a,
b
}
</script>

<template>
	<p>Child3</p>
</template>


父组件
const child3Ref = ref(null)
onMounted(() => {
console.log(child3Ref.value)
console.log(child3Ref.value.a)
console.log(child3Ref.value.b)
})

<template>
<child-3 ref="childRef"></child-3>
</template>