还记得那些年被高阶组件支配的恐惧吗?props命名冲突、组件嵌套过深、调试困难...每次修改组件都像在拆炸弹。如果你还在Vue 3中苦苦挣扎如何复用组件逻辑,今天这篇文章就是为你准备的。
我将带你彻底告别HOC的痛点,掌握3种更现代、更优雅的代码复用方案。这些方案都是基于Vue 3的Composition API,不仅解决了HOC的老问题,还能让你的代码更加清晰和可维护。
为什么说HOC在Vue 3中已经过时?
先来看一个典型的高阶组件例子。假设我们需要给多个组件添加用户登录状态检查:
1// 传统的HOC实现 2function withAuth(WrappedComponent) { 3 return { 4 name: `WithAuth${WrappedComponent.name}`, 5 data() { 6 return { 7 isLoggedIn: false, 8 userInfo: null 9 } 10 }, 11 async mounted() { 12 // 检查登录状态 13 const user = await checkLoginStatus() 14 this.isLoggedIn = !!user 15 this.userInfo = user 16 }, 17 render() { 18 // 传递所有props和事件 19 return h(WrappedComponent, { 20 ...this.$attrs, 21 ...this.$props, 22 isLoggedIn: this.isLoggedIn, 23 userInfo: this.userInfo 24 }) 25 } 26 } 27} 28 29// 使用HOC 30const UserProfileWithAuth = withAuth(UserProfile) 31
这个HOC看似解决了问题,但实际上带来了不少麻烦。首先是props冲突风险,如果被包裹的组件已经有isLoggedIn这个prop,就会产生命名冲突。其次是调试困难,在Vue Devtools中你会看到一堆WithAuth前缀的组件,很难追踪原始组件。
最重要的是,在Vue 3的Composition API时代,我们有更好的选择。
方案一:Composition函数 - 最推荐的替代方案
Composition API的核心思想就是逻辑复用,让我们看看如何用composable函数重构上面的认证逻辑:
1// 使用Composition函数 2import { ref, onMounted } from 'vue' 3import { checkLoginStatus } from '@/api/auth' 4 5// 将认证逻辑提取为独立的composable 6export function useAuth() { 7 const isLoggedIn = ref(false) 8 const userInfo = ref(null) 9 const loading = ref(true) 10 11 const checkAuth = async () => { 12 try { 13 loading.value = true 14 const user = await checkLoginStatus() 15 isLoggedIn.value = !!user 16 userInfo.value = user 17 } catch (error) { 18 console.error('认证检查失败:', error) 19 isLoggedIn.value = false 20 userInfo.value = null 21 } finally { 22 loading.value = false 23 } 24 } 25 26 onMounted(() => { 27 checkAuth() 28 }) 29 30 return { 31 isLoggedIn, 32 userInfo, 33 loading, 34 checkAuth 35 } 36} 37 38// 在组件中使用 39import { useAuth } from '@/composables/useAuth' 40 41export default { 42 name: 'UserProfile', 43 setup() { 44 const { isLoggedIn, userInfo, loading } = useAuth() 45 46 return { 47 isLoggedIn, 48 userInfo, 49 loading 50 } 51 } 52} 53
这种方式的优势很明显。逻辑完全独立,不会产生props冲突。在Devtools中调试时,你能清晰地看到原始组件和响应式数据。而且这个useAuth函数可以在任何组件中复用,不需要额外的组件嵌套。
方案二:渲染函数与插槽的完美结合
对于需要控制UI渲染的场景,我们可以结合渲染函数和插槽来实现更灵活的逻辑复用:
1// 使用渲染函数和插槽 2import { h } from 'vue' 3 4export default { 5 name: 'AuthWrapper', 6 setup(props, { slots }) { 7 const { isLoggedIn, userInfo, loading } = useAuth() 8 9 return () => { 10 if (loading.value) { 11 // 加载状态显示加载UI 12 return slots.loading ? slots.loading() : h('div', '加载中...') 13 } 14 15 if (!isLoggedIn.value) { 16 // 未登录显示登录提示 17 return slots.unauthorized ? slots.unauthorized() : h('div', '请先登录') 18 } 19 20 // 已登录状态渲染默认插槽,并传递用户数据 21 return slots.default ? slots.default({ 22 user: userInfo.value 23 }) : null 24 } 25 } 26} 27 28// 使用方式 29<template> 30 <AuthWrapper> 31 <template #loading> 32 <div class="skeleton-loader">正在检查登录状态...</div> 33 </template> 34 35 <template #unauthorized> 36 <div class="login-prompt"> 37 <h3>需要登录</h3> 38 <button @click="redirectToLogin">立即登录</button> 39 </div> 40 </template> 41 42 <template #default="{ user }"> 43 <UserProfile :user="user" /> 44 </template> 45 </AuthWrapper> 46</template> 47
这种方式保留了组件的声明式特性,同时提供了完整的UI控制能力。你可以为不同状态提供不同的UI,而且组件结构在Devtools中保持清晰。
方案三:自定义指令处理DOM相关逻辑
对于需要直接操作DOM的逻辑复用,自定义指令是不错的选择:
1// 权限控制指令 2import { useAuth } from '@/composables/useAuth' 3 4const authDirective = { 5 mounted(el, binding) { 6 const { isLoggedIn, userInfo } = useAuth() 7 8 const { value: requiredRole } = binding 9 10 // 如果没有登录,隐藏元素 11 if (!isLoggedIn.value) { 12 el.style.display = 'none' 13 return 14 } 15 16 // 如果需要特定角色但用户没有权限,隐藏元素 17 if (requiredRole && userInfo.value?.role !== requiredRole) { 18 el.style.display = 'none' 19 } 20 }, 21 updated(el, binding) { 22 // 权限变化时重新检查 23 authDirective.mounted(el, binding) 24 } 25} 26 27// 注册指令 28app.directive('auth', authDirective) 29 30// 在模板中使用 31<template> 32 <button v-auth>只有登录用户能看到这个按钮</button> 33 <button v-auth="'admin'">只有管理员能看到这个按钮</button> 34</template> 35
自定义指令特别适合处理这种与DOM操作相关的逻辑,代码简洁且易于理解。
实战对比:用户权限管理场景
让我们通过一个完整的用户权限管理例子,对比一下HOC和新方案的差异:
1// 传统HOC方式 - 不推荐 2function withUserRole(WrappedComponent, requiredRole) { 3 return { 4 data() { 5 return { 6 currentUser: null 7 } 8 }, 9 computed: { 10 hasPermission() { 11 return this.currentUser?.role === requiredRole 12 } 13 }, 14 render() { 15 if (!this.hasPermission) { 16 return h('div', '无权限访问') 17 } 18 return h(WrappedComponent, { 19 ...this.$attrs, 20 ...this.$props, 21 user: this.currentUser 22 }) 23 } 24 } 25} 26 27// Composition函数方式 - 推荐 28export function useUserPermission(requiredRole) { 29 const { userInfo } = useAuth() 30 const hasPermission = computed(() => { 31 return userInfo.value?.role === requiredRole 32 }) 33 34 return { 35 hasPermission, 36 user: userInfo 37 } 38} 39 40// 在组件中使用 41export default { 42 setup() { 43 const { hasPermission, user } = useUserPermission('admin') 44 45 if (!hasPermission.value) { 46 return () => h('div', '无权限访问') 47 } 48 49 return () => h(AdminPanel, { user }) 50 } 51} 52
Composition方式不仅代码更简洁,而且类型推断更友好,测试也更容易。
迁移指南:从HOC平稳过渡
如果你有现有的HOC代码需要迁移,可以按照以下步骤进行:
首先,识别HOC的核心逻辑。比如上面的withAuth核心就是认证状态管理。
然后,将核心逻辑提取为Composition函数:
1// 将HOC逻辑转换为composable 2function withAuthHOC(WrappedComponent) { 3 return { 4 data() { 5 return { 6 isLoggedIn: false, 7 userInfo: null 8 } 9 }, 10 async mounted() { 11 const user = await checkLoginStatus() 12 this.isLoggedIn = !!user 13 this.userInfo = user 14 }, 15 render() { 16 return h(WrappedComponent, { 17 ...this.$props, 18 isLoggedIn: this.isLoggedIn, 19 userInfo: this.userInfo 20 }) 21 } 22 } 23} 24 25// 转换为 26export function useAuth() { 27 const isLoggedIn = ref(false) 28 const userInfo = ref(null) 29 30 onMounted(async () => { 31 const user = await checkLoginStatus() 32 isLoggedIn.value = !!user 33 userInfo.value = user 34 }) 35 36 return { isLoggedIn, userInfo } 37} 38
最后,逐步替换项目中的HOC使用,可以先从新组件开始采用新方案,再逐步重构旧组件。
选择合适方案的决策指南
面对不同的场景,该如何选择最合适的方案呢?
当你需要复用纯逻辑时,比如数据获取、状态管理,选择Composition函数。这是最灵活和可复用的方案。
当你需要复用包含UI的逻辑时,比如加载状态、空状态,选择渲染函数与插槽组合。这提供了最好的UI控制能力。
当你需要操作DOM时,比如权限控制隐藏、点击外部关闭,选择自定义指令。这是最符合Vue设计理念的方式。
记住一个原则:能用Composition函数解决的问题,就不要用组件包装。保持组件的纯粹性,让逻辑和UI分离。
拥抱Vue 3的新范式
通过今天的分享,相信你已经看到了Vue 3为逻辑复用带来的全新可能性。从HOC到Composition API,不仅仅是API的变化,更是开发思维的升级。
HOC代表的组件包装模式已经成为过去,而基于函数的组合模式正是未来。这种转变让我们的代码更加清晰、可测试、可维护。
下次当你想要复用逻辑时,不妨先想一想:这个需求真的需要包装组件吗?还是可以用一个简单的Composition函数来解决?
希望这些方案能够帮助你写出更优雅的Vue代码。如果你在迁移过程中遇到任何问题,欢迎在评论区分享你的经历和困惑。
《Vue高阶组件已过时?这3种新方案让你的代码更优雅》 是转载文章,点击查看原文。