【HOC】高阶组件在Vue老项目中的实战应用 - 模块任意排序

前言

最近接手了一个颇具挑战性的需求:在现有页面的A、B、C、D四个功能模块基础上,需要实现根据服务端下发的索引值进行动态排序展示的能力。这意味着页面可能需要呈现(D-C-B-A)的倒序排列,或是(A-C-B-D)的特殊组合,甚至是其他任意由后端控制的排列方式,如图;

对于常规的Vue项目,大多数开发肯定认为用jsx可以轻易实现类似的功能,但是很遗憾,我接手的这个页面采用template模板语法编写,单文件组件已膨胀至2000余行,在如此复杂的上下文中,直接重构为JSX或拆分组件都面临巨大风险,我们不得不在现有的代码框架下,寻找最稳妥的解决方案。

实践

分析背景,我们得出结论,要实现这个需求,我们的解决方案应当:

  1. 尽可能不破坏现有的HTML结构
  2. 尽可能不添加多行的JS逻辑

经过方案对比,我们锁定CSS的order属性作为突破口,通过为子元素添加css属性order的方式,控制不同模块在页面中的渲染,简易实现如下:

html 复制代码
<div style="display: flex; flex-direction: column;">
  <div class="moduleA" style="order: 1;">A</div>
  <div class="moduleB" style="order: 2;">B</div>
  <div class="moduleC" style="order: 3;">C</div>
</div>

那么,既然方案的关键找到了,接下来我们如何去合理的实现呢? 起初,我想到的是实现一个组件,组件提供一个slot,父组件将各个模块传入slot,再通过props控制index去排序,比如:

js 复制代码
<template>
  <div :style="{ order: index }">
    <slot></slot>
  </div>
</template>

<script setup>
defineProps({
  orderKey: {
    type: String,
    required: true
  }
})
const getIndex = (orderKey) => {
  // DO SOMETHING ....
  // 这里业务需要 由于父组件已经2000多行, 所以将排序逻辑下沉至子组件
  return index
}
</script>

父组件使用:

js 复制代码
<template>
  <div style="display: flex; flex-direction: column;">
    <Order orderKey="A">
      <ModuleA></ModuleA>
    </Order>
    <Order orderKey="B">
      <ModuleB></ModuleB>
    </Order>
    <Order orderKey="C">
      <ModuleC></ModuleC>
    </Order>
  </div>
</template>

但是,这样做有下面些问题:

  1. 产生冗余DOM层级,破坏原有结构;
  2. 父组件需显式包裹每个模块,违背DRY原则;
  3. 傻瓜式编程,不够优雅(笔者个人认为);

为了解决上述问题,我们可以使用高阶组件(HOC)来实现。高阶组件是一种函数,它接受一个组件作为参数,并返回一个新的组件。这个新的组件可以在原组件的基础上添加一些额外的功能,比如props、生命周期钩子等。 我们可以定义一个HOC组件,它接受一个组件作为参数,并返回一个新的组件,新组件的props中包含一个orderKey属性,用于控制组件的渲染顺序。

javascript 复制代码
import { h, defineComponent, computed } from 'vue'
import { useRoute } from 'vue-router';

export function withOrder(WrappedComponent) {
  return defineComponent({
    name: 'WithOrder',
    props: {
      orderKey: {
        type: String,
        required: true
      }
    },
    setup(props, { attrs }) {

      // 获取对应搜索模块的排序
      const getIndex = (orderKey) => {
        // DO SOMETHING ....
        // 这里业务需要 由于父组件已经2000多行, 所以将排序逻辑下沉至子组件
        return index
      }
      const orderIndex = getIndex(props.orderKey)
      if (!orderIndex) {
        return null
      }
      return () => h(
        WrappedComponent,
        {
          ...attrs,
          style: {
            ...attrs.style,
            order: getOrderIndex(props.orderKey)
          },
          ref: attrs.ref
        }
      )
    }
  })
}

父组件使用:

js 复制代码
<template>
  <div style="display: flex; flex-direction: column;">
    <ModuleAWithOrder orderKey="A" />
    <ModuleBWithOrder orderKey="B" />
    <ModuleCWithOrder orderKey="C" />
  </div>
</template>
<script setup>
import ModuleA from './ModuleA.vue'
import ModuleB from './ModuleB.vue'
import ModuleC from './ModuleC.vue'
import { withOrder } from './hoc'
const ModuleAWithOrder = withOrder(ModuleA)
const ModuleBWithOrder = withOrder(ModuleB)
const ModuleCWithOrder = withOrder(ModuleC)
</script>

结语

这样,我们就可以通过HOC组件,动态的控制组件的渲染顺序。该方案几乎仅涉及CSS改动,而不需要在父组件中添加过多的JS逻辑,同时也符合DRY原则。

参考文献

相关推荐
机器视觉知识推荐、就业指导几秒前
QML 批量创建模块 【Repeater】 组件详解
前端·c++·qml
lmryBC496 分钟前
golang接口-interface
java·前端·golang
uhakadotcom10 分钟前
OpenTelemetry入门:让你的应用程序更透明
后端·面试·github
慕斯策划一场流浪12 分钟前
fastGPT—nextjs—mongoose—团队管理之团队列表api接口实现
开发语言·前端·javascript·fastgpt env文件配置·fastgpt团队列表接口实现·fastgpt团队切换api·fastgpt团队切换逻辑
LaoZhangAI34 分钟前
【2025最新】Claude免费API完全指南:无需信用卡,中国用户也能用
前端
cypking40 分钟前
解决 axios get请求瞎转义问题
vue.js
向阳2561 小时前
SpringBoot+vue前后端分离整合sa-token(无cookie登录态 & 详细的登录流程)
java·vue.js·spring boot·后端·sa-token·springboot·登录流程
hepherd1 小时前
Flask学习笔记 - 模板渲染
前端·flask
LaoZhangAI1 小时前
【2025最新】Manus邀请码免费获取完全指南:5种稳定渠道+3个隐藏方法
前端
经常见1 小时前
浅拷贝与深拷贝
前端