preface

During vUE project development, we often need to reference child components, including third-party UI components (Element, iView), but after adding scoped to the parent component, we cannot style the child in the parent component.

Vue – the role of the loader

To solve the problem of writing subgroup styles in the parent component, it is necessary to understand a before-and-after comparison of the.vue component.

In a vue project, the entire project is organized by a.vue single file component, a component is a fully functional structural unit that contains template (HTML) + style (CSS) + logic (JS) separated from the UI. It is because of vue-loader that we can encode in the form of componentization. Here is a simple vUE component example:

<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello world! '}}}</script>

<style>
.example {
  color: red;
}
</style>
Copy the code

When compiled by vue-loader, it looks like this in the browser:

<head>
	<style type="text/css">
		.example {
		  color: red;
		}
	</style>
</head>

<body>
  <div class="example">Hello world!</div>
  <script type="text/javascript" src="/js/app.js"></script>
</body>
Copy the code

Style, script, and template in the.vue component are compiled into the corresponding style tag, HTML tag, and script tag.

Cause analysis of style failure

We already know the basic functions of vue-loader. Next, let’s look at a common parent-child component example to illustrate why styles fail.

Project Structure:

App.vue
ParentA.vue
ParentB.vue
Child.vue
Copy the code

In this article we focus on the HTML and style sections of the.vue component.

App.vue

<template>
  <div class="App">
    {{ msg }}
    <ParentA></ParentA>
    <ParentB></ParentB>
  </div>
</template>

<style>
.App {
  color: # 000;
}
</style>
Copy the code

ParentA.vue

<template>
  <div class="ParentA">
    {{ msg }}
  </div>
</template>

<style>
.ParentA {
  color: red;
}
</style>

Copy the code

ParentB.vue

<template>
  <div class="ParentA">{{ msg }}</div>
</template>
<style>
.ParentB {
  color: blue;
}
</style>
Copy the code

Before adding scoped

Before the scoped attribute is added to the style tag, the.vue component is compiled as follows:

<head>
  <style type="text/css">
	.ParentA {
	  color: red;
	}
  </style>
</head>

<body>
  <div class="ParentA"> ParentA </div>
  <div class="ParentA">ParentB</div>
</body>

Copy the code

The parentb. vue component pair uses the ParentA class name. Style changes to the ParentA class pollute the global style, that is, affect the parentb.vue component style, which greatly reduces the maintainability of the project.

In a large project, the development of different people can lead to the use of the same class name in two different components, which can affect each other. Fortunately, vue-loader provides us with the scoped attribute, which resolves this problem coding.

After adding scoped

Now let’s add the parenta. vue style tag to the scoped attribute.

<style type="text/css">
  .ParentA[data-v-183fa219] {
	color: red;
  }
</style>
<div data-v-183fa219  class="ParentA"> ParentA</div>
<div class="ParentA">ParentB</div>
Copy the code

You can see that parenta. vue has a property selector in the style tag, and the corresponding DATA-V-183FA219 property value in the HTML tag. This way, no matter how the.parenta style changes, it will not affect the style of ParentB, even if they use the same class name. This is known as the scope constraint.

In VUE, the components of a UI interface are nested within the components of the VUE. Next we want to reference a child component in ParentA. To avoid contaminating the global style, we also add the scoped attribute to its style tag.

Child.vue

<template>
  <div class="Child">
    {{ msg }}
  </div>
</template>

<style scoped>
.Child {
  color: green;
}
</style>
Copy the code

Then change the style of child. vue in parenta. vue (PS: in introducing third party components such as iView, we also often overwrite its component style to meet UI requirements)

<template>
  <div class="ParentA">
    {{ msg }}
    <Child></Child>
  </div>
</template>

<style lang="scss" scoped>.ParentA { color: red; // Override the text color of the Child component.Child {color: pink; }}</style>
Copy the code

Parenta. vue Output after compilation:

<style type="text/css">
  .Child[data-v-0fcd625e] {
	color: green;
  }
</style>
<style type="text/css">
  .ParentA[data-v-183fa219] {
    color: red;
  }
  .ParentA .Child[data-v-183fa219] {
	color: pink;
  }
</style>

<div data-v-183fa219 class="ParentA"> 
	ParentA 
	<div data-v-0fcd625e data-v-183fa219 class="Child">Child </div>
</div>
Copy the code

Here are some things to note: As you can see, the tag generated by child. vue has its own attribute ID value data-V-0fCD625e, and also contains the parent-vue attribute ID value data-V-183fa219. Therefore, it is also valid to style child. vue in the parenta. vue component.

When does ** become invalid? ** We continue to add a Child element to child.vue.

<template>
  <div class="Child">
    {{ msg }}
    <div class="Child-content">The Child content</div>
  </div>
</template>
Copy the code

Suppose at this point we also want to change the style of child-content via parenta. vue: parenta.vue

// ParentA.vue
<style lang="scss" scoped>.ParentA { color: red; .Child { color: pink; &-content { background: green; }}}</style>
Copy the code

At this point, you’ll notice that it doesn’t work. Let’s look at the compiled output to see why:


<style type="text/css">.ParentA[data-v-183fa219] { color: red; } .ParentA .Child[data-v-183fa219] { color: pink; } // Child-content .ParentA .Child-content[data-v-183fa219] { background: green; }</style>
<body>
	<div data-v-183fa219  class="ParentA"> 
	ParentA 
	<div data-v-0fcd625e data-v-183fa219  class="Child"> 
		Child 
		<div data-v-0fcd625e  class="Child-content">The Child content</div>
	</div>
</div>
</body>

Copy the code

You can see that the parent component’s scoped attribute ID value is not assigned to the child-content element of the Child component, so styling these Child components in the parent component does not take effect.

.ParentA .Child-content[data-v-183fa219] {
	  background: green;
}
Copy the code

Doesn’t correspond to child-content.

<div data-v-0fcd625e  class="Child-content">The Child content</div>
Copy the code

The solution

A new solution for vue-loader (PS: this is the official issue discussion address Support /deep/ selector) is to add a deep selector to handle the element styles of child components with scoped Settings.

Parenta.vue: parenta.vue: parenta.vue: parenta.vue: parenta.vue: parenta.vue

<style lang="scss" scoped>.ParentA { color: red; /deep/ .Child { color: pink; &-content { background: green; }}}</style>
Copy the code

Then look at the compiled output of the style tag with deep added:

<style type="text/css">
.ParentA[data-v-183fa219] {
  color: red;
}
.ParentA[data-v-183fa219] .Child {
    color: pink;
}
.ParentA[data-v-183fa219] .Child-content {
      background: green;
}
</style>
Copy the code

ParentA[data-V-183fa219].Child-content

.ParentA .Child-content[data-v-183fa219] {
	  background: green;
}
Copy the code

Any child element covered by ParentA[data-V-183FA219] can be styled in the parent component.

summary

In this paper, the problems and solutions brought by Vue Scoped are introduced step by step through comparison. Most of the time, we can quickly figure out the answer to a question using Google, and asking why can lead us to draw a similar conclusion.

Vue-loader also provides :: V-deep and >>>, which have the same effect as deep. Note that preprocessors like Sass do not parse >>>.

The code covered in this article is here, first published on my website scoped Styling Problems in Vue Development

(End of article)

Further reading

  • Vue-loader Github Repository – Anyone interested can go to Vue-loader to explore the implementation of scoped source code.