业务模块
...
项目依赖
注:node > v12.0.0
{
"axios": "^0.24.0",
"vant": "^3.2.8",
"vue": "^3.2.16",
"vue-router": "^4.0.12",
"vuex": "^4.0.2",
"sass": "^1.43.4",
"typescript": "^4.4.3",
"vite": "^2.6.4",
}
一个是定义属性,一个是代理
define 定义 property 属性
(obj , prop , descriptor)
description 叙述、表述
descript 动词
descriptor 描述项集合
Vue3 是一个革命性的版本,它不仅带来了性能的提升,更重要的是引入了组合式 API,彻底改变了我们编写 Vue 组件的方式。本文将深入探讨 Vue3 的核心特性,并通过实际案例展示如何更好地使用这些特性。
组合式 API 是 Vue3 最重要的特性之一,它解决了 Vue2 中代码组织的问题,使得逻辑复用和代码组织变得更加灵活。
setup()
是组合式 API 的入口点,它在组件实例创建之前执行setup
在组件实例创建之前执行,所以无法访问 this
setup
函数接收两个参数:props
和 context
import { defineComponent } from 'vue'
export default defineComponent({
props: {
title: String
},
setup(props, context) {
// props 是响应式的,但不要解构它
console.log(props.title)
// context 包含 attrs, slots, emit 等
const { attrs, slots, emit } = context
return {
// 返回的内容会暴露给模板
}
}
})
Vue3 的响应式系统基于 Proxy 实现,提供了更强大的响应式能力。
import { ref, watchEffect } from 'vue'
export default {
setup() {
// ref 用于基本类型的响应式
const count = ref(0)
// 访问 ref 的值需要使用 .value
console.log(count.value) // 0
// 在模板中会自动解包,不需要 .value
const increment = () => {
count.value++
}
// watchEffect 会自动追踪依赖
watchEffect(() => {
console.log('count is:', count.value)
})
return {
count,
increment
}
}
}
import { reactive, computed, watch } from 'vue'
export default {
setup() {
// reactive 用于对象的响应式
const state = reactive({
count: 0,
double: computed(() => state.count * 2),
history: []
})
// 监听单个属性
watch(
() => state.count,
(newValue, oldValue) => {
state.history.push({
newValue,
oldValue,
timestamp: new Date()
})
}
)
// 深度监听对象
watch(
() => state,
(newValue) => {
console.log('state changed:', newValue)
},
{ deep: true }
)
return {
state
}
}
}
Vue3 的生命周期钩子都带有 on
前缀,并且只能在 setup
中使用:
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured
} from 'vue'
export default {
setup() {
onBeforeMount(() => {
console.log('组件挂载前')
})
onMounted(() => {
console.log('组件已挂载')
// 可以在这里进行 DOM 操作
})
onBeforeUpdate(() => {
console.log('组件更新前')
})
onUpdated(() => {
console.log('组件已更新')
})
onBeforeUnmount(() => {
console.log('组件卸载前')
})
onUnmounted(() => {
console.log('组件已卸载')
// 可以在这里清理定时器、事件监听等
})
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误:', err)
return false // 阻止错误继续传播
})
}
}
import { reactive, toRefs, toRef } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
name: 'Vue3'
})
// toRefs 将整个对象转换为响应式引用
const { count, name } = toRefs(state)
// toRef 创建单个属性的响应式引用
const countRef = toRef(state, 'count')
// 这些引用都是响应式的
console.log(count.value) // 0
console.log(name.value) // 'Vue3'
return {
count,
name,
countRef
}
}
}
import {
isRef,
unref,
isReactive,
isReadonly,
markRaw
} from 'vue'
export default {
setup() {
const count = ref(0)
// 检查是否是 ref
console.log(isRef(count)) // true
// 获取 ref 的值,如果不是 ref 则返回原值
console.log(unref(count)) // 0
const state = reactive({})
// 检查是否是响应式对象
console.log(isReactive(state)) // true
// 检查是否是只读对象
console.log(isReadonly(readonly(state))) // true
// 标记对象为非响应式
const raw = markRaw({ count: 0 })
}
}
自定义 Hook 是组合式 API 最强大的特性之一,它让我们能够轻松地复用逻辑。
import { toRefs, reactive, onMounted, onUnmounted } from 'vue'
function useMousePosition() {
const state = reactive({
x: 0,
y: 0,
isMoving: false
})
const updateMouse = (e: MouseEvent) => {
state.x = e.pageX
state.y = e.pageY
state.isMoving = true
// 使用 requestAnimationFrame 优化性能
requestAnimationFrame(() => {
state.isMoving = false
})
}
onMounted(() => {
document.addEventListener('mousemove', updateMouse)
})
onUnmounted(() => {
document.removeEventListener('mousemove', updateMouse)
})
return toRefs(state)
}
import { ref, onMounted } from 'vue'
interface UseFetchOptions {
immediate?: boolean
onSuccess?: (data: any) => void
onError?: (error: Error) => void
}
function useFetch<T>(url: string, options: UseFetchOptions = {}) {
const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const loading = ref(false)
const fetchData = async () => {
loading.value = true
try {
const response = await fetch(url)
data.value = await response.json()
options.onSuccess?.(data.value)
} catch (e) {
error.value = e as Error
options.onError?.(e as Error)
} finally {
loading.value = false
}
}
if (options.immediate) {
onMounted(fetchData)
}
return {
data,
error,
loading,
fetchData
}
}
// 使用示例
export default {
setup() {
const { data, error, loading, fetchData } = useFetch('/api/users', {
immediate: true,
onSuccess: (data) => {
console.log('数据加载成功:', data)
}
})
return {
data,
error,
loading,
fetchData
}
}
}
Teleport 是 Vue3 新增的组件,用于将组件的内容传送到 DOM 树的其他位置。这在处理模态框、弹出框等场景特别有用。
<template>
<div class="app">
<button @click="showModal = true">打开模态框</button>
<Teleport to="body">
<div v-if="showModal" class="modal-overlay" @click="closeModal">
<div class="modal-content" @click.stop>
<h2>{{ title }}</h2>
<p>{{ content }}</p>
<div class="modal-footer">
<button @click="confirm">确认</button>
<button @click="closeModal">取消</button>
</div>
</div>
</div>
</Teleport>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
props: {
title: {
type: String,
default: '提示'
},
content: {
type: String,
required: true
}
},
setup(props, { emit }) {
const showModal = ref(false)
const closeModal = () => {
showModal.value = false
emit('close')
}
const confirm = () => {
emit('confirm')
closeModal()
}
return {
showModal,
closeModal,
confirm
}
}
}
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
min-width: 300px;
}
.modal-footer {
margin-top: 20px;
text-align: right;
}
.modal-footer button {
margin-left: 10px;
}
</style>
Vue3 在性能方面有显著提升
import { shallowRef, shallowReactive, markRaw } from 'vue'
export default {
setup() {
// 浅层响应式,只跟踪顶层属性
const shallowState = shallowReactive({
user: {
name: 'Vue3',
address: {
city: 'Beijing'
}
}
})
// 浅层 ref,只跟踪 .value 的变化
const shallowRef = shallowRef({
count: 0
})
// 标记为非响应式
const staticData = markRaw({
config: {
theme: 'dark'
}
})
}
}
import { defineAsyncComponent } from 'vue'
export default {
components: {
// 异步组件
AsyncComponent: defineAsyncComponent(() =>
import('./AsyncComponent.vue')
),
// 带加载状态的异步组件
AsyncComponentWithLoading: defineAsyncComponent({
loader: () => import('./AsyncComponent.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
}
}
Vue3 使用 TypeScript 重写,提供了更好的类型支持。
import { defineComponent, PropType } from 'vue'
interface User {
id: number
name: string
age: number
}
export default defineComponent({
props: {
// 基础类型
title: {
type: String,
required: true
},
// 对象类型
user: {
type: Object as PropType<User>,
required: true
},
// 数组类型
items: {
type: Array as PropType<User[]>,
default: () => []
}
},
setup(props) {
// props 类型会被正确推导
console.log(props.user.name)
return {}
}
})
import { ref, computed } from 'vue'
interface State {
count: number
name: string
}
export default defineComponent({
setup() {
// ref 类型
const count = ref<number>(0)
// reactive 类型
const state = reactive<State>({
count: 0,
name: 'Vue3'
})
// computed 类型
const double = computed<number>(() => count.value * 2)
return {
count,
state,
double
}
}
})
import { onRenderTracked, onRenderTriggered } from 'vue'
export default {
setup() {
onRenderTracked((event) => {
console.log('组件渲染追踪:', {
target: event.target,
type: event.type,
key: event.key
})
})
onRenderTriggered((event) => {
console.log('组件渲染触发:', {
target: event.target,
type: event.type,
key: event.key,
newValue: event.newValue,
oldValue: event.oldValue
})
})
}
}
import { onMounted, onUnmounted } from 'vue'
export default {
setup() {
let startTime
onMounted(() => {
startTime = performance.now()
})
onUnmounted(() => {
const endTime = performance.now()
console.log(`组件生命周期: ${endTime - startTime}ms`)
})
}
}
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
function decrement() {
count.value--
}
return {
count,
double,
increment,
decrement
}
}
// components/Counter.vue
import { useCounter } from '../composables/useCounter'
export default {
setup() {
const { count, double, increment, decrement } = useCounter()
return {
count,
double,
increment,
decrement
}
}
}
// store/counter.js
import { reactive } from 'vue'
export const store = reactive({
count: 0,
increment() {
this.count++
}
})
// components/Counter.vue
import { store } from '../store/counter'
export default {
setup() {
return {
store
}
}
}
从 Vue2 迁移到 Vue3 时,建议采用渐进式迁移策略:
@vue/composition-api
插件在 Vue2 中提前使用组合式 APIdefineComponent
包装组件获得更好的类型推导Vue.observable
Vue3 带来了许多激动人心的新特性,特别是组合式 API 的引入,使得代码组织更加灵活,逻辑复用更加方便。通过合理使用这些特性,我们可以构建出更易维护、性能更好的应用。