Skip to content

示例:同名类的 scoped 隔离 ​

这个案例来自 docs/demo:一个 Demo 组件里渲染了四张类名完全相同 (class="card")的卡片,分别来自四种样式来源:

卡片写法样式来源作用域
1结构直接写在父组件里demo.scoped.scss父组件 hash
2子组件 ChildCardchild-card.scoped.css子组件自身 hash
3子组件 InheritCard无自带样式,逐层绑定父 scopedId继承父组件 hash
4子组件 GlobalCard父组件引入的 global.css全局(选择器不改写)

style scoped 的含义:当前文件里类名的样式只属于当前文件,不污染全局。 类名相同没关系 —— 每条规则都会被改写成带本文件 hash 的选择器 (.card[data-v-{hash}]),DOM 上也只带属于自己文件的 data-v-{hash},两者 一一对应、互不干扰。若隔离不生效,四张同名卡会互相覆盖、渲染错乱;隔离生效 时,谁命中谁不命中,一眼就能看出来。

下方是真实渲染:页面 <script> 里 import Demo from './demo/demo.tsx',直接 把 Demo 当作页面组件渲染出来。

同名 .card:scoped / 继承 / 全局

1. 父组件内直写 card

结构写在 demo.tsx 中,DOM 带父组件 hash;样式来自父的 demo.scoped.scss 里同名 .card,只作用于本文件。

2. ChildCard 自带 scoped

同名 .card,样式来自本组件的 child-card.scoped.css (.card[data-v-子hash]),父的 .card 规则命中不了这里。

3. InheritCard 全元素绑定父 scopedId

每个标签都带上父级 data-v,父的 .card__name / .card__desc 等内层规则也能命中。

4. GlobalCard(global.css · 无 scoped)

global.css 不带 scoped 后缀:选择器不改写、全站生效 —— 别处出现同名 .card 也会被它影响;上面三张卡因 [data-v-*] 特异性更高而把它覆盖。

完整源码(docs/demo) ​

以下源码按组件从父到子排列,用 vitepress snippet(<<< @/…)直接引入 docs/demo 的真实文件。

Demo(父组件) ​

tsx
import ChildCard from './components/ChildCard'
import GlobalCard from './components/GlobalCard'
import InheritCard from './components/InheritCard'

// 父组件的外部 scoped scss(命名约定 *.scoped.scss)——hash 由 demo.tsx 的绝对路径生成
import './demo.scoped.scss'
// 全局样式(不带 scoped 后缀):由父组件引入,选择器不改写、全站生效。
// 对照看:它其实会命中本页所有 .card,但带 scoped 的卡被各自更高特异性的
// .card[data-v-*] 覆盖;只有不带任何 scoped 身份的 GlobalCard 会显示它。
import './global.css'

export default function Demo() {
  return (
    <section className="demo">
      <h2 className="demo__title">同名 .card:scoped / 继承 / 全局</h2>

      <div className="demo__row">
        {/* 卡片 1:结构直接写在父组件里 —— 命中父的 .card[data-v-父hash] */}
        <div className="card">
          <p className="card__name">1. 父组件内直写 card</p>
          <p className="card__desc">
            结构写在 demo.tsx 中,DOM 带父组件 hash;样式来自父的 demo.scoped.scss
            里同名 .card,只作用于本文件。
          </p>
        </div>

        {/* 卡片 2/3/4:子组件。父组件会在大写组件标签上注入 scopedId="data-v-<父hash>",
            是否使用(绑定到自己的 DOM)由子组件决定 */}
        <ChildCard />
        <InheritCard />
        <GlobalCard />
      </div>
    </section>
  )
}
scss
$card-radius: 12px;
$brand: #4f46e5;

.demo {
  max-width: 760px;
  padding: 24px;
  border: 1px solid rgba($brand, 0.25);
  border-radius: 16px;
  background: #ffffff;

  &__title {
    margin: 0 0 16px;
    color: $brand;
    font-size: 18px;
    font-weight: 800;
  }

  &__row {
    display: flex;
    gap: 14px;
    flex-wrap: wrap;
    margin-top: 16px;
  }
}

// 父组件自己的 .card:编译后为 .card[data-v-父hash],
// 只命中带父 hash 的 DOM(卡片 1 与手动绑定了父 scopedId 的卡片 3)。
.card {
  width: 210px;
  padding: 14px;
  border-radius: $card-radius;
  border: 2px solid rgba($brand, 0.4);
  background: #eef2ff;

  &__name {
    margin: 0 0 6px;
    color: $brand;
    font-weight: 700;
  }

  &__desc {
    margin: 0;
    color: #475569;
    font-size: 12px;
    line-height: 1.7;
  }
}
css
/* global.css —— 不带 scoped 后缀的普通全局样式:
   选择器不会被追加 [data-v-*],规则全站生效(同名类都会被命中)。 */
.card {
  width: 210px;
  padding: 14px;
  border-radius: 12px;
  border: 2px dashed rgba(234, 88, 12, 0.5);
  background: #fff7ed;
}

.card__name {
  margin: 0 0 6px;
  color: #ea580c;
  font-weight: 700;
}

.card__desc {
  margin: 0;
  color: #475569;
  font-size: 12px;
  line-height: 1.7;
}

卡片 1 的结构直接写在父组件里:它和页面上的其它 DOM 一样带上父组件 hash, 命中父的 .card[data-v-父hash];父的 scoped 样式到此为止,进不了任何子组件。 父组件还同时 import 了不带 scoped 后缀的 global.css(全局对照,见下方 GlobalCard)。

ChildCard(子组件 · 同名 .card + 自带 scoped) ​

tsx
import './child-card.scoped.css'

export default function ChildCard() {
  return (
    // 类名与父组件相同(.card),但 hash 属于本文件 —— 同名类各自隔离
    <div className="card">
      <p className="card__name">2. ChildCard 自带 scoped</p>
      <p className="card__desc">
        同名 .card,样式来自本组件的 child-card.scoped.css
        (.card[data-v-子hash]),父的 .card 规则命中不了这里。
      </p>
    </div>
  )
}
css
/* 与父组件同名的 .card:作用域属于 ChildCard.tsx(data-v-子hash),
   与父组件 .card[data-v-父hash] 互不干扰 —— 同名类各自隔离 */
.card {
  width: 210px;
  padding: 14px;
  border-radius: 12px;
  border: 2px solid rgba(34, 197, 94, 0.4);
  background: #f0fdf4;
}

.card__name {
  margin: 0 0 6px;
  color: #16a34a;
  font-weight: 700;
}

.card__desc {
  margin: 0;
  color: #475569;
  font-size: 12px;
  line-height: 1.7;
}

与父同名 .card,但样式来自本组件自己的 scoped 文件 (.card[data-v-子hash]):两条规则各自命中自己文件产生的 DOM,同名类互不污染 —— 父的样式命中不了这张卡,这张卡的样式也影响不到父组件。

InheritCard(子组件 · 同名 .card + 手动继承父 scopedId) ​

tsx
interface InheritCardProps {
  /** 组件 scoped:父组件自动注入 scopedId="data-v-<父hash>" */
  scopedId?: string
}

export default function InheritCard({ scopedId }: InheritCardProps) {
  // 需要被父 scoped 样式命中的每个标签都带上父级 scopedId(逐层绑定原因见
  // 示例页 InheritCard 小节里的 ::: tip 警示)
  const inherit = scopedId ? { [scopedId]: '' } : {}

  return (
    <div className="card" {...inherit}>
      <p className="card__name" {...inherit}>3. InheritCard 全元素绑定父 scopedId</p>
      <p className="card__desc" {...inherit}>
        每个标签都带上父级 data-v,父的 .card__name / .card__desc 等内层规则也能命中。
      </p>
    </div>
  )
}

与父同名 .card,且没有自己的 scoped 文件;想让父的 scoped 样式命中自己, 就把父组件注入的 scopedId 绑到自己的 DOM 上(继承父组件的外观)。

警示:继承需要逐层绑定

组件 scoped 下,父样式会编译成带 [data-v-父hash] 的选择器。只把 scopedId 绑到根元素时,只有根元素继承父样式:父样式里形如 .card__name / .card__desc 的内层选择器(编译后 .card__name[data-v-父])命中不了不带该 属性的子元素。因此要把 scopedId 展开到每一个希望被父样式命中的标签上 (结构有多少层,就需要继承多少层)。

GlobalCard(父组件中渲染 · 同名 .card · 只吃全局样式) ​

tsx
export default function GlobalCard() {
  return (
    // 与其它卡片同名的 .card,但没有任何 scoped 规则命中它:
    //  - 没有自己的 *.scoped.* 文件(本组件的 hash 上没有对应规则);
    //  - 也没有把父组件注入的 scopedId 绑到 DOM 上(不继承父样式)。
    // 于是能命中它的只剩父组件引入的全局 global.css(选择器不带 [data-v-*])。
    <div className="card">
      <p className="card__name">4. GlobalCard(global.css · 无 scoped)</p>
      <p className="card__desc">
        global.css 不带 scoped 后缀:选择器不改写、全站生效 —— 别处出现同名 .card
        也会被它影响;上面三张卡因 [data-v-*] 特异性更高而把它覆盖。
      </p>
    </div>
  )
}

第四张卡同样由父组件渲染、类名依然是 .card,但它既没有自己的 scoped 文件, 也没有绑定父 scopedId —— 能命中它的只剩父组件引入的global.css: 不带 scoped 后缀的选择器不会被改写,全站生效。对照即见差异:这份全局规则 其实也命中了前三张卡,但它们各自带更高特异性的 [data-v-*] 规则( .card[data-v-父] / .card[data-v-子]),把全局样式覆盖掉了;只有 GlobalCard 没有任何 scoped 身份,全局样式才最终可见。站点别处若出现同名 .card,同样会 被 global.css 影响 —— 这正是 scoped 要防的“污染”。