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

参考文献

相关推荐
大时光4 分钟前
gsap -滚动插件 ScrollTrigger 简单demo
前端
tangbin58308523 分钟前
iOS Swift:蓝牙 BLE 连接外设CoreBluetooth
前端
WWWWW先生25 分钟前
02 登录功能实现
前端·javascript
嚴寒26 分钟前
我用 AI 画了个设计稿,然后让它自己写成了代码
前端·ai编程
踏浪无痕30 分钟前
MCP 是什么?用大白话讲清楚
面试
彭锐34336 分钟前
哨兵节点实现的自驱式任务队列
前端
阿星AI工作室39 分钟前
我做了个飞书转公众号排版器,6套高颜值主题想换就换
前端·人工智能
UrbanJazzerati1 小时前
PostgreSQL 完全实战指南:从小白到高手 DDL篇
后端·面试
_Eleven1 小时前
继TailWindCss和UnoCss后的CSS-in-JS vs Utility-First 深度对比
前端
UrbanJazzerati1 小时前
Python实现Salesforce Bulk API 2.0批量数据导入:从Excel到云端的高效方案
后端·面试