vue组件中的样式隔离方式与原理解析

scoped实现样式隔离与穿透

scoped在vue单文件组件中用来标记style标签,让该标签内的css选择器只能选中组件内的元素。就是说让组件内的样式与其它组件隔离,不会影响父组件、子组件的样式。

scoped的实现原理

vue模板编译器在编译有scoped的stye标签时,会生成对应的postCSS插件,该插件会给每个scoped标记的style标签模块,生成唯一一个对应的data-v-xxxhash字符串,同时:

  • 该字符串会作为属性,添加到该组件内,每一个元素上。

    如果存在子组件,则只会在子组件顶级根元素添加该属性(透传),无法给子组件内部元素添加。

  • scoped标记的style标签内,每个选择器都会联合该属性选择器(所有选择器都会默认被重写)。

    如果是嵌套选择器,会在最内层的选择器联合上属性选择器。

css 复制代码
.ipt {}
.ipt .el-input__wrapper {}
.ipt .el-input__wrapper span {}
/*结果,每个scoped对应的唯一字符串,会联合到最内层的选择器*/
.ipt[data-v-88888888] {}
.ipt .el-input__wrapper[data-v-88888888] {}
.ipt .el-input__wrapper span[data-v-88888888] {}

样式隔离效果:

有scoped的style标签内的选择器,只能选中本组件元素,无法选中子组件的元素。即,在父组件使用子组件的选择器修改样式,是无法选中子组件内的元素的。

  • 生成联合属性选择器时,默认只给最内层选择器联合。限定了每个(嵌套)选择器选中的元素,必须是带有唯一属性标记的元素。而子组件内的元素是不会带上该属性标记的。

scoped中的样式穿透原理

样式穿透意思是:让父组件在有scoped的标签里,使用子元素的选择器来修改子元素的样式,即父组件定义的样式穿透到子组件(默认无法穿透)。

vue3中只能使用:deep()来实现样式穿透,而>>> /deep/已经被废弃。

原理:

  • 生成联合选择器时,不再默认给最内层的选择器联合,而是手动指定某个选择器的上一级选择器进行联合。

    vue编译分为三部分:script、template、style,scoped处理就在style编译里处理。

css 复制代码
/* 指定给.el-input__wrapper前面的ipt联合属性选择器 */
.union .ipt :deep(.el-input__wrapper) input {}
/* 结果: */
.union .ipt[data-v-0904fc8e] .el-input__wrapper input {}

CSS Modules实现样式隔离

css Modules的作用和vue中的scoped作用一样,都是用来实现样式的隔离。在webpack的项目中是css-loader提供的能力。在vite中已经适配了vue3单文件组件。

实现原理

将导入的css模块里的自定义css类名,替换为一个无语义,但唯一的字符串类名,并提供一个对象充当map映射来访问转换后的类名。

我们先来介绍wepack中的实现,来理解它的执行过程。

webpack中的CSS Modules

在webpack中通过配置css-loader,即可实现CSS Modules。可以应用在react或原生等场景中。但它的应用场景更多是让两个css模块(即使有相同的类名)能够互相隔离。

原理:

  • 转换:导入一个css模块时,将所有的css类的类名都替换成一个唯一的字符串。说白了就是,将自定义css类名都替换成无语义,但唯一的类名。
  • 访问转换映射:导入该css模块时,可以拿到这个模块里自定义类名和对应被替换后的类名的映射(一个对象),访问它身上的未转换前的css类名,就可以拿到转换后的类名。然后将转换后的类添加到对应的元素上的classList即可。
  • 插入到html文档中的css是转换类名后的css。

效果:

  • 当导入两个css文件,即使有相同的类名,也不会相互影响。但是需要根据拿到的映射对象,给元素加上被替换后的唯一类名。

  • 为了避免所有css文件都会进行转换类名操作(有些全局css模块不需要转换),可以给不需要转换的css文件起一个特殊的文件名,并通过test正则,为它们匹配一套不会转换的css-loader配置。

实现:

  1. 配置css-loader,处理css文件(模块)时,进行类名替换

    js 复制代码
    module.exports = {
        module: {
            rules: [
                {
                    test:/\.css$/,
                    exclude: /node_modules/, // 排除依赖里的代码
                    use: [
                        'style-loader', // 替换类名后,把css插入html头部
                        {
                            loader: 'css-loader',
                            options: {
                                modules: true, // 开启css模块
                            }
                        }
                    ]
                }
            ]
        }
    }
  2. 导入css文件(模块)时,用一个变量接收导入结果,这个结果就包含原始类名和被替换后类名的映射。其中key是原始类名,value是被替换后的。

    js 复制代码
    // a.css
    .test .div {
    	color: green;
    }
    
    // b.css
    .test .div {
    	color: yellow;
    }
    
    // index.js
    import style1 from 'a.css'
    import style2 from 'b.css'
    // 把页面上第一个有test类的元素加上a.css里test类对应的样式
    document.queryselector('.test').classList.add(style1.test)

vue3中的CSS Modules

vue3组件中应用CSS Modules是通过在style添加module属性,让该style中的类名都替换成唯一类名,并且类名映射对象只能在组件内部访问。最终的效果就是和scoped一样,实现组件样式与其它组件隔离。

步骤:

  1. 在单位组件的style标签添加module属性,取值为自定义的映射名(说白了就是映射对象的名字)

    html 复制代码
    <style module="cssmodule">
    .red {
      color: red;
    }
    .box .text {
      font-size: large;
    }
    </style>
  2. template中可直接拿到该映射对象。

    • style标签里的每一个css类,都会全部变成映射对象里的属性
    html 复制代码
    <template>
      <div :class="cssmodule.box">
        <span :class="[cssmodule.text, cssmodule.red]">
          hellow
        </span>
      </div>
    </template>
  3. setup标签中通过useCssModulehooks也可以拿到css映射对象

    html 复制代码
    <script setup lang="ts">
    import { useCssModule } from 'vue'
    
    // 具名情况下, 返回 <style module="classes"> 内的 class 映射
    let cssModules = useCssModule('cssmodule');
        
    // 匿名情况下, <style module> 则不需要传参
    // let cssModules = useCssModule();
    console.log(cssModules);
    
    </script>

完整示例:

在vue中,使用scoped会更简单,所以CSS Modules一般用在render函数里。

html 复制代码
<script setup lang="ts">
import { useCssModule } from 'vue'

// 具名情况下, 返回 <style module="cssmodule"> 内的 class 映射对象
let cssModules = useCssModule('cssmodule');
    
// 匿名情况下, 如<style module> ,则useCssModule调用时不需要传参
// let cssModules = useCssModule();
console.log(cssModules);

</script>

<template>
  <!-- 通过style标签的module属性值获取类名 -->
  <!-- 如果<style module>,则通过$style访问: <div :class="$style.box"> -->
  <div :class="cssmodule.box">
    <span :class="[cssmodule.text, cssmodule.red]">
      hellow
    </span>
  </div>
</template>
<!--module属性可以只占位(匿名)如:<style module>  -->
<style module="cssmodule">
.red {
  color: red;
}
.box .text {
  font-size: large;
}
</style>
相关推荐
十一吖i13 分钟前
前端将后端返回的文件下载到本地
vue.js·elementplus
光影少年14 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_16 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891118 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
熊的猫1 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
mosen8682 小时前
Uniapp去除顶部导航栏-小程序、H5、APP适用
vue.js·微信小程序·小程序·uni-app·uniapp
别拿曾经看以后~3 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死3 小时前
导航栏及下拉菜单的实现
前端·css·css3
Gavin_9154 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
Devil枫9 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试