Vue 中使用 BEM:构建可扩展组件的样式结构
BEM 在 Vue 项目中依然大放异彩,得益于其严格的类名层级,能有效避免组件化架构中的样式冲突。Vue 负责响应式和逻辑处理,而 BEM 确保块(Block)和元素(Element)保持独立。这种组合能有效防止应用规模扩大时出现 CSS 杂乱无章的情况。
BEM 基于三大核心原则:
- 块(Block):独立实体,如
.header或.card,无外部依赖。 - 元素(Element):块的子部分,用双下划线表示 —
.button__icon。 - 修饰符(Modifier):变体或状态,用单下划线表示 —
.button_disabled、.card_theme_dark。
这种结构隔离了样式,让调试和重构变得轻松自如。
Vue 组件中的样式编写
在 Vue 中,样式放在 <style scoped> 内,限制作用域仅限于当前组件。Scoped 能防止样式泄漏,但要小心深层嵌套。
仅在集成外部库且 scoped 不够用时,才使用 :deep()。别用它来绕过自己的架构设计 — 那往往是结构问题的信号。
BEM 与 scoped 完美搭配,通过逻辑分组类名。块定义基础样式,修饰符处理变体,元素管理内部结构。
实战示例:带状态的按钮
没有体系,类名会乱七八糟地堆积。这是典型问题:
<template>
<button :class="{
'button-basic': isBasic,
'button-href': isHref,
'button-avatar': isAvatar
}">
{{ text }}
</button>
</template>
问题:
- 无层级:类名未绑定到块。
- 扩展性差:新状态意味着更多条件。
- 可读性低:逻辑随时间变得晦涩。
使用 BEM,模板简化且自文档化:
<template>
<button :class="[
'button',
{
'button_disabled': disabled,
'button_loading': loading,
},
{
'button_size_xl': size === 'xl',
'button_size_l': size === 'l',
},
{
'button_primary': color === 'primary',
'button_secondary': color === 'secondary',
}
]">
{{ text }}
</button>
</template>
类名按语义分组:基础块、状态、大小、主题。在 CSS 中:
.button {
/* 基础样式 */
padding: 12px 24px;
border: none;
border-radius: 4px;
}
.button_disabled {
opacity: 0.5;
cursor: not-allowed;
}
.button_loading {
position: relative;
}
.button_primary:not(.button_disabled):hover {
background-color: #0055ff;
}
:not() 选择器能过滤冲突状态,无需额外类名。
扩展与团队协作
在大项目中,BEM 降低认知负担:开发者一看模板,就能明白块及其修饰符,无需翻 CSS 文件。
团队收益:
- 可预测性:类名直观反映用途。
- 隔离性:块间互不干扰。
- 可扩展性:新增修饰符无需重构。
复杂组件示例 — 带主题和尺寸的产品卡片:
<template>
<div class="card" :class="[
`card_size_${size}`,
`card_theme_${theme}`
]">
<div class="card__image"></div>
<div class="card__title">{{ title }}</div>
<button class="card__button button">购买</button>
</div>
</template>
块内元素继承上下文但保持独立。
何时跳过 BEM
原型或落地页用 BEM 有点小题大做:Tailwind 或工具类更快。长期项目、团队协作和复杂 UI 才适合 BEM。
核心要点
- BEM 以严格层级补充 Vue,与 scoped 样式零冲突。
- 修饰符归类状态和变体,简化模板。
:deep()仅限外部库,别用于内部 hack。- 大项目中,BEM 加速新人上手和重构。
- 试试真实组件:可读性立竿见影。
— Editorial Team
暂无评论。