collins

V1

2022/04/12阅读:13主题:自定义主题1

温故而知新,浅入 Vue scoped 原理

前言

通过这篇文章你将了解到:

  • Vue scoped 是什么?
  • Vue scoped 作用?
  • Vue scoped 原理?
  • 渲染的 HTML 标签上的 data-v-xxx 属性是如何生成的?
  • CSS 代码中的添加的属性选择器是如何实现的?

是什么?

在 vue 文件中的 style 标签上,有一个特殊的属性:scoped。当一个 style 标签拥有 scoped 属性时,它的 CSS 样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组件元素。通过该属性,可以使得组件之间的样式不互相污染。如果一个项目中的所有 style 标签全部加上了 scoped,相当于实现了样式的模块化。

作用

样式隔离,样式模块化。

浅入原理

  • 每个 Vue 文件都将对应一个唯一的 id,该 id 根据文件路径名和内容 hash 生成,通过组合形成 scopeId。

  • 编译 template 标签时,会为每个标签添加了当前组件的 scopeId,如:

<div class="demo">test</div>
// 会被编译成:
<div class="demo" data-v-12e4e11e>test</div>
  • 编译 style 标签时,会根据当前组件的 scopeId 通过属性选择器和组合选择器输出样式,如:
.demo{color: red;}
// 会被编译成:
.demo[data-v-12e4e11e]{color: red;}

这样就相当为我们配置的样式加上了一个唯一表示。

但是,有两个问题:

  • 渲染的 HTML 标签上的 data-v-xxx 属性是如何生成的?
  • CSS 代码中的添加的属性选择器是如何实现的?

带着问题我们往下看。

知识储备(可跳过)

在深入了解之前,我们先储备几个相关知识:resourceQuery、loader.pitch、VueLoaderPlugin。(如果你没有耐心,可跳过当前小节,直接从 vue-loader 小节开始)

1. resourceQuery

resourceQuery 的作用是,根据引入文件路径参数的匹配路径,vue-loader 中就是通过 resourceQuery 拼接不同的 query 参数,将各个标签分配给对应的 loader 进行处理。

{
  test: /.css$/,
  resourceQuery: /inline/,
  use: 'url-loader'
}
// 当引入文件路径携带query参数匹配时,也将加载该loader
import Foo from './foo.css?inline'

2. loader.pitch

正常情况,loader 的执行是从右到左,但是其实在从右到左执行之前,会先 从左到右 调用 loader 上的  pitch 方法。例如:

module.exports = {
//...
module: {
    rules: [
      {
//...
        use: ['a-loader''b-loader''c-loader'],
      },
    ],
  },
};

将会发生这些步骤:

|- a-loader `pitch`
  |- b-loader `pitch`
    |- c-loader `pitch`
      |- requested module is picked up as a dependency
    |- c-loader normal execution
  |- b-loader normal execution
|- a-loader normal execution

那在 pitching 阶段能做些什么?首先,传递给  pitch  方法的  data,在执行阶段也会暴露在  this.data  之下,并且可以用于在循环时,捕获并共享前面的信息。

module.exports = function (content) {
  // this.data.value = 42
  return someSyncOperation(content, this.data.value);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  data.value = 42;
};

其次,如果某个 loader 在  pitch  方法中给出一个结果,那么这个过程会回过身来,并跳过剩下的 loader。在我们上面的例子中,如果  b-loader  的  pitch  方法返回了一些东西:

module.exports = function (content) {
  return someSyncOperation(content);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  if (someCondition()) {
    return (
      'module.exports = require(' +
      JSON.stringify('-!' + remainingRequest) +
      ');'
    );
  }
};

上面的步骤将被缩短为:

|- a-loader `pitch`
  |- b-loader `pitch` returns a module
|- a-loader normal execution

3. VueLoaderPlugin

VueLoaderPlugin 主要做的两件事:

  • 一个是注册公共的 pitcher。
  • 一个是复制 webpack 的 rules。

vue-loader

在了解了 resourceQuery、loader.pitch、VueLoaderPlugin 之后,我们接下来我们看看 vue-loader 做的事情。

pitcher

根据 query.type 注入处理对应标签的 loader。由于 loader.pitch 会先于 loader 执行 ,在捕获阶段执行,检查 query.type 并直接调用相关的 loader。

  • type = style,执行 stylePostLoader
  • type = template,执行 templateLoader image 这里引出了两个重点 loader:stylePostLoader、templateLoader,下文会有两个单独的小节来介绍。

prepare

这一步主要是做一些准备工作。

  1. 生成单文件唯一的哈希 ID。 image
  2. 处理 template 标签,拼接 query 参数 image
  3. 处理 style 标签,为每个标签拼接 type=style 等参数 image image 准备工作做好之后,就是不用的 type 调用不用的 loader 进行处理。第一个就是对 template 的处理,templateLoader。

templateLoader

回头来看,Vue 中一个组件最后都会生成  render 方法,然后 render 生成  VNode,vnode 是描述组件对应的 HTML 标签和结构,一个 vnode 包含了渲染 DOM 节点需要的基本属性,当然这里的基本属性也包含了 scopeId。那这里的 scopeId 怎么最后到 DOM 上的了? 在 templateLoader.js 中,当 scopre=true 的 template 的文件会根据单文件唯一的哈希 ID 生成一个 scopeId。

image 接下来就是通过模板编译器将模板生成我们熟悉的 vnode,这个过程中,会对配置属性进行处理,也就是在这个过程中,scopeId,被解析到 vnode 的配置属性。然后在 render 函数执行时调用 createElement ,作为 vnode 的原始属性,渲染成到 DOM 节点上。这也回答了上文提到的第一个问题「渲染的 HTML 标签上的 data-v-xxx 属性是如何生成的? 」。 image

stylePostLoader

templateLoader,解决了 id 渲染 DOM 上面的问题,而 stylePostLoader 的作用就是在 Css 中添加属性选择器。 在 stylePostLoader.js 中生成一个 id ,同一个单页面组件中的 style,与 templateLoader 中的 scopeId 保持一致。 image 然后通过 PostCSS 解析 style 标签内容,同时通过 scopedPlugin 为每个选择器追加一个 [scopeId] 的属性选择器。 image 这里还会对 scoped 有一些特殊处理。对于  '>>>' 、 '/deep/'、::v-deep、pseudo 等特殊选择器时,将不会将 [scopeId] 的属性选择器追加。 image

到这里对于文章之前提到的第二个问题「CSS 代码中的添加的属性选择器是如何实现的?」,就水落石出了,通过 selector.insertAfter 为当前 styles 下的每一个选择器添加了属性选择器,其值即为传入的[scopeId]。

由于只有当前组件渲染的 DOM 节点上上面存在相同的属性,从而就实现了 css scoped 的效果。

总结

本文简单浅析了 Vue scoped 的底层实现原理。vue-loader 通过生成哈希 ID,根据 type 的不同调用不同的 loader 将,哈希 ID 分别注入到 DOM 和属性选择器中。实现 CSS 局部作用域的效果。CSS Scoped 可以算作为 Vue 定制的一个处理原生 CSS 作用域的解决方案。

以上就是本文的全部内容。谢谢观看,如果你还觉得不错,帮忙点个赞,谢谢。

参考

分类:

前端

标签:

JavaScript

作者介绍

collins
V1