最近在维护个人博客项目时,遇到一个非常典型的场景:
我的“个人主页”和“博客详情页”左侧都有一个长得一模一样的“作者信息面板”(包含头像、关注/粉丝数、私信按钮等)。
最开始,这些代码全部揉在具体的页面文件里。随着功能增加,代码越来越臃肿。于是我决定进行一波重构,把左侧面板抽离成一个独立的 UserInfo.vue 组件。
在这个过程中,我不仅体会到了组件高内聚的爽快感,还和 CSS 布局对齐的“幽灵 Bug”狠狠切磋了一把。
今天把过程记录下来,希望能帮到遇到同样问题的小伙伴。
进阶思考:到底什么是真正的“高内聚”?
刚开始抽离 UserInfo.vue 时,遇到一个关于“私信”功能的问题。点击私信按钮,需要弹出一个 <Chat> 聊天窗口。
最初的习惯性思维是:在子组件 UserInfo.vue 里用 emit 抛出一个事件,让外部的父页面去监听这个事件,然后控制 <Chat> 组件的显示和隐藏。
但这带来了一个问题:不管我在哪里复用 UserInfo 组件,父页面都必须额外引入一次 <Chat> 组件,并写一堆控制状态的代码。这太累赘了!
最终破局点:直接把 <Chat> 组件塞进 UserInfo.vue 里!
既然“私信”是用户信息的强相关功能,那就让 UserInfo 自己消化掉。
- 在 UserInfo.vue 内部引入 Chat.vue。
- 在内部维护 chatVisible 状态。
- 在内部处理“不能和自己聊天”等校验逻辑。
这样一改,UserInfo.vue 就变成了一个真正的即插即用组件。父页面只需要 <UserInfo :user="authorInfo" /> 一行代码,所有用户信息展示、关注、点赞、私信聊天功能全包了,外部代码变得异常清爽!
踩坑实录:复用组件时的“对齐幽灵”
组件抽离得很完美,但在实际复用到“个人主页”和“博客详情页”时,UI 彻底乱套了:
- 博客详情页: 左侧信息栏和右侧文章完全对不齐,左侧莫名其妙多出一截空白。
- 个人主页: 修复了详情页后,主页的左侧又和中间的工具栏对不齐了。
排查了半天,发现罪魁祸首是我在抽离组件时,把特定的布局占位(Spacer)和悬浮(Sticky)样式一起写进了子组件里。
原先的页面为了配合顶部的工具栏,写了一个 <div class="align-spacer"></div>(高度 36px)来强行占位,并且用了 position: sticky; top: 20px;。当把这些带着强烈“上下文环境”的代码封装进通用组件后,它到了不需要工具栏的详情页,自然就往下掉了一大截。
破局之道:把布局控制权交还给父组件
经过这次踩坑,我总结出一个复用组件的铁律:
通用组件内部只管业务逻辑和自身 UI,绝对不要写死对外的 margin、定位(absolute/fixed/sticky)或空 div 占位!
最终的解决方案如下:
1. 净化子组件 (UserInfo.vue):
删掉内部所有用于顶部占位的 <div class="align-spacer"></div>,删掉 sticky 相关的 CSS。让它变成一个高度完全由内容撑开的纯粹组件。
2. 详情页的极致对齐:
在博客详情页的 Flex 容器上,加上 align-items: flex-start;。
防止子元素在纵向上被默认拉伸,确保左右两栏像刀切一样在顶部完美平齐。
css
/* 博客详情页的父容器 */
.main-body {
display: flex;
align-items: flex-start; /* 核心:确保顶部对齐 */
gap: 24px;
}3. 个人主页的精准下推:
在个人主页中,给 UserInfo 组件套一层专门的 Wrapper,由这个 Wrapper 来负责该页面的特有布局(吸顶和下推)。
html
<div class="person-left-wrapper">
<UserInfo :user="user" />
</div>css
/* 个人主页专属 Wrapper */
.person-left-wrapper {
position: sticky;
top: 20px;
width: 280px;
flex-shrink: 0;
margin-top: 36px; /* 精确下推 36px,与中间工具栏完美对齐 */
}总结
这次重构虽然踩了几个坑,但收获颇丰:
- 业务逻辑: 能让子组件内部闭环的逻辑(比如包含弹窗、聊天),就不要抛给父组件,这才是真正的解耦与高内聚。
- CSS 布局: 抽离公共组件时,一定要“剥离环境”。定位、外边距占位是父级容器该操心的事,子组件只负责把自己画好就行。
敲代码就是一个不断折腾、推翻自己再重构的过程。搞定了这波重构,看着清爽的代码和对齐完美的 UI,强迫症表示非常满足!