Vue3中Composition API的原理與實戰(zhàn)指南
還記得第一次接觸Composition API時的困惑嗎?"這不就是把data、methods寫在一個setup函數(shù)里?"兩年的深度實戰(zhàn)后,我發(fā)現(xiàn)這種想法大錯特錯。Composition API帶來的不僅是語法變化,更是前端開發(fā)范式的根本革新。今天,我想通過真實項目案例,帶你領(lǐng)悟Composition API的精髓。
開篇反思:為什么需要Composition API?
從一個血淚教訓(xùn)說起
去年我接手了一個遺留的Vue2項目,單個組件文件竟然有1500多行代碼!讓我們看看這個"怪物"組件的結(jié)構(gòu):
// 某個用戶管理頁面 - 典型的Options API意大利面條代碼 export default { data() { return { // 用戶數(shù)據(jù) (50行) userList: [], currentUser: {}, userForm: { /* 巨大的表單對象 */ }, // 分頁數(shù)據(jù) (20行) pagination: { /* ... */ }, // 搜索數(shù)據(jù) (30行) searchFilters: { /* ... */ }, // 權(quán)限數(shù)據(jù) (40行) permissions: { /* ... */ }, // 上傳數(shù)據(jù) (25行) uploadConfig: { /* ... */ }, // 還有更多... (200+行data) } }, computed: { // 300+行的computed,各種邏輯混雜 filteredUsers() { /* 復(fù)雜的過濾邏輯 */ }, userStats() { /* 統(tǒng)計邏輯 */ }, permissionMatrix() { /* 權(quán)限計算 */ }, // ... 更多computed }, methods: { // 800+行的methods,什么都有 fetchUsers() { /* 獲取用戶 */ }, validateForm() { /* 表單驗證 */ }, uploadFile() { /* 文件上傳 */ }, calculatePermissions() { /* 權(quán)限計算 */ }, // ... 幾十個方法 } }
維護(hù)這樣的代碼簡直是噩夢:
- 查找困難:想找個方法要滾動半天
- Bug頻發(fā):修改一處影響多處
- 邏輯混亂:用戶管理、權(quán)限、上傳邏輯全混在一起
- 無法復(fù)用:邏輯和組件強(qiáng)綁定
Composition API的解決思路
Composition API的核心思想是按功能邏輯組織代碼,而不是按選項類型組織:
// 重構(gòu)后的清爽代碼 <script setup> // 每個功能都是獨立、可復(fù)用的邏輯單元 import { useUserManagement } from './composables/useUserManagement' import { usePagination } from './composables/usePagination' import { useSearch } from './composables/useSearch' import { usePermissions } from './composables/usePermissions' import { useFileUpload } from './composables/useFileUpload' // 聲明式地組合功能 const users = useUserManagement() const pagination = usePagination({ pageSize: 20 }) const search = useSearch(['name', 'email', 'role']) const permissions = usePermissions() const upload = useFileUpload({ accept: '.jpg,.png' }) // 功能間的協(xié)調(diào)邏輯也變得清晰 const handleSearch = async () => { pagination.reset() await users.fetch({ ...search.params, page: pagination.current, size: pagination.size }) } </script>
立即顯現(xiàn)的優(yōu)勢:
- 邏輯清晰:每個功能都有獨立的命名空間
- 高度復(fù)用:usePagination可以在任何列表頁使用
- 易于測試:每個composable都可以獨立測試
- 類型友好:完美的TypeScript支持
一、深入理解:setup函數(shù)的運行機(jī)制
setup到底在做什么
很多人以為setup只是把data、methods包裝了一下,這種理解太淺了。讓我用一個例子來說明setup的深層含義:
// 傳統(tǒng)思維:這只是語法糖? <script setup> const count = ref(0) const increment = () => count.value++ </script> // 實際上編譯后的代碼 export default { setup() { const count = ref(0) const increment = () => count.value++ // 返回的對象會被暴露給模板 return { count, increment } } }
setup函數(shù)的特殊之處:
1. 執(zhí)行時機(jī)的精妙設(shè)計
// setup的執(zhí)行時機(jī) export default { beforeCreate() { console.log('1. beforeCreate') }, setup() { console.log('2. setup執(zhí)行') // 在beforeCreate和created之間 onBeforeMount(() => { console.log('4. beforeMount') }) onMounted(() => { console.log('5. mounted') }) }, created() { console.log('3. created') } }
為什么要在這個時機(jī)執(zhí)行?
因為setup需要在響應(yīng)式系統(tǒng)初始化之后,但在實例創(chuàng)建之前運行。這樣既能使用響應(yīng)式API,又能影響實例的創(chuàng)建過程。
2. 作用域的獨立性
// 每個組件實例都有獨立的setup作用域 <script setup> // 這個變量只屬于當(dāng)前組件實例 let privateCounter = 0 const publicCounter = ref(0) // 這個函數(shù)也是私有的,外部無法訪問 function internalHelper() { privateCounter++ } // 只有通過defineExpose才能暴露給外部 defineExpose({ publicCounter, reset: () => { publicCounter.value = 0 privateCounter = 0 } }) </script>
從心智模型理解Composition API
傳統(tǒng)Options API的心智模型
// Options API:按選項類型分組 const component = { data: { // 所有數(shù)據(jù)放這里 userInfo: {}, products: [], cart: {} }, computed: { // 所有計算屬性放這里 userName() { return this.userInfo.name }, productCount() { return this.products.length }, cartTotal() { return this.cart.total } }, methods: { // 所有方法放這里 fetchUser() { /* */ }, fetchProducts() { /* */ }, addToCart() { /* */ } } }
這種組織方式類似于按文件類型整理的文件夾:
項目文件夾/
├── 所有圖片/
├── 所有文檔/
├── 所有視頻/
└── 所有代碼/
當(dāng)你要找"用戶管理相關(guān)的內(nèi)容"時,需要在多個文件夾中翻找。
Composition API的心智模型
// Composition API:按功能邏輯分組 function useUserManagement() { const userInfo = ref({}) const userName = computed(() => userInfo.value.name) const fetchUser = async () => { /* */ } return { userInfo, userName, fetchUser } } function useProductManagement() { const products = ref([]) const productCount = computed(() => products.value.length) const fetchProducts = async () => { /* */ } return { products, productCount, fetchProducts } } function useCartManagement() { const cart = ref({}) const cartTotal = computed(() => cart.value.total) const addToCart = (product) => { /* */ } return { cart, cartTotal, addToCart } }
這種組織方式類似于按項目功能整理的文件夾:
項目文件夾/
├── 用戶管理/
│ ├── 用戶信息.jpg
│ ├── 用戶文檔.doc
│ └── 用戶代碼.js
├── 產(chǎn)品管理/
└── 購物車管理/
想找什么功能,直接去對應(yīng)的文件夾即可。
深入剖析:Composition的三個層次
在實際項目中,我總結(jié)出Composition API有三個應(yīng)用層次:
層次1:基礎(chǔ)組合(替代data、methods)
<script setup> // 最簡單的狀態(tài)管理 const loading = ref(false) const data = ref([]) const fetchData = async () => { loading.value = true try { data.value = await api.getData() } finally { loading.value = false } } onMounted(fetchData) </script>
這個層次只是語法的改變,還沒有體現(xiàn)出Composition API的真正優(yōu)勢。
層次2:邏輯提?。▌?chuàng)建可復(fù)用的函數(shù))
// composables/useAsyncData.js export function useAsyncData(apiFn) { const loading = ref(false) const data = ref(null) const error = ref(null) const execute = async (...args) => { loading.value = true error.value = null try { data.value = await apiFn(...args) } catch (err) { error.value = err } finally { loading.value = false } } return { loading, data, error, execute } } // 在組件中使用 <script setup> import { useAsyncData } from '@/composables/useAsyncData' import { api } from '@/api' const { loading, data, error, execute: fetchUsers } = useAsyncData(api.getUsers) const { loading: productLoading, data: products, execute: fetchProducts } = useAsyncData(api.getProducts) onMounted(() => { fetchUsers() fetchProducts() }) </script>
這個層次開始體現(xiàn)復(fù)用性,但還是比較簡單的邏輯。
層次3:復(fù)雜狀態(tài)編排(企業(yè)級應(yīng)用)
// composables/useUserManagement.js export function useUserManagement() { // 狀態(tài)管理 const users = ref([]) const currentUser = ref(null) const loading = ref(false) // 緩存策略 const cache = new Map() const cacheKey = computed(() => { return `users_${JSON.stringify(searchParams.value)}` }) // 搜索參數(shù) const searchParams = ref({ keyword: '', role: '', status: 'active' }) // 依賴注入 const authStore = inject('authStore') const notificationBus = inject('notificationBus') // 復(fù)雜的計算邏輯 const filteredUsers = computed(() => { return users.value.filter(user => { if (!authStore.hasPermission('view_user', user)) return false if (searchParams.value.keyword && !user.name.includes(searchParams.value.keyword)) return false if (searchParams.value.role && user.role !== searchParams.value.role) return false return true }) }) // 副作用管理 watch(searchParams, async (newParams, oldParams) => { // 防抖搜索 await debounce(() => fetchUsers(), 300) }, { deep: true }) // 緩存的獲取邏輯 const fetchUsers = async () => { const key = cacheKey.value if (cache.has(key)) { users.value = cache.get(key) return } loading.value = true try { const data = await userApi.getUsers(searchParams.value) users.value = data cache.set(key, data) notificationBus.emit('users:fetched', data) } catch (error) { notificationBus.emit('error', error.message) } finally { loading.value = false } } // 清理邏輯 onUnmounted(() => { cache.clear() }) return { // 狀態(tài) users: readonly(users), currentUser: readonly(currentUser), loading: readonly(loading), searchParams, // 計算屬性 filteredUsers, // 方法 fetchUsers, setCurrentUser: (user) => currentUser.value = user, clearSearch: () => { searchParams.value = { keyword: '', role: '', status: 'active' } } } }
這個層次才是Composition API的真正威力所在:
- 狀態(tài)管理:完整的響應(yīng)式狀態(tài)
- 緩存策略:智能的數(shù)據(jù)緩存
- 依賴注入:與其他系統(tǒng)的集成
- 副作用管理:自動化的數(shù)據(jù)同步
- 權(quán)限控制:細(xì)粒度的訪問控制
- 錯誤處理:統(tǒng)一的錯誤處理機(jī)制
二、構(gòu)建企業(yè)級的Composition Hook
2.1 用戶管理Hook的完整實現(xiàn)
// hooks/useUser.js import { ref, computed, watch, provide, inject } from 'vue' import { userApi } from '@/api/user' import { useLocalStorage } from '@/hooks/useLocalStorage' import { useEventBus } from '@/hooks/useEventBus' // 全局用戶狀態(tài)管理 const USER_INJECTION_KEY = Symbol('user') export function createUserProvider() { const user = ref(null) const loading = ref(false) const error = ref(null) // 持久化用戶token const { value: token, setValue: setToken, removeValue: removeToken } = useLocalStorage('user_token', '') // 事件總線 const { emit, on } = useEventBus() // 計算屬性 const isAuthenticated = computed(() => !!user.value?.id) const userName = computed(() => user.value?.name || '游客') const userAvatar = computed(() => user.value?.avatar || '/default-avatar.png') const permissions = computed(() => user.value?.permissions || []) // 檢查權(quán)限 const hasPermission = (permission) => { return permissions.value.includes(permission) || permissions.value.includes('admin') } // 登錄方法 const login = async (credentials) => { try { loading.value = true error.value = null const response = await userApi.login(credentials) user.value = response.user setToken(response.token) // 發(fā)布登錄成功事件 emit('user:login', user.value) return response } catch (err) { error.value = err.message throw err } finally { loading.value = false } } // 登出方法 const logout = async () => { try { await userApi.logout() } catch (err) { console.warn('Logout API failed:', err) } finally { user.value = null removeToken() emit('user:logout') } } // 獲取用戶信息 const fetchUserInfo = async () => { if (!token.value) return try { loading.value = true const userInfo = await userApi.getUserInfo() user.value = userInfo } catch (err) { // token可能已過期 if (err.status === 401) { logout() } error.value = err.message } finally { loading.value = false } } // 更新用戶信息 const updateProfile = async (profileData) => { try { loading.value = true const updatedUser = await userApi.updateProfile(profileData) user.value = { ...user.value, ...updatedUser } emit('user:profile-updated', user.value) return updatedUser } catch (err) { error.value = err.message throw err } finally { loading.value = false } } // 監(jiān)聽token變化,自動獲取用戶信息 watch(token, (newToken) => { if (newToken) { fetchUserInfo() } else { user.value = null } }, { immediate: true }) const userProvider = { user: readonly(user), loading: readonly(loading), error: readonly(error), isAuthenticated, userName, userAvatar, permissions, hasPermission, login, logout, fetchUserInfo, updateProfile } provide(USER_INJECTION_KEY, userProvider) return userProvider } // 在組件中使用 export function useUser() { const userProvider = inject(USER_INJECTION_KEY) if (!userProvider) { throw new Error('useUser must be used within a user provider') } return userProvider }
2.2 異步數(shù)據(jù)獲取Hook
// hooks/useAsyncData.js import { ref, computed, watch, unref } from 'vue' export function useAsyncData(fetcher, options = {}) { const { immediate = true, resetOnExecute = true, shallow = true, throwError = false } = options const data = shallow ? shallowRef(null) : ref(null) const loading = ref(false) const error = ref(null) const executeCount = ref(0) const execute = async (...args) => { try { executeCount.value++ loading.value = true if (resetOnExecute) { error.value = null } const result = await fetcher(...args) data.value = result return result } catch (err) { error.value = err if (throwError) { throw err } return null } finally { loading.value = false } } // 重試邏輯 const retry = () => execute() // 刷新數(shù)據(jù) const refresh = () => execute() // 清空數(shù)據(jù) const clear = () => { data.value = null error.value = null } // 計算狀態(tài) const isFirstLoad = computed(() => executeCount.value === 0) const hasData = computed(() => data.value != null) const hasError = computed(() => error.value != null) if (immediate) { execute() } return { data: readonly(data), loading: readonly(loading), error: readonly(error), executeCount: readonly(executeCount), isFirstLoad, hasData, hasError, execute, retry, refresh, clear } } // 使用示例 export function useProducts() { const { data: products, loading, error, execute: fetchProducts, refresh: refreshProducts } = useAsyncData(() => productApi.getList()) const productCount = computed(() => products.value?.length || 0) const searchProducts = async (keyword) => { return fetchProducts({ keyword }) } return { products, loading, error, productCount, fetchProducts, refreshProducts, searchProducts } }
2.3 表單處理Hook
// hooks/useForm.js import { ref, reactive, computed, watch, nextTick } from 'vue' export function useForm(initialValues = {}, options = {}) { const { validateOnChange = false, validateOnBlur = true } = options // 表單數(shù)據(jù) const formData = reactive({ ...initialValues }) // 表單驗證狀態(tài) const errors = ref({}) const touched = ref({}) const validating = ref(false) // 驗證規(guī)則 const rules = ref({}) // 設(shè)置驗證規(guī)則 const setRules = (newRules) => { rules.value = newRules } // 單個字段驗證 const validateField = async (field) => { const rule = rules.value[field] if (!rule) return true try { validating.value = true // 支持函數(shù)和數(shù)組兩種規(guī)則格式 const fieldRules = Array.isArray(rule) ? rule : [rule] for (const fieldRule of fieldRules) { if (typeof fieldRule === 'function') { const result = await fieldRule(formData[field], formData) if (result !== true) { errors.value[field] = result return false } } else if (fieldRule.validator) { const result = await fieldRule.validator(formData[field], formData) if (!result) { errors.value[field] = fieldRule.message || '驗證失敗' return false } } } // 驗證通過,清除錯誤 delete errors.value[field] return true } catch (err) { errors.value[field] = err.message || '驗證出錯' return false } finally { validating.value = false } } // 全表單驗證 const validate = async () => { const fieldNames = Object.keys(rules.value) const results = await Promise.all( fieldNames.map(field => validateField(field)) ) return results.every(result => result) } // 設(shè)置字段值 const setFieldValue = (field, value) => { formData[field] = value if (validateOnChange) { nextTick(() => validateField(field)) } } // 設(shè)置字段觸摸狀態(tài) const setFieldTouched = (field, isTouched = true) => { touched.value[field] = isTouched if (isTouched && validateOnBlur) { validateField(field) } } // 重置表單 const resetForm = () => { Object.keys(formData).forEach(key => { formData[key] = initialValues[key] }) errors.value = {} touched.value = {} } // 提交表單 const submitForm = async (onSubmit) => { // 標(biāo)記所有字段為已觸摸 Object.keys(rules.value).forEach(field => { touched.value[field] = true }) const isValid = await validate() if (isValid && onSubmit) { return onSubmit(formData) } return isValid } // 計算屬性 const hasErrors = computed(() => Object.keys(errors.value).length > 0) const isSubmittable = computed(() => !hasErrors.value && !validating.value) // 獲取字段錯誤 const getFieldError = (field) => { return touched.value[field] ? errors.value[field] : null } // 監(jiān)聽表單數(shù)據(jù)變化 watch( () => formData, () => { if (validateOnChange) { Object.keys(touched.value).forEach(field => { if (touched.value[field]) { validateField(field) } }) } }, { deep: true } ) return { formData, errors: readonly(errors), touched: readonly(touched), validating: readonly(validating), hasErrors, isSubmittable, setRules, validateField, validate, setFieldValue, setFieldTouched, resetForm, submitForm, getFieldError } }
三、高級Hook模式與最佳實踐
3.1 組合式Hook的設(shè)計模式
// hooks/useUserProfile.js - 復(fù)合型Hook import { computed } from 'vue' import { useUser } from './useUser' import { useForm } from './useForm' import { useAsyncData } from './useAsyncData' export function useUserProfile() { // 組合多個基礎(chǔ)Hook const { user, updateProfile } = useUser() // 表單處理 const { formData, errors, setRules, submitForm, resetForm, getFieldError } = useForm({ name: '', email: '', bio: '', avatar: '' }) // 設(shè)置驗證規(guī)則 setRules({ name: [ (value) => value ? true : '姓名不能為空', (value) => value.length >= 2 ? true : '姓名至少2個字符' ], email: [ (value) => value ? true : '郵箱不能為空', (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? true : '郵箱格式不正確' ] }) // 頭像上傳 const { data: uploadResult, loading: uploading, execute: uploadAvatar } = useAsyncData(async (file) => { const formData = new FormData() formData.append('avatar', file) return uploadApi.uploadAvatar(formData) }, { immediate: false }) // 監(jiān)聽用戶數(shù)據(jù)變化,同步到表單 watch(user, (newUser) => { if (newUser) { Object.assign(formData, { name: newUser.name || '', email: newUser.email || '', bio: newUser.bio || '', avatar: newUser.avatar || '' }) } }, { immediate: true }) // 監(jiān)聽頭像上傳結(jié)果 watch(uploadResult, (result) => { if (result?.url) { formData.avatar = result.url } }) // 提交表單 const handleSubmit = () => { return submitForm(async (data) => { await updateProfile(data) // 可以添加成功提示等邏輯 }) } // 計算屬性 const hasChanges = computed(() => { if (!user.value) return false return Object.keys(formData).some(key => formData[key] !== user.value[key] ) }) return { // 表單狀態(tài) formData, errors, getFieldError, hasChanges, // 頭像上傳 uploading, uploadAvatar, // 操作方法 handleSubmit, resetForm } }
3.2 響應(yīng)式緩存Hook
// hooks/useCache.js import { ref, computed, watch } from 'vue' export function useCache(key, fetcher, options = {}) { const { ttl = 5 * 60 * 1000, // 5分鐘緩存 staleWhileRevalidate = true, maxRetries = 3 } = options const cache = new Map() const loading = ref(false) const error = ref(null) const retryCount = ref(0) const getCacheKey = (params) => { return typeof key === 'function' ? key(params) : key } const isStale = (cacheEntry) => { return Date.now() - cacheEntry.timestamp > ttl } const fetchData = async (params) => { const cacheKey = getCacheKey(params) const cacheEntry = cache.get(cacheKey) // 如果有緩存且未過期,直接返回 if (cacheEntry && !isStale(cacheEntry)) { return cacheEntry.data } // 如果啟用了staleWhileRevalidate且有過期緩存 if (staleWhileRevalidate && cacheEntry) { // 先返回過期數(shù)據(jù),后臺重新獲取 setTimeout(() => backgroundFetch(params), 0) return cacheEntry.data } return foregroundFetch(params) } const foregroundFetch = async (params) => { try { loading.value = true error.value = null const data = await fetcher(params) const cacheKey = getCacheKey(params) cache.set(cacheKey, { data, timestamp: Date.now() }) retryCount.value = 0 return data } catch (err) { error.value = err if (retryCount.value < maxRetries) { retryCount.value++ return foregroundFetch(params) } throw err } finally { loading.value = false } } const backgroundFetch = async (params) => { try { const data = await fetcher(params) const cacheKey = getCacheKey(params) cache.set(cacheKey, { data, timestamp: Date.now() }) } catch (err) { console.warn('Background fetch failed:', err) } } const invalidateCache = (params) => { const cacheKey = getCacheKey(params) cache.delete(cacheKey) } const clearAllCache = () => { cache.clear() } return { loading: readonly(loading), error: readonly(error), fetchData, invalidateCache, clearAllCache } } // 使用示例 export function useProductList() { const { fetchData, loading, error, invalidateCache } = useCache( (params) => `products:${JSON.stringify(params)}`, (params) => productApi.getList(params), { ttl: 10 * 60 * 1000 } // 10分鐘緩存 ) const products = ref([]) const loadProducts = async (params = {}) => { try { products.value = await fetchData(params) } catch (err) { console.error('Failed to load products:', err) } } const refreshProducts = (params) => { invalidateCache(params) return loadProducts(params) } return { products: readonly(products), loading, error, loadProducts, refreshProducts } }
四、性能優(yōu)化與最佳實踐
4.1 避免響應(yīng)式性能陷阱
// ? 錯誤的做法:過度響應(yīng)式 export function useBadExample() { const heavyData = reactive({ // 大量復(fù)雜嵌套數(shù)據(jù) list: new Array(10000).fill(0).map(i => ({ id: i, details: { /* 復(fù)雜對象 */ } })) }) return { heavyData } } // ? 正確的做法:按需響應(yīng)式 export function useGoodExample() { // 只對需要響應(yīng)的部分使用reactive const selectedIds = ref(new Set()) const viewMode = ref('list') // 大量數(shù)據(jù)使用shallowRef const heavyData = shallowRef([]) // 計算屬性只依賴必要的響應(yīng)式數(shù)據(jù) const selectedItems = computed(() => { return heavyData.value.filter(item => selectedIds.value.has(item.id) ) }) const updateData = (newData) => { // 手動觸發(fā)更新 heavyData.value = newData } return { selectedIds, viewMode, selectedItems, updateData } }
4.2 Hook的依賴注入模式
// plugins/composition-providers.js export function createCompositionProviders(app) { // 全局狀態(tài)提供者 app.provide('globalStore', createGlobalStore()) app.provide('userProvider', createUserProvider()) app.provide('themeProvider', createThemeProvider()) } // main.js import { createApp } from 'vue' import { createCompositionProviders } from './plugins/composition-providers' const app = createApp(App) createCompositionProviders(app) app.mount('#app')
五、總結(jié)與最佳實踐建議
經(jīng)過兩年多的Composition API實踐,我總結(jié)了以下最佳實踐:
5.1 Hook設(shè)計原則
- 單一職責(zé):每個Hook只負(fù)責(zé)一個明確的功能領(lǐng)域
- 可組合性:Hook之間可以自然組合,形成更復(fù)雜的功能
- 可測試性:Hook應(yīng)該易于單元測試
- 類型安全:充分利用TypeScript的類型推導(dǎo)
5.2 命名約定
// ? 推薦的命名方式 useUser() // 用戶相關(guān)邏輯 useAsyncData() // 異步數(shù)據(jù)處理 useLocalStorage() // 本地存儲 useEventBus() // 事件總線 // ? 避免的命名方式 getUserHook() // 冗余的Hook后綴 userLogic() // 不明確的命名 handleUser() // 與方法命名混淆
5.3 代碼組織建議
hooks/
├── core/ # 核心Hook
│ ├── useAsyncData.js
│ ├── useLocalStorage.js
│ └── useEventBus.js
├── business/ # 業(yè)務(wù)Hook
│ ├── useUser.js
│ ├── useProducts.js
│ └── useOrders.js
├── ui/ # UI相關(guān)Hook
│ ├── useModal.js
│ ├── useToast.js
│ └── useForm.js
└── index.js # 統(tǒng)一導(dǎo)出
Composition API的真正價值在于它讓我們能夠以一種更加自然、直觀的方式組織代碼。通過合理的Hook設(shè)計,我們可以構(gòu)建出既高性能又易維護(hù)的Vue3應(yīng)用。
以上就是Vue3中Composition API的原理與實戰(zhàn)指南的詳細(xì)內(nèi)容,更多關(guān)于Vue3 Composition API的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue + AnimeJS實現(xiàn)3d輪播圖的詳細(xì)代碼
輪播圖在開發(fā)中是經(jīng)常用到的,3D輪播圖是其中最常用的一種,所以在這篇文章中將給大家介紹Vue + AnimeJS實現(xiàn)3d輪播圖,文中有詳細(xì)的代碼示例供大家參考,具有一定的參考價值,需要的朋友可以參考下2024-01-01vue+elementUi實現(xiàn)點擊地圖自動填充經(jīng)緯度以及地點
這篇文章主要為大家詳細(xì)介紹了vue+elementUi實現(xiàn)點擊地圖自動填充經(jīng)緯度以及地點,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07關(guān)于SpringBoot與Vue交互跨域問題解決方案
最近在利用springboot+vue整合開發(fā)一個前后端分離的個人博客網(wǎng)站,所以這一篇總結(jié)一下在開發(fā)中遇到的一個問題,關(guān)于解決在使用vue和springboot在開發(fā)前后端分離的項目時,如何解決跨域問題。在這里分別分享兩種方法,分別在前端vue中解決和在后臺springboot中解決。2021-10-10解決vuex數(shù)據(jù)異步造成初始化的時候沒值報錯問題
今天小編大家分享一篇解決vuex數(shù)據(jù)異步造成初始化的時候沒值報錯問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11關(guān)于VanCascader默認(rèn)值(地址code轉(zhuǎn)換)
這篇文章主要介紹了關(guān)于VanCascader默認(rèn)值(地址code轉(zhuǎn)換),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07淺析vue 函數(shù)配置項watch及函數(shù) $watch 源碼分享
這篇文章主要介紹了vue 函數(shù)配置項watch及函數(shù) $watch 源碼分享 ,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-11-11vue?el-date-picker?日期回顯后無法改變問題解決
這篇文章主要介紹了vue?el-date-picker?日期回顯后無法改變問題解決,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04