Component style scoping (:deep/:slotted/:global)
Component Style Scoping (:deep/:slotted/:global)
In Vue's Single File Components (SFCs), <style scoped>
is the core mechanism for achieving style isolation. When the scoped
attribute is added to a <style>
tag, Vue adds a unique data-v-xxxxxx
attribute to all DOM elements within the component and achieves style isolation through attribute selectors. However, in real-world development, we often need to bypass this scoping to modify child component styles, which is where special selectors like :deep()
, :slotted()
, and :global()
come into play.
Basic Principles of Scoped Styles
<template>
<div class="demo">
<p>This text will be affected by scoped styles</p>
</div>
</template>
<style scoped>
.demo {
color: red;
}
/* After compilation, it becomes */
.demo[data-v-f3f3eg9] {
color: red;
}
</style>
Scoped styles are implemented through PostCSS transformation, which adds attribute constraints to selectors. This mechanism ensures styles only affect the current component but also introduces three common issues:
- Inability to directly modify child component internal styles
- Inability to modify slotted content styles
- Lack of flexibility when adding global styles
:deep() Deep Selector
:deep()
is used to bypass scoping and modify child component internal styles. In Vue 2, the equivalent syntax was >>>
or /deep/
, while Vue 3 standardizes on :deep()
.
<template>
<ChildComponent class="child-wrapper"/>
</template>
<style scoped>
/* Invalid syntax */
.child-wrapper .inner-element {
color: blue;
}
/* Correct syntax */
:deep(.child-wrapper .inner-element) {
color: blue;
}
/* After compilation, it becomes */
[data-v-f3f3eg9] .child-wrapper .inner-element {
color: blue;
}
Practical use case examples:
<!-- Modifying Element Plus's el-input internal styles -->
<style scoped>
:deep(.el-input__inner) {
background-color: #f5f7fa;
}
</style>
<!-- Modifying third-party component library styles -->
<style scoped>
:deep(.third-party-component .title) {
font-size: 18px;
}
</style>
:slotted() Slot Selector
:slotted()
is used to modify styles for content passed through slots. By default, scoped styles do not affect slotted content.
<template>
<div class="container">
<slot name="header"></slot>
</div>
</template>
<style scoped>
/* Invalid syntax */
.container .header {
color: red;
}
/* Correct syntax */
:slotted(.header) {
color: red;
}
/* After compilation, it becomes */
[data-v-f3f3eg9] .header {
color: red;
}
Complex example:
<template>
<div class="card">
<slot name="title"></slot>
<slot name="content"></slot>
</div>
</template>
<style scoped>
:slotted([slot="title"]) {
font-size: 24px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
:slotted([slot="content"]) {
font-size: 14px;
line-height: 1.6;
}
</style>
:global() Global Selector
:global()
is used to define global styles within scoped styles, typically for overriding third-party styles or defining animations.
<style scoped>
/* Defining global animations */
:global(.fade-enter-active),
:global(.fade-leave-active) {
transition: opacity 0.5s;
}
:global(.fade-enter-from),
:global(.fade-leave-to) {
opacity: 0;
}
/* Modifying body background color */
:global(body) {
background-color: #f8f8f8;
}
</style>
Mixed usage example:
<style scoped>
/* Component-specific styles */
.container {
padding: 20px;
}
/* Global styles */
:global(.ant-btn) {
margin-right: 10px;
}
/* Deep selector */
:deep(.el-dialog__body) {
padding: 20px;
}
/* Slot styles */
:slotted(.item) {
margin-bottom: 10px;
}
</style>
Combination Techniques
In real projects, these selectors are often used in combination:
<template>
<Modal>
<template #header>
<h2 class="modal-title">Title</h2>
</template>
<ChildComponent class="content"/>
</Modal>
</template>
<style scoped>
/* Modifying modal header slotted content */
:slotted(.modal-title) {
color: var(--primary-color);
}
/* Modifying child component internal elements */
:deep(.content .item) {
padding: 15px;
}
/* Global style resets */
:global(.reset-ul) {
margin: 0;
padding: 0;
list-style: none;
}
</style>
Performance Considerations and Best Practices
- Moderate Usage: Overusing
:deep()
can lead to loss of style scoping control; use only when necessary. - Specific Selectors: Use specific selector paths to avoid global impact.
/* Not recommended */ :deep(*) { color: red; } /* Recommended */ :deep(.child .item) { color: red; }
- Combining with CSS Modules: In large projects, consider combining with CSS Modules.
<style module scoped> .container { /* Local styles */ } :global(.ant-btn) { /* Global styles */ } </style>
Common Issues and Solutions
Issue 1: Styles not applying after using :deep()
- Check if the child component correctly renders the DOM structure
- Verify the selector path is correct
- Check for higher-priority style overrides
Issue 2: Slot styles not applying
- Ensure slotted content is actually passed in
- Check if the
:slotted()
selector matches the slotted content's class name - Note the limitations of scoped styles on slotted content
Issue 3: Styles polluting globally
- Avoid using overly generic selectors in
:global()
- Consider using naming conventions like BEM to reduce conflicts
- Add specific prefixes to necessary global styles
<!-- Bad example -->
<style scoped>
:global(.btn) {
/* This affects all .btn elements */
}
</style>
<!-- Good example -->
<style scoped>
:global(.my-component-btn) {
/* Limited scope */
}
</style>
Comparison with Other Technical Solutions
-
CSS Modules:
- Achieves isolation through unique class names
- Requires manually passing class names to child components
- Cannot directly modify child component internal styles
-
CSS-in-JS:
- Complete style isolation
- Higher runtime overhead
- Less integrated with Vue ecosystem compared to scoped styles
-
Naming Conventions like BEM:
- Relies on developer discipline
- Cannot achieve true isolation
- Requires manual namespace management
Configuration in Build Tools
In vite.config.js, you can configure scoped style behavior:
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/styles/variables.scss" as *;`
}
},
modules: {
scopeBehaviour: 'local' // or 'global'
}
}
})
In webpack, configure via vue-loader:
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
css: {
modules: {
localIdentName: '[name]_[local]_[hash:base64:5]'
}
}
}
}
]
}
}
Future Development of Style Scoping
Vue 3.3+ introduces combined usage of scoped
and module
attributes on <style>
tags:
<style scoped module>
/* Combines scoping with CSS Modules syntax */
</style>
Composition API's useCssModule:
import { useCssModule } from 'vue'
export default {
setup() {
const style = useCssModule()
return { style }
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn