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

参考文献

相关推荐
闲猫2 小时前
Spring AI 对接Deepseek ChatModel 聊天对话
java·前端·spring
paopaokaka_luck3 小时前
英语单词学习系统的设计与实现(基于艾宾浩斯遗忘曲线的智能复习机制、语音朗读、多题型测试与错题自动收录、Echarts图形化分析)
vue.js·spring boot·后端·echarts
自信的未来3 小时前
JSON 工具|Web Worker 工程化打包 + 语法自动修复 + 多语言代码生成实战
java·前端·json
我叫黑大帅3 小时前
MySQL 并发插入竞态问题:原子写入实践指南
后端·mysql·面试
全栈前端老曹4 小时前
【MongoDB】安全与权限管理 —— 用户认证、角色权限、SSL 加密
前端·javascript·数据库·安全·mongodb·nosql·ssl
zhangxingchao5 小时前
AI大模型核心八:从 Agent Skill、长文档 RAG 到知识库更新与训练策略
前端·人工智能·后端
我叫黑大帅5 小时前
别再手动写 updated_at了,这坑我踩过
后端·面试·go
ShineWinsu5 小时前
对于Linux:网络基础的解析
linux·网络·面试·udp·笔试·ip·tcp
zhangxingchao5 小时前
AI大模型核心七:从 Workflow、RAG、记忆治理到幂等性
前端·人工智能·后端
gyx_这个杀手不太冷静6 小时前
Agent开发进阶指南(第 1 章):AI Agent 到底是什么?
前端·agent·ai编程