前言
Vue.js 作为当前最流行的前端框架之一,以其简洁的 API 和灵活的组件化开发模式赢得了广大开发者的喜爱。在日常开发中,掌握一些 Vue 的小知识点能够显著提升开发效率和代码质量。本文将分享一些 Vue 开发中的实用技巧和容易被忽略的细节,帮助你更好地驾驭 Vue。
一、组件通信的多种方式
1. Props 和 Events
最基础的父子组件通信方式,父组件通过 props 向下传递数据,子组件通过 events 向上传递消息。
javascript
// 父组件
<template>
<child-component :message="parentMessage" @child-click="handleClick" />
</template>
<script>
export default {
data() {
return {
parentMessage: 'Hello from parent'
}
},
methods: {
handleClick(payload) {
console.log('Child clicked with:', payload)
}
}
}
</script>
// 子组件
<template>
<button @click="sendToParent">{{ message }}</button>
</template>
<script>
export default {
props: {
message: String
},
methods: {
sendToParent() {
this.$emit('child-click', { data: 'some data' })
}
}
}
</script>
小技巧 :使用 .sync
修饰符可以简化双向绑定的实现:
javascript
// 父组件
<child-component :value.sync="syncValue" />
// 子组件
this.$emit('update:value', newValue)
2. Provide/Inject
对于深层嵌套的组件,props 逐层传递会变得繁琐。这时可以使用 provide/inject:
javascript
// 祖先组件
export default {
provide() {
return {
theme: this.themeData
}
},
data() {
return {
themeData: 'dark'
}
}
}
// 后代组件
export default {
inject: ['theme'],
created() {
console.log(this.theme) // => 'dark'
}
}
注意:provide/inject 绑定不是响应式的,除非你传入一个响应式对象。
3. Event Bus
对于非父子关系的组件,可以使用一个空的 Vue 实例作为事件中心:
javascript
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 组件A
import { EventBus } from './event-bus'
EventBus.$emit('event-name', payload)
// 组件B
import { EventBus } from './event-bus'
EventBus.$on('event-name', payload => {
// 处理事件
})
注意:记得在组件销毁时移除事件监听,避免内存泄漏:
javascript
beforeDestroy() {
EventBus.$off('event-name')
}
二、高效使用计算属性和侦听器
1. 计算属性缓存 vs 方法
计算属性是基于它们的响应式依赖进行缓存的,只有在相关依赖发生改变时才会重新求值。相比之下,每当触发重新渲染时,调用方法总会再次执行函数。
javascript
// 计算属性
computed: {
reversedMessage() {
return this.message.split('').reverse().join('')
}
}
// 方法
methods: {
reverseMessage() {
return this.message.split('').reverse().join('')
}
}
2. 计算属性的 setter
计算属性默认只有 getter,但在需要时你也可以提供一个 setter:
javascript
computed: {
fullName: {
get() {
return this.firstName + ' ' + this.lastName
},
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
3. 深度侦听
当需要侦听对象内部值的变化时,需要使用深度侦听:
javascript
watch: {
someObject: {
handler(newVal, oldVal) {
// 注意:在嵌套的对象中,newVal 和 oldVal 会是同一个对象
},
deep: true,
immediate: true // 立即以表达式的当前值触发回调
}
}
三、动态组件与异步组件
1. 动态组件
使用 <component>
元素和 is
特性可以实现动态组件:
javascript
<component :is="currentComponent"></component>
data() {
return {
currentComponent: 'ComponentA'
}
}
2. 异步组件
对于大型应用,可以将应用分割成小模块,按需从服务器加载:
javascript
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
components: {
AsyncComponent
}
四、自定义指令的妙用
Vue 允许注册自定义指令,用于对普通 DOM 元素进行底层操作。
javascript
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时......
inserted: function(el) {
// 聚焦元素
el.focus()
}
})
// 使用
<input v-focus>
自定义指令的钩子函数
bind
:只调用一次,指令第一次绑定到元素时调用inserted
:被绑定元素插入父节点时调用update
:所在组件的 VNode 更新时调用componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用unbind
:只调用一次,指令与元素解绑时调用
五、高效使用插槽
1. 具名插槽
javascript
// 子组件
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
// 父组件
<child-component>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</child-component>
2. 作用域插槽
让插槽内容能够访问子组件中的数据:
javascript
// 子组件
<ul>
<li v-for="(item, index) in items" :key="index">
<slot :item="item" :index="index"></slot>
</li>
</ul>
// 父组件
<child-component>
<template v-slot:default="slotProps">
<span class="item">{{ slotProps.item }}</span>
<span class="index">{{ slotProps.index }}</span>
</template>
</child-component>
3. 动态插槽名
javascript
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
六、Vue 性能优化小技巧
1. 合理使用 v-if 和 v-show
v-if
是"真正"的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-show
只是简单地切换元素的 CSS 属性display
。
建议 :频繁切换时使用 v-show
,运行时条件很少改变时使用 v-if
。
2. 为列表项设置唯一的 key
使用 v-for
时总是添加 key,且最好使用唯一 ID 而不是数组索引:
javascript
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
3. 避免 v-for 和 v-if 一起使用
当它们处于同一节点时,v-for
的优先级比 v-if
更高,这意味着 v-if
将分别重复运行于每个 v-for
循环中。
不推荐:
javascript
<li v-for="user in users" v-if="user.isActive" :key="user.id">
{{ user.name }}
</li>
推荐:
javascript
<ul v-if="shouldShowUsers">
<li v-for="user in activeUsers" :key="user.id">
{{ user.name }}
</li>
</ul>
computed: {
activeUsers() {
return this.users.filter(user => user.isActive)
}
}
4. 长列表性能优化
对于大数据量的列表,可以使用虚拟滚动技术:
javascript
<virtual-list :size="40" :remain="8" :items="items">
<template v-slot:default="{ item }">
<div>{{ item.content }}</div>
</template>
</virtual-list>
可以使用第三方库如 vue-virtual-scroller
实现。
七、Vue 3 新特性简介
虽然本文主要讨论 Vue 2,但也简单介绍一些 Vue 3 的重要新特性:
1. Composition API
javascript
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
onMounted(() => {
console.log('component is mounted!')
})
return {
count,
doubleCount,
increment
}
}
}
2. 多个根节点
Vue 3 组件可以包含多个根节点:
javascript
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
3. Teleport
将子节点渲染到 DOM 中的其他位置:
javascript
<teleport to="body">
<div class="modal">
I'm a teleported modal!
</div>
</teleport>
结语
Vue.js 是一个功能丰富且灵活的前端框架,掌握这些小技巧可以帮助你写出更高效、更易维护的代码。无论是组件通信、性能优化还是新特性的使用,都需要在实际项目中不断实践和总结。希望本文分享的知识点能为你的 Vue 开发之旅带来帮助!