Vue基础常见面试点总结
Vue 组件生命周期
- beforeCreate:
实例刚创建 data还没准备好
可以在此时加一些loading效果,在created时进行移除
- created:
创建完成,属性已经绑定,data中数据可使用。
但挂载还未开始,还未生成真实dom ,$el还不可使用
可以在此时异步请求后端得到数据
- beforeMount: 在挂载前被调用;准备渲染还未渲染完成
- mounted:
挂载完成,用户可以看到界面
不会保证所有子组件也被挂载
可在内部使用this.$nextTick 进行单一事件对数据的更新后更新dom
使用场景:挂载元素dom节点的获取 。
- beforeUpdate:
数据更新时调用,但是还没有对视图进行重新渲染,
这个时候,可以获取视图更新之前的状态
- updated:
由于数据的变更导致的视图重新渲染,可以通过 DOM 操作来获取视图的最新状态。
使用场景:如初始化插件、绑定事件监听器或者发送统计数据。
- beforeDestroy: 实例销毁之前调用,移除一些不必要的冗余数据,比如定时器。
- destroyed:Vue 实例销毁后调用
- activated:组件被激活时调用
- deactivated:在组件被停用时调用
Vue 组件的通讯方式
1.props
通过父组件向子组件传递数据,将数据作为组件的属性(props)传递给子组件。
<!-- 父组件 -->
<template>
<div>
<child-component :message="parentMessage" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
parentMessage: 'Hello from parent'
};
}
}
</script>
<!-- 子组件 ChildComponent.vue -->
<template>
<div>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
props: ['message']
}
</script>
2.$emit
子组件可以触发自定义事件,并通过事件传递数据给父组件或其他监听该事件的组件
<!-- 子组件 ChildComponent.vue -->
<template>
<div>
<button @click="sendMessage">Send Message</button>
</div>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$emit('custom-event', 'Hello from child');
}
}
}
</script>
<!-- 父组件 -->
<template>
<div>
<child-component @custom-event="handleEvent" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
methods: {
handleEvent(message) {
console.log(message); // 输出:Hello from child
}
}
}
</script>
3.Ref / Reactive
使用 Vue 3 中的 ref 和 reactive 来创建和响应式地共享数据,以便不同组件可以访问和修改这些数据。
<!-- 子组件 -->
<template>
<div>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello from child'
};
}
}
</script>
<!-- 父组件 -->
<template>
<div>
<child-component ref="childRef" />
<button @click="getChildMessage">Get Child Message</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
components: {
ChildComponent
},
setup() {
const childRef = ref(null);
const getChildMessage = () => {
const message = childRef.value.message;
console.log(message); // 输出:Hello from child
};
return {
childRef,
getChildMessage
};
}
}
</script>
4.eventBus
使用一个全局事件总线来实现组件之间的通信。不同的组件可以通过事件总线来订阅和发布事件,实现数据的传递和通知
// 安装依赖
npm install mitt / yarn add mitt
// EventBus.js
import mitt from 'mitt';
const bus = mitt();
export default bus;
<!-- 组件A -->
<template>
<div>
<button @click="sendMessage">Send Message</button>
</div>
</template>
<script>
import bus from './EventBus';
export default {
methods: {
sendMessage() {
// 发布消息
bus.emit('custom-event', 'Hello from Component A');
}
}
}
</script>
<!-- 组件B -->
<template>
<div>
<p>{{ message }}</p>
</div>
</template>
<script>
import bus from './EventBus';
export default {
data() {
return {
message: ''
};
},
mounted() {
// 订阅消息
bus.on('custom-event', (message) => {
this.message = message;
});
}
}
</script>
5.Vuex 状态管理
使用 Vuex 这样的状态管理库来集中管理应用程序的状态。不同的组件可以通过访问共享的状态来实现通信和共享数据。
6.插槽 Slot
插槽是一种允许父组件向子组件传递内容的机制。父组件可以在子组件中定义插槽,并将内容传递给插槽,子组件可以根据需要使用这些内容。
<!-- 父组件 -->
<template>
<div>
<child-component>
<!-- 插槽内容 -->
<template v-slot:content>
<p>{{ message }}</p>
<button @click="updateMessage">Update Message</button>
</template>
</child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
message: 'Hello from parent'
};
},
methods: {
updateMessage() {
this.message = 'Updated message from parent';
}
}
}
</script>
<!-- 子组件 -->
<template>
<div>
<!-- 插槽 -->
<slot name="content"></slot>
</div>
</template>
<script>
export default {
// 子组件逻辑
}
</script>
7.provide/inject
使用 provide 和 inject API 来在组件树中提供和注入数据。父组件通过 provide 提供数据,子组件通过 inject 来注入需要的数据。
<!-- 祖先组件 -->
<template>
<div>
<child-component />
</div>
</template>
<script>
import { provide } from 'vue';
export default {
setup() {
const message = 'Hello from ancestor';
provide('message', message);
}
}
</script>
<!-- 后代组件 -->
<template>
<div>
<p>{{ injectedMessage }}</p>
</div>
</template>
<script>
import { inject } from 'vue';
export default {
setup() {
const injectedMessage = inject('message');
return {
injectedMessage
};
}
}
</script>
8.$ attrs/$ listeners
子组件通过$ attrs/$listeners获取父组件传递的数据和方法
最佳使用场景:父、子孙组件通讯
父组件
<template>
<div id="parent">
<button @click="changeName('李四')">修改姓名</button>
<!-- 把父组件的name传递给子组件 -->
<!-- 把父组件的changeName方法,子组件通过$listeners获取方法 -->
<child :name="name" v-on="{ changeName }"></child>
</div>
</template>
<script>
// 引入子组件Child
import Child from './child'
export default {
components: { Child },
data() {
return {
name: '张三',
}
},
methods: {
// 修改姓名,并传递给子组件、子孙组件使用,用$listeners获取方法
changeName(newName) {
this.name = newName
console.log(this.name)
},
},
}
</script>
子组件
<template>
<div id="child">
<p>子组件:{{ $attrs.name }}</p>
<button @click="changeName('王五')">子组件-修改姓名</button>
<!-- v-bind="$attrs":把父组件所绑定传递过来的name,再次传递给子孙组件 -->
<!-- v-on="$listeners":把父组件所绑定传递过来的方法changeName,再次传递给子孙组件 -->
<child-child v-bind="$attrs" v-on="$listeners"></child-child>
</div>
</template>
<script>
import ChildChild from './child-child'
export default {
components: { ChildChild },
inheritAttrs: false, // 是否显示元素上的属性,默认为true: 显示
methods: {
// 修改姓名
changeName(newName) {
// 通过$listeners调用父组件传来的changeName方法
this.$listeners.changeName(newName)
},
},
}
</script>
子孙组件
<template>
<div id="child-child">
<p>子孙组件:{{ $attrs.name }}</p>
<button @click="changeName('吴六')">子孙组件-修改姓名</button>
</div>
</template>
<script>
export default {
inheritAttrs: true, // 是否显示元素上的属性,默认为true: 显示
methods: {
// 修改姓名
changeName(newName) {
// 通过$listeners调用父组件传来的changeName方法
this.$listeners.changeName(newName)
},
},
}
</script>
nextTick
概念:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
Vue在更新DOM时时异步执行的。当数据发生变化时,vue并不会立刻去更新Dom,Vue将修改数据的操作放在一个异步更新队列中,视图需要等队列中所有数据变化完成之后,再统一进行更新。
1.为什么要有nextTick
{{num}}
for(let i=0; i<100000; i++){
num = i
}
如果没有 nextTick 更新机制
那么num 每次更新值都会触发视图更新(上面这段代码也就是会更新10万次视图)
有了nextTick机制,只需要更新一次,所以nextTick本质是一种优化策略。
2.使用:
使用Vue.nextTick()是为了可以获取更新后的DOM 。
this.testMsg="修改后的值";
console.log(this.$refs.aa.innerText); // => '原始的值'
Vue.nextTick(function(){
console.log(document.querySelector('#firstBtn').innerText) // => '修改后的值'
})
高阶应用:动态组件,异步组件,keep-alive ,slot
1.动态组件
动态组件指的是动态切换组件的显示与隐藏动态切换组件的显示与隐藏
data(){
return{
comName:'Left'
}
}
// 通过is属性,动态指定要渲染的组件
<component :is="comName"></component>
// 点击按钮 动态切换组件的名称
<button @click="comName = 'Left'">展示Left组件</button>
<button @click="comName = 'Right'">展示Right组件</button>
2.keep-alive
默认情况下,切换动态组件时无法保持组件的状态。
此时可以使用vue 内置的 <keep-alive>
组件保持动态组 件的状态,
以避免反复重新渲染导致的性能问题。
<!-- 失活的组件将会被缓存!-->
<keep-live>
<component :is="comName"></component>
</keep-live>
3.异步组件
异步组件是一种延迟加载组件的方式,Vue的一种优化方式。比如可以运用在首屏加载等场景。
Vue.js 异步组件的实现方式是通过 import() 函数来实现的。
在使用异步组件时,需要将组件定义为一个函数,返回一个 import() 函数,
该函数会异步加载组件并返回一个 Promise 对象。
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
// 处理加载状态
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
自定义实现 v-model
<input type="text" v-model="message">
等同于
<input type="text" :value="message" @:input="message = $event.target.value">
// 父组件
<div>
// <child-component v-model="message"></child-component>
<child-component :value="message" @:input="message = $event.target.value"/>
</div>
// 子组件
<div>
<input type="text" :value="value" @input="handelInput"/>
</div>
<script>
export default{
props:['value'],
methods:{
handelInput(e){
this.$emit('input',e.target.value)
}
}
}
</script>
需要自定义事件,是因为有些组件属性和事件不是v-model默认的value和input
// 父组件
<div>
// <child-component v-model="message"></child-component>
<child-component :checked="message" @:change="message = $event.target.value"/>
</div>
// 子组件
<div>
<input type="checkbox" :checked="checked" @change="handelInput"/>
</div>
<script>
model:{
prop:'checked',
event:"change"
}
export default{
props:['value'],
methods:{
handelInput(e){
this.$emit('change',e.target.value)
}
}
}
</script>
Vue3 和 Vue2 区别
1.生命周期的变化
`创建前`:beforeCreate -> 使用setup()
`创建后`:created -> 使用setup()
`挂载前`:beforeMount -> onBeforeMount
`挂载后`:mounted -> onMounted
`更新前`:beforeUpdate -> onBeforeUpdate
`更新后`:updated -> onUpdated
`销毁前`:beforeDestroy -> onBeforeUnmount
`销毁后`:destroyed -> onUnmounted
2.组合式API和选项式API
在vue2中`采用选项式API`
在vue3中`采用组合式API`
3.v-if和v-for的优先级
`在vue2中v-for的优先级高于v-if`,可以放在一起使用,但是不建议这么做,会带来性能上的浪费
`在vue3中v-if的优先级高于v-for`,一起使用会报错。可以通过在外部添加一个标签,将v-for移到外层
4.插槽方式不同
具名插槽使用方式不同:vue2使用slot='',vue3使用v-slot:''
作用域插槽使用方式不同:
vue2中在父组件中使用slot-scope="data"从子组件获取数据,
vue3中在父组件中使用 #data 或者 #default="{data}"获取
// 匿名插槽
子组件:
<div>
<slot></slot>
</div>
父组件:
<child>
<span>我是插槽插入的内容</span>
</child>
// 具名插槽
// vue2
子组件:
<div>
<slot name="person"></slot>
</div>
父组件:
<child>
<span slot="person">我是插槽插入的内容</span>
</child>
// vue3
子组件:
<div>
<slot name="person"></slot>
</div>
父组件:
<child>
<template v-slot:person>
<span>我是插槽插入的内容</span>
</template>
</child>
// 作用域插槽
// vue2
<div>
<slot :data="data"></slot>
</div>
父组件:
<child>
<span slot-scope="data">我是插槽插入的内容</span>
</child>
// vue3
子组件:
<div>
<slot :data="data"></slot>
</div>
父组件:
<child>
<span #data>我是插槽插入的内容</span> === <span #default="{data}">我是插槽插入的内容</span>
</child>
5.响应式原理不同
`vue2`通过Object.definedProperty()的get()和set()来做数据劫持、
结合和发布订阅者模式来实现,Object.definedProperty()会遍历每一个属性。
`vue3`通过proxy代理的方式实现。
Vue3 中 ref reactive 的区别和选用
1.ref
允许我们创建可以使用
任何值类型
的响应式 ref。
如果我们创建的是一个对象的响应式数据,其实里面原理也是通过 reactive 实现的。
ref() 将传入参数的值包装为一个带.value
属性的 ref 对象
1.一个包含对象类型值的 ref 可以响应式地替换整个对象,如果是 reactive 的话不会被换成响应式的
<script setup>
let obj = ref({name: 'zs', age: 1})
obj.value = {name: 'lisi', age: 12} // 依旧是响应式的
setInterval(() => {
obj.value.age++
}, 2000)
</script>
<template>
<div>
{{ obj }}
</div>
</template>
2.ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性
let obj = {
name: ref('zs'),
age: ref(12)
}
let {age} = obj
setInterval(() => {
age.value++
}, 2000)
2.reactive
1.reactive 接受一个
对象类型
的值,返回一个对象的代理
和原始对象是不等的。
2.状态都是默认深层响应式的
。这意味着即使在更改深层次的对象或数组,你的改动也能被检测到。
3.响应式对象内的嵌套对象依然是代理(Proxy)
4.当响应式对象改变时,原始对象的值也会被改变
<script setup>
import {reactive} from 'vue'
let o = {name: 'zs', info: {age: 1}}
let obj = reactive(o)
let age = obj.info.age // age 不是响应式的
setInterval(() => {
obj.info.age++
// console.log(o);
}, 2000)
</script>
<template>
<div>
{{ obj }}
<!-- {{ o }} -->
</div>
</template>
3.toRef和toRefs
用于将reactive内的节点提取出来,同时具有响应式结构
// toRef的使用
<script setup>
import {
reactive,
toRef,
toRefs
} from 'vue'
var student = reactive({
name: '张三',
age: 15,
contact: {
phone: '18714896998',
qq: '103422412',
email: 'wm218@qq.com'
}
})
// 定义toRef
var name = toRef(student, 'name') // toRef用法
var phone = toRef(student.contact, 'qq') // toRef用法
console.log(name.value); name.value = '李四' //修改toRef的值时,status会同步响应
console.log(student); // 打印修改后的结果 </script>
// toRefs的使用
<template>
<view></view>
</template>
<script setup>
import {
reactive,
toRef,
toRefs
} from 'vue'
var student = reactive({
name: '张三',
age: 15,
contact: {
phone: '18714896998',
qq: '103422412',
email: 'wm218@qq.com'
}
})
// toRefs
var info = toRefs(student)
// 这种调用方式等同于直接调用student的结构
console.log(info.name.value); // 此时info结构:{name: ..., age: ..., contace: ...}
// 常用于组件、函数返回动态响应式变量
var getInfo = function(){
return {...toRefs(student)}
}
console.log(getInfo().name.value);
</script>
区别
toRef与toRefs都是将reactive的json内节点提取出来,做为独立的响应式结构。
二者的区别在于:toRef是指定某一节点提取出来,toRefs是一次性将所有节点提取出来。但toRefs只能提取一级节点!
toRefs返回的变量修改,与原始值无任何响应式关联。
为何使用 key
一般在v-if和v-for涉及到key的使用
主要是用于给每个节点(组件)设置唯一的标识符,为了高效的更新虚拟DOM。
key 只在其直接的子组件中起作用,如果两个组件的 key 相同,但它们不是直接的子组件,则 Vue 仍然会重新渲染它们。
key 应该是稳定且唯一的,不应该使用随机数或索引作为 key,因为这样可能会导致渲染错误。
总结 Vue 的性能优化
代码模块化
:可以把很多通用的地方封装成组件,重复利用。css可以通过less和sass的方式减少重复代码。
Vue路由设置成懒加载
:当首屏渲染的时候,能够加快渲染速度
keep-alive页面缓存
:用来对组件进行缓存,从而节省性能
v-for 遍历必须为 item 添加key
,且避免同时使用 v-if
合理使用异步组件
数据持久化存储的使用尽量使用防抖和节流
优化
Vuex
1.vuex的定义
是一个专为 Vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态。
2.使用步骤
- store
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const store = new Vuex.Store({ state:{ msg:'我是vuex' }, // 存放数据的仓库 getters:{}, // 加工仓库的数据 actions:{}, // 异步操作获取数据(无异步请求可跳过该步骤) mutations:{} // 更改仓库的数据 }) export default store // 在main.js中挂载在vue实例上
- 操作数据
-- 组件中取用数据 this.$store.state.msg -- 修改vuex中的数据 规则:action-->mutation-->state // 组件内部方法 this.$store.dispatch('actionsChange') // vuex mutations:{ // 这里第一个形参state就是仓库state,是可以访问到state里的msg的值,即 可以修改state // 第二个形参params是actions中传过来的数据 mutationsChange(state,params){ console.log(state,params); state.msg = params } }, actions:{ // 这里的形参store对象下面有commit方法 // 可以去告知对应的mutations中的函数执行 actionsChange(store){ console.log(store); setTimeout(() => { store.commit('mutationsChange', '规范修改vuex') }, 500); } }, // 区别 mutations用于同步状态的修改 actions用于执行异步操作并间接修改状态。 getters:{ gettersChange(state){ return state.msg + '---getter修改state中的数据' } } // 组件中使用 this.$store.getters.gettersChange
Vue-router 相关,如导航守卫,路由懒加载,动态参数
1.路由懒加载
就是用一个回调函数来加载组件,函数调用的时候才会执行加载组件又叫延迟加载。
import Home from "../views/Home.vue";
const routes: Array<RouteRecordRaw> = [
{
path: "/about",
name: "About",
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
];
2.动态参数
- query传参数
路由配置:/xx // 字符串写法 页面使用:/xx?id=001&title='xx' // 对象写法 页面使用:<router-link :to="{ path:'/xx/xx', query:{ id:xx, name:xx }}"></router-link> 取参页面:route.query.参数名
- params传参数
路由配置:xx/:id/:title 页面使用:<router-link :to="{ name:'组件名',(使用path无效) params:{ id:xx, name:xx }}"></router-link> 取参:route.params.参数名
3.导航守卫
导航守卫又叫路由守卫。分为三种:全局路由守卫,组件内路由守卫,router独享守卫
- 全局路由守卫:小区大门保安 可以用来进行权限验证、登录状态检查、路由检查等操作
// 全局前置守卫
router.beforeEach((to, from, next) => {
console.log(to) => // 到哪个页面去?
console.log(from) => // 从哪个页面来?
//next() => // 一个回调函数
// next(false): 中断当前的导航。
// next('/') 或者 next({ path: '/' }): 当前的导航被中断,跳转到一个不同的地址。
if (to.path !== '/login' && !isLoggedIn) {
// 如果用户未登录且访问的不是登录页,则重定向到登录页
next('/login');
} else {
// 已登录或者访问的是登录页,则正常跳转
next();
}
}
// 全局后置守卫
router.afterEach(to,from) = {}
- 组件内路由守卫:这些守卫是直接写在组件内部的方法,用于对组件内部的路由变化做出反应
export default {
验证身份,加载异步数据
beforeRouteEnter(to, from, next) {
// 在组件实例创建之前调用
// 可以使用回调函数或返回一个Promise延迟进入
},
取消导航
beforeRouteLeave(to, from, next) {
// 在离开当前路由时调用
// 可以进行确认、保存数据等操作
},
条件渲染和动态路由
beforeRouteUpdate(to, from, next) {
// 在当前路由被复用时调用
// 可以对参数的变化作出响应
}
}
- router独享守卫:这些守卫可以直接在路由配置中定义,只对特定的路由生效。
// index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const routes = [
{
path: '/example',
component: ExampleComponent,
beforeEnter: (to, from, next) => {
// 在单个路由的独享守卫逻辑
}
},
// ...其他路由配置...
];
const router = new VueRouter({
routes
});
export default router;
Vue3 Composition API ,如何自定义一个 Hook 抽离公共逻辑
hook
本质是一个函数
,把setup函数中使用的Composition API进行了封装
复用代码, 让setup中的逻辑更清楚易懂。
// 案例 自定义动态时间
<template>
<div>
<h3>
{{ p.times }}
</h3>
</div>
</template>
<script lang="ts">
import { reactive,onMounted,onBeforeUnmount } from 'vue'
import useTimes from "./hooks/userPosition.js"
export default {
setup () {
const p = useTimes()
return {
p
}
}
}
</script>
import {ref,onMounted,onBeforeUnmount } from 'vue'
export default function(){
let times = ref(new Date())
let id = null
onMounted(()=>{
id = setInterval(() => {
times.value = new Date()
}, 1000);
})
onBeforeUnmount(()=>{
clearInterval(id)
})
return {
times
}
}