递归组件里面的数据状态不共享
递归组件里面的数据状态不共享,所以需要用 pinia 之类的东西去共享数据。
菜鸟这里想了一下,要是状态共享就完了,就真的牵一发动全身了,很难排查问题。
<script setup>
import { useProjectCenterActiveObjStore } from "@/store/modules/projectCenterActiveObj";
defineProps({
asideData: {
type: Array,
default: () => []
},
layers: {
type: Number,
default: 0
}
});
const useActiveObj = useProjectCenterActiveObjStore();
const setActiveObj = (item) => {
useActiveObj.setActiveObj(item);
};
// 设置激活的button为普通样式,非激活的就是plain样式
const getPlain = (id) => {
if (useActiveObj.activeObj.id === id) {
return false;
}
return true;
};
const getClass = (layers) => {
if (layers === 0) {
return `ml-[${layers * 20}px] font-bold w-full`;
}
return `ml-[${layers * 20}px] w-[120px]`;
};
</script>
<template>
<div>
<div v-for="item in asideData" :key="item.id">
<template v-if="item.children">
<el-tag type="info" size="small" :class="getClass(layers)">{{ item.title }} </el-tag>
<aside-view :aside-data="item.children" :layers="layers + 1"></aside-view>
</template>
<el-button
v-else
type="primary"
size="small"
:plain="getPlain(item.id)"
:data-id="item.id"
:class="`ml-[${layers * 20}px] w-[120px]`"
@click="setActiveObj(item)"
>{{ item.title }}</el-button
>
</div>
</div>
</template>
<style lang="scss" scoped>
:deep(.el-tag--small) {
height: 24px;
}
</style>
v-model 和 update事件 的书写顺序可能会影响结果
菜鸟在修改项目代码时发现,如果按照红色的写,虽然确实会触发更新函数 excute,但是请求后端使用的数据还是老的;如果用绿色的,则请求的数据是新的,这也是组件使用的一个坑!
这里和上篇文章写的:element plus 使用细节 (二) 中的 change事件里面不要访问v-model的数据 还有点差别。
这里的是vue自带的更新方法,上篇文章是element自己封装的change方法,所以上篇文章就算书写顺序是绿色的,也不行!
组件设计
这些都是菜鸟最近在工作中,深有体会的一些设计规范,可以减少后期返工,不能因为工期就不考虑设计!!!
我们公司另一位大佬说了很好的一句话:
封装不应该封装 UI功能(不仅包括样式和组件,还包括“渲染结构的描述方式”,如 template / JSX / JS 配置,这些都属于 UI 层,不应该被当作业务封装),因为组件库已经做得很好了,需要封装的是业务重复的功能,以及UI组件没有做的功能!
组件设计 - 表格列
表格最好通过 JS 配置进行统一管理,而不是在 UI 中写死。每一列应具备独立的配置(如字段、顺序、显隐等)。
原因是:写死在模板中的表格结构,本质是“不可变结构”,一旦涉及列顺序调整、权限控制、个性化配置,就会导致大量重复修改甚至重构。
这样设计的好处是:
- 可以灵活调整列顺序,而不需要修改模板结构
- 支持用户个性化配置(如拖拽排序、显示/隐藏列)
- 降低后续维护成本,避免频繁改动 UI 代码
- 更容易做持久化(如本地存储或服务端保存用户配置)
本质上是将“表格结构”从“视图层”抽离为“数据配置驱动”,提高扩展性和可维护性。
但是没有设计是完全灵活的,一个组件只要被大量引用,“牵一发动全身”是不可避免的,这是复用带来的天然代价。
设计只能减少负担,并不能解决,除非你一个界面就写死一个,不进行复用,但是这样又会导致要修改的时候需要同时修改N个文件!
注意
菜鸟感觉这个是提取组件提取早了,项目未真正成型前,就应该 “一个界面就写死一个”,等后面基本不会改变后再来抽取,过早的抽取反而适得其反!
这里是菜鸟推荐的思路
- 前期直接 “一个界面就写死一个” js 文件渲染 table
- 后期基本不用改动的时候,提取公用列的 js
- 如果后期之后还有修改,就按照如下封装一个函数
公用列,提取成js
export function createColumns(config = {}) {
const {
override = {}, // 替换
order = [], // 排序
exclude = [], // 删除
append = [] // 新增
} = config
let cols = [
……公用列
]
// 1. 删除
cols = cols.filter(col => !exclude.includes(col.key))
// 2. 覆盖
cols = cols.map(col => ({
...col,
...(override[col.key] || {})
}))
// 3. 添加新列
cols = [...cols, ...append]
// 4. 排序
if (order.length) {
const orderMap = new Map(order.map((k, i) => [k, i]))
cols.sort((a, b) => {
const aIndex = orderMap.has(a.key) ? orderMap.get(a.key) : Infinity
const bIndex = orderMap.has(b.key) ? orderMap.get(b.key) : Infinity
return aIndex - bIndex
})
}
return cols
}
注意
其实JS也有不好的地方,就是不够直观,特别是很多提取出去之后,再进行组合,那就真的没法看!
所以最好的就是自定义列的顺序,一开始就后端一起做,要么就按照下面的进行封装!