最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【vue】单文件sfc里的scoped是什么

    正文概述 掘金(丢了帽子的路飞)   2021-04-05   805

    1、样式污染

    考虑这样的一个场景。有组件A组件B的style中定义了同样的类.h1, 那么打包之后的css文件中就会有两个同样的.h1类, 当页面展示组件A或者组件B的时候样式都同时受到了定义的两个.h1的影响,造成样式错乱。为了更好说明,我写了A和B两个组件,代码如下:

    1. 组件A
    <template>
      <div class="compA">
        <h1 class="h1">A Hello</h1>
        <B></B>
      </div>
    </template>
    
    <script>
    import B from "./B";
    export default { 
      components: {
        B
      }
    };
    </script>
    
    <style>
    .h1 {
      margin: 40px 0 0;
      color: red;
    }
    </style>
    
    1. 组件B
    <template>
      <div class="compB">
        <h1 class="h1">B</h1>
        <h1 class="h1">World</h1>
      </div>
    </template>
    
    <script>
    export default {};
    </script>
    
    <style>
    .h1 {
      margin: 40px 0 0;
      color: green;
    }
    </style>
    
    

    打开页面,此时三个h1的颜色都是红色。

    【vue】单文件sfc里的scoped是什么

    在组件B中.h1定义的color: green;被覆盖了。 打开浏览器控制台可以看到,下面的.h1将上面的.h1定义的color覆盖了。

    【vue】单文件sfc里的scoped是什么

    2、使用scoped

    1. A组件
    <template>
      <div class="compA">
        <h1 class="h1">A Hello</h1>
        <B></B>
      </div>
    </template>
    
    <script>
    import B from "./B";
    export default { 
      components: {
        B
      }
    };
    </script>
    
    <style scoped>
    .h1 {
      margin: 40px 0 0;
      color: red;
    }
    </style>
    
    1. B组件
    <template>
      <div class="compB">
        <h1 class="h1">B</h1>
        <h1 class="h1">World</h1>
      </div>
    </template>
    
    <script>
    export default {};
    </script>
    
    <style scoped>
    .h1 {
      margin: 40px 0 0;
      color: green;
    }
    </style>
    

    打开页面后,是这样显示的:

    【vue】单文件sfc里的scoped是什么

    这才是我们想要的效果。打开控制台,发现有下面两个变化:

    1. 打包后的css选择器上多了data-v-XXXXXX

    【vue】单文件sfc里的scoped是什么

    1. dom元素上多了data-v-XXXXXX

    【vue】单文件sfc里的scoped是什么

    由此,我们大概能猜到style标签上加了scoped后编译后的css和页面中的dom元素都会加上data-v-XXXXXX。这样浏览器就能区分A组件的.h1和B组件的.h1

    上面讲的是使用scoped来解决样式污染的问题。当然这并不是我的目的,就像标题说的那样,我希望尝试把scoped是什么以及是怎么样生效的讲清楚。从一下几个问题作为切入点。

    1. 什么是scopeId?scopeId是怎么生成的?
    2. 在sfc的template中我们并没有定义data-v这样的属性, 那为什么在页面的dom元素上会多出data-v-XXXXXX这个属性呢?
    3. 在sfc的style中我们写的选择器也没有加上data-v-XXXXXX,那这是怎么加上去的呢?

    2、什么是scopeId? scopeId是怎么生成的?

    每一个sfc组件都会有自己的唯一scopeId,类似data-v-XXXXXX中的XXXXXX就是scopeId。 我们都知道.vue单文件组件是需要经过vue-loader去处理的。既然每个vue文件的scopeId是唯一的,那么很容易联想到scopeId是不是vue-loader生成的。vue-loader里面的确有这样的逻辑。

      const id = hash(
        isProduction
          ? (shortFilePath + '\n' + source.replace(/\r\n/g, '\n'))
          : shortFilePath
      )
    

    上面的代码就是生成scopeId执行的代码。

    3、在sfc的template中我们并没有定义data-v这样的属性, 那为什么在页面的dom元素上会多出data-v-XXXXXX这个属性呢?

    不知道大家有没有仔细看过组件导出的对象是什么样的?

    import B from "./B";
    console.log('组件B', B)
    

    打印的结果:

    【vue】单文件sfc里的scoped是什么

    导出的组件配置上会多一个_scopeId属性,这个属性不是我们自己定义上去的, 属性值data-v-5277df62是不是很熟悉?

    这个值是不是和上面的dom截图中组件B内部dom元素上的scopeId一样的?

    很明显sfc组件只是在编译阶段往组件配置对象上加了_scopeId属性, 属性值也不是随便写的, 是由vue-loader生成的。生成逻辑上面已经讲过了。

    那么dom元素是什么时候加上scope的呢?在vue2patch逻辑中有这样一段代码,这段代码在src/core/vdom/patch.js中的createElm内。大概的逻辑就是在createElm内部创建真实的dom之后,调用setScope给dom元素添加scopeId

      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      setScope(vnode)
    

    setScope的逻辑是这样的:

      function setScope (vnode) {
        let i
        if (isDef(i = vnode.fnScopeId)) {
          nodeOps.setStyleScope(vnode.elm, i)
        } else {
          let ancestor = vnode
          while (ancestor) {
            if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
              nodeOps.setStyleScope(vnode.elm, i)
            }
            ancestor = ancestor.parent
          }
        }
        // for slot content they should also get the scopeId from the host instance.
        if (isDef(i = activeInstance) &&
          i !== vnode.context &&
          i !== vnode.fnContext &&
          isDef(i = i.$options._scopeId)
        ) {
          nodeOps.setStyleScope(vnode.elm, i)
        }
      }
    

    4、在sfc的style中我们写的选择器也没有加上data-v-XXXXXX,那这是怎么加上去的呢?

    这一切都是由一个叫stylePostLoader的loader来完成的。

    1. stylePostLoader处理css代码

    【vue】单文件sfc里的scoped是什么

    1. compileStyle内部使用postCss对css代码进行转换。下面的截图中可以看到使用了scoped时会使用一个scoped_1插件

    【vue】单文件sfc里的scoped是什么

    1. 查看scoped_1插件的代码,发现有这样一段代码。
     selector.insertAfter(node, selectorParser.attribute({
        attribute: id
     }));
    

    5、/deep/的作用?

    scopedId默认是加在选择器的最后一级上的。比如说下面的例子中有组件A组件B。在组件A中使用了组件B, 当在组件A中修改组件B的内部.el-autocomplete样式时:

    .compA .el-autocomplete {
      width: 280px;
    }
    
    /* 编译后的代码 */
    .compA .el-autocomplete[data-v-19388c91] {
      width: 280px;
    }
    

    使用/deep/

    .compA /deep/.el-autocomplete {
      width: 280px;
    }
    
    /* 编译后的代码 */
    .compA[data-v-19388c91] .el-autocomplete {
      width: 280px;
    }
    

    对比下面的dom结构图, 很明显知道了deep之后起作用的原因了。

    【vue】单文件sfc里的scoped是什么

    • 使用/deep/之前我们想要修改组件B内部带有.el-autocomplete的元素的样式,打包后使用的选择器是.compA .el-autocomplete[data-v-19388c91],很明显带有.el-autocomplete的元素上是不存在data-v-19388c91属性的,所以样式不起作用。
    • 使用/deep/之后呢?打包后的css是这样的.compA[data-v-19388c91] .el-autocomplete, 将scopedId移动到了.compA上, 此时样式是起作用的。
    1. 当在父组件内给子组件的根节点修改样式时,其实是不用加/deep/的, 因为在子组件的根节点上同时有父组件和子组件的scopeId
    2. 当在父组件内给子组件的非根节点修改样式时需要带上/deep/,因为在父组件定义选择器的最后一级使用的是父组件的scopeId,而子组件中需要选择的元素却使用的是子组件的scopeId,所以是不会匹配上的,样式也不会生效。

    起源地下载网 » 【vue】单文件sfc里的scoped是什么

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元