【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原则。

参考文献

相关推荐
宁酱醇41 分钟前
CSS基础_@拉钩教育【笔记】
前端·css
建群新人小猿42 分钟前
CRMEB-PRO系统定时任务扩展开发指南
android·java·开发语言·前端
牧天白衣.43 分钟前
vue 和 html 的区别
前端
麦麦大数据1 小时前
vue+django农产品价格预测和推荐可视化系统[带知识图谱]
vue.js·python·django·知识图谱·推荐算法·价格预测·农业大数据
知识分享小能手1 小时前
JavaScript学习教程,从入门到精通,Ajax数据交换格式与跨域处理(26)
xml·开发语言·前端·javascript·学习·ajax·css3
好名字08211 小时前
el-tabs与table样式冲突导致高度失效问题解决(vue2+elementui)
前端·vue.js·elementui
qq_278063711 小时前
vue elementui 去掉默认填充 密码input导致的默认填充
前端·vue.js·elementui
黄同学real1 小时前
HTML5 新增的主要标签整理
前端·html·html5
liwulin05061 小时前
【JAVAFX】实现屏幕指定区域截图,带尺寸显示
服务器·前端·python
是麟渊1 小时前
面试回答之STAR结构
面试·职场和发展