vue 快速入门 系列 —— vue loader 上

其他章节请看:

vue 快速入门 系列

vue loader 上

通过前面“webpack 系列”的学习,我们知道如何用 webpack 实现一个不成熟的脚手架,比如提供开发环境和生成环境,开发环境提供本地服务器,有热模块替换,能使用 sass、es6等开发项目。

实际工作中我们可能会使用声明式框架 vue 或 react 来开发项目,而它们都提供了相应的脚手架。在学习 vue-cli(vue官方的脚手架)之前,我们先来玩一下 vue loader。

Tip:本篇也可以称之为“vue loader 官网”笔记。通过本篇文章,我们能学会编写一个简单的,用于单文件组件开发的脚手架;以及对单文件组件规范有一个初步的认识和理解。

:本文很多配置都参考笔者的另一篇文章webpack 快速入门 系列 —— 实战一,比如babel、postcss、图片、css提取等配置。

介绍

Vue Loader 是什么

Vue Loader 是一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。

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

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

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

Tip: 更详细的介绍请看下面的“Vue 单文件组件 (SFC) 规范”章节。

Vue Loader 还提供了很多酷炫的特性:

  • 允许为 Vue 组件的每个部分使用其它的 webpack loader,例如在 <style> 的部分使用 Sass 和在 <template> 的部分使用 Pug;
  • 允许在一个 .vue 文件中使用自定义块,并对其运用自定义的 loader 链;
  • 使用 webpack loader 将 <style><template> 中引用的资源当作模块依赖来处理;
  • 为每个组件模拟出 scoped CSS;
  • 在开发过程中使用热重载来保持状态。

简而言之,webpack 和 Vue Loader 的结合为你提供了一个现代、灵活且极其强大的前端工作流,来帮助撰写 Vue.js 应用。

Tip: 上面这些特性,下文都会详细介绍。

环境准备

直接参考"实战一->准备本篇的环境"一节,搭建好 test-vue-loader 项目。

附上项目:

test-vue-loader        
  - src                 // 项目源码
    - a.css
    - b.js
    - c.js
    - index.html        // 页面模板
    - index.js          // 入口
  - package.json        // 存放了项目依赖的包
  - webpack.config.js   // webpack配置文件

hello-world

现在我们要把 App.vue 这个单文件组件跑起来:

// src/App.vue
<template>
  <div class="example">{{ msg }}</div>
</template>

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

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

请看操作:

首先将 vue-loader 和 vue-template-compiler 一起安装。

// vue 包当然也需要
> npm install -D vue@2 vue-loader@15 vue-template-compiler@2

每个 vue 包的新版本发布时,一个相应版本的 vue-template-compiler 也会随之发布。每次升级项目中的 vue 包时,也应该匹配升级 vue-template-compiler。

接着修改配置文件,核心代码如下:

const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  module: {
    rules: [
      // ... 其它规则
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    // 请确保引入这个插件!
    new VueLoaderPlugin()
  ]
}

在将 src/index.js 改为:

//引入Vue
import Vue from 'vue';
//引入组件
import App from './App.vue';

new Vue({    
    el: "body",    
    template: '<App/>',    
    components: {App}
});

通过 npm run dev 启动,浏览器控制台报错,信息如下:

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

(found in <Root>)

// 翻译
您正在使用 Vue 的仅运行时构建,其中模板编译器不可用。 要么将模板预编译为渲染函数,要么使用包含编译器的构建。

告诉我们,正使用“只包含运行时版”,可以将模板编译为渲染函数,或者使用包含编译器的构建。

Tip:vue 有不同的版本,例如:

  • 完整版:同时包含编译器和运行时的版本。
  • 编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码。
  • 运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切。

在“模块(module)”一文中介绍了 require() 方法加载第三方模块的规则,所以 import Vue from 'vue'; 就会去加载 test-vue-loader\node_modules\vue\package.json 中 main 指向的文件("main": "dist/vue.runtime.common.js"),确实是运行时版。

可以通过以下两种方式修改 index.js 来解决这个问题。

  • 使用完整版本
// inidex.js

- import Vue from 'vue';
// 前面会匹配是否核心包、第三方包、路径查找(./ 或 ../ 或 /),最后读取到项目目录下 node_modules 包里的包
// vue.esm.js ES Module (基于构建工具使用),并且是完整版
+ import Vue from 'vue/dist/vue.esm.js'
...

Tip:这种方式在浏览器中会有如下警告:

[Vue warn]: Do not mount Vue to <html> or <body> - mount to normal elements instead.

所以将 el 中的 body 改为 #app 这种形式即可。

  • 改为渲染函数
// index.js
import Vue from 'vue';
import App from './App.vue';

new Vue({    
    el: "body", 
    render: h => h(App)
});

浏览器页面显示红色文字”Hello world!“,单文件组件解析成功。

处理资源路径

当 Vue Loader 编译单文件组件中的 <template> 块时,它也会将所有遇到的资源 URL 转换为 webpack 模块请求。

让 App.vue 引入一张图片:

<template>
  <div class="example">
    {{ msg }}
    <!-- 增加图片 -->
    <img src="./6.68kb.png"></img>
  </div>
</template>

终端报错:

...
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.

// 翻译
您可能需要一个合适的加载器来处理此文件类型,目前没有配置加载器来处理此文件。

下载包,并修改配置:

> npm i -D url-loader@4 file-loader@6
// webpack.config.js -> module.rules
{
  test: /\.(png|jpg|gif)$/i,
  use: [
    {
      loader: 'url-loader',
      options: {
        // 调整的比 6.68 要小,这样图片就不会打包成 base64 
        limit: 1024*6,
      },
    },
  ],
},

再次运行,浏览器还是看不到图片,查看源码:

<div class="example">
  Hello world!
  <img src="[object Module]">
</div>

图片的 src 有问题,这是因为 url-loader 默认采用 ES 模块语法,而 Vue 生成的是 CommonJS 模块语法,即 require('./6.68kb.png'),解决方法是让二者采用相同的模板语法,下面将 url-loader 的 es-module 关闭:

// webpack.config.js
module: {
    rules: [
      ...
      {
        test: /\.(png|jpg|gif)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 1024*6,
              // +
              esModule: false,
            },
          },
        ],
      },
    ]
},

再次运行,图片正常显示。控制台查看:

<div class="example">
  Hello world!
  <img src="26bd867dd65e26dbc77d1e151ffd36e0.png">
</div>

转换规则

  • 如果路径是绝对路径 (例如 /6.68kb.png),则会原样保留。测试如下:

修改 App.vue,将其改为绝对路径

<img src="/6.68kb.png"></img>

浏览器页面中图片没出来,检查元素:

<div class="example">
  Hello world!
  <img src="/6.68kb.png">
</div>

通过运行 npm run build 发现 dist 目录中也没有生成 6.68kb.png。于是我们知道使用绝对路径,不仅会原样保留,也不会将该资源打包出去。

  • 如果路径以 ~ 开头,其后的部分将会被看作模块依赖。这意味着你可以用该特性来引用一个 Node 依赖中的资源。测试如下:

将图片 6.68kb.png 拷贝到一个模块 node_modules/vue 中

修改图片引用

<img src="~vue/6.68kb.png"></img>

图片正常显示

  • 如果路径以 @ 开头,也会被看作模块依赖。如果你的 webpack 配置中给 @ 配置了 alias,这就很有用了。所有 vue-cli 创建的项目都默认配置了将 @ 指向 /src。测试如下:

将路径改为以 @ 开头,终端报错:

// 以 @ 开头
<img src="@vue/6.68kb.png"></img>

// 终端报错
Module not found: Error: Can't resolve '@vue/6.68kb.png' in '....\test-vue-loader\src'

@ 是指向 /src 吗?还是报错:

<img src="@/6.68kb.png"></img>

// 终端报错
Module not found: Error: Can't resolve '@/6.68kb.png' in...

给 @ 配置 alias,图片正常显示。请看代码:

<img src="@/6.68kb.png"></img>

// 给 @ 配置 alias
module.exports = {
  ...
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src/'),
    },
  },
}

使用预处理器

在 webpack 中,所有的预处理器需要匹配对应的 loader。Vue Loader 允许你使用其它 webpack loader 处理 Vue 组件的某一部分。它会根据 lang 特性以及你 webpack 配置中的规则自动推断出要使用的 loader。

sass

给 App.vue 增加 sass 代码:

// App.vue 尾部增加如下sass代码
<style lang="scss">
$size: 3em;
.example {
  font-size: $size;
}
</style>

为了能让 sass/scss 生效,需要安装依赖包:

> npm i -D sass-loader@10 node-sass@6

修改配置文件:

// webpack.config.js -> module.rules

// 普通的 `.scss` 文件和 `*.vue` 文件中的
// `<style lang="scss">` 块都应用它
{
  test: /\.scss$/,
  use: [
    'vue-style-loader',
    'css-loader',
    'sass-loader'
  ]
}

重启服务,你会发现页面中的 ”hello world“ 字号变大,sass 编译成功。

Tip: vue-style-loader 是一个基于 style-loader 的 fork。 与 style-loader 类似,您可以将其链接在 css-loader 之后,以将 CSS 作为样式标签动态注入文档。 但是,由于它作为依赖项包含在 vue-loader 中并默认使用,因此在大多数情况下,您不需要自己配置此加载器,即无需下载 vue-style-loader 即可使用。

Sass vs SCSS

sass-loader 默认处理不基于缩进的 scss 语法。

将 sass 改为缩进语法,终端会报错:

// 缩进语法
<style lang="sass">
$size: 3em
.example 
  font-size: $size;
</style>

// 终端报错
SassError: Invalid CSS after "$size: 3em": expected expression (e.g. 1px, bold), was ".example "...

:需要将 lang 从 scss 改为 sass,配合下面的 rule 工作。

为了使用基于缩进的 sass 语法,你需要向这个 loader 传递选项:

// webpack.config.js -> module.rules
{
  test: /\.sass$/,
  use: [
      'vue-style-loader',
      'css-loader',
      {
          loader: 'sass-loader',
          options: {
              // sass-loader version >= 8
              sassOptions: {
                  indentedSyntax: true
              }
          }
      }
  ]
},

重启服务器,缩进语法生效了。

共享全局变量

sass-loader 也支持一个 prependData 选项,这个选项允许你在所有被处理的文件之间共享常见的变量,而不需要显式地导入它们。请看示例:

// webpack.config.js -> module.rules
{
  test: /\.sass$/,
  use: [
      ...
      {
          loader: 'sass-loader',
          options: {
              ...,
              additionalData: `$size: 3em;`,
          }
      }
  ]
},

App.vue 中直接使用 $size,而无需定义:

...
<style lang="sass">
.example 
  font-size: $size;
</style>

less

若直接在 App.vue 中增加如下 less 的样式,会报错:

// 给 App.vue 增加 less 语法
<style lang="less">
@size: 2em;
.example {
  font-size: @size
}
</style>
// 终端报错:
ERROR in ./src/App.vue?vue&type=style&index=2&lang=less&..
Module parse failed: Unexpected character '@' (29:0)...

安装依赖,并增加 rule,重启服务即可生效。

> npm i -D less@4 less-loader@7
// webpack.config.js -> module.rules
{
  test: /\.less$/,
  use: [
    'vue-style-loader',
    'css-loader',
    'less-loader'
  ]
}

Stylus

若直接在 App.vue 中增加如下 stylus 的样式,会报错:

// 给 App.vue 增加 stylus 语法
<style lang="stylus">
/* stylus 语法 */
$size = 3em
.example
  font-size: $size
</style>
// 终端报错:
ERROR in ./src/App.vue?vue&type=style&index=3&lang=stylus&..

安装依赖,并增加 rule,重启服务即可生效。

> npm i  -D stylus@0 stylus-loader@4
// webpack.config.js -> module.rules
{
  test: /\.styl(us)?$/,
  use: [
    'vue-style-loader',
    'css-loader',
    'stylus-loader'
  ]
}

PostCSS

tip:Vue Loader v15 不再默认应用 PostCSS 变换。你需要通过 postcss-loader 使用 PostCSS。

我们的 vue loader 是 15.9.7,满足该条件。

postcss-loader 可以和上述其它预处理器结合使用。下面我们就给 less 预处理器添加 postcss。

修改 App.vue,给 less 中增加明天的 css 语法:

// lch 是明天的CSS
<style lang="less">
@size: 2em;
.example {
  color: lch(100 100 100);
  font-size: @size
}
</style>

浏览器查看样式,发现 color: lch(100 100 100) 没生效。

安装依赖包,并修改配置文件:

> npm i -D postcss-loader@4 postcss-preset-env@6
// webpack.config.js
// +
const postcssLoader = { 
  loader: 'postcss-loader', 
  options: {
    // postcss 只是个平台,具体功能需要使用插件
    postcssOptions:{
      plugins:[
        [
          "postcss-preset-env",
          {
            browsers: 'ie >= 8, chrome > 10',
          },
        ],
      ]
    }
  } 
}

module: {
    rules: [
    {
        test: /\.less$/,
        use: [
            'vue-style-loader',
            'css-loader',
            // +
            postcssLoader,
            'less-loader'
        ]
    },

重新启动服务器,”Hello World!“ 显示黄色。lch 也编译成了 color: rgb(255, 255, 0)

Babel

首先编写箭头函数,如果打包后能转为普通函数,则说明 babel 配置成功。

给 App.vue 增加箭头函数:

<script>
export default {
  ...
};
// 箭头函数
const sum = (a, b) => (a + b);
console.log(sum(1, 10));
</script>

浏览器的控制台输出 11,但在浏览器中的源中查看 mian.js,发现箭头函数没有转为普通函数。

const sum = (a, b) => (a + b);\r\nconsole.log(sum(1, 10));\r\n\n\n/

安装依赖,并修改配置:

> npm i -D babel-loader@8 @babel/preset-env@7
// webpack.config.js -> module.rules
{
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
      loader: 'babel-loader',
      options: {
        presets: [
          ['@babel/preset-env']
        ]
      }
    }
}

重新启动服务器,箭头函数就变成普通函数:

var sum = function sum(a, b) {\n  return a + b;\n};\n\nconsole.log(sum(1, 10));
排除 node_modules

exclude: /node_modules/ 在应用于 .js 文件的 JS 转译规则 (例如 babel-loader) 中是蛮常见的。鉴于 v15 中的推导变化,如果你导入一个 node_modules 内的 Vue 单文件组件,它的 <script> 部分在转译时将会被排除在外。

我们将 src/App.vue 拷贝一份到 node_modules/vue 目录中,并修改 index.js 中 App.vue 的引入方式:

- import App from './App.vue';
+ import App from 'vue/App.vue';

在浏览器中的源中查看 mian.js,发现箭头函数没有转为普通函数:

const sum = (a, b) => (a + b);\r\nconsole.log(sum(11, 10));

为了确保 js 的转译应用到 node_modules 的 Vue 单文件组件,你需要通过使用一个排除函数将它们加入白名单:

{
    test: /\.js$/,
    exclude: file => (
        /node_modules/.test(file) &&
        !/\.vue\.js/.test(file)
    ),
    ...
}

重启服务即可生效

:进行下面测试之前,别忘了还原 App.vue 的引入

import App from './App.vue'

TypeScript

给 App.vue 写入 ts 代码,终端报错:

// 修改 App.vue 的 script
<script lang='ts'>
export default {
  ...
}

...
/* typescript */
class Greeter<T> {
    greeting: T;
    constructor(message: T) {
        this.greeting = message;
    }
    greet() {
        return this.greeting;
    }
}

let greeter = new Greeter<string>("Hello, world");
console.log(greeter.greet())
</script>
// 终端报错
...
You may need an additional loader to handle the result of these loaders.
|
| /* typescript */
> class Greeter<T> {
|     greeting: T;
|     constructor(message: T) {

安装依赖包,并修改配置:

> npm i -D typescript@4 ts-loader@7
// webpack.config.js
module.exports = {
  resolve: {
    // 将 `.ts` 添加为一个可解析的扩展名。
    extensions: ['.ts', '.js']
  },
  module: {
    rules: [
      // ... 忽略其它规则
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        options: { appendTsSuffixTo: [/\.vue$/] }
      }
    ]
  },
  ...
}

重启服务,终端报错:

[tsl] ERROR
      TS18002: The 'files' list in config file 'tsconfig.json' is empty.

新建 test-vue-loader/tsconfig.json,内容如下:

{
  "compilerOptions": {
    "sourceMap": true
  }
}

重启服务,终端报错信息变为:

TS18003: No inputs were found in config file 'tsconfig.json'. Specified 'include' paths were '["**/*"]' and 'exclude' paths were '[]'.

可以给 tsconfig.json 增加 allowJs:

{
  "compilerOptions": {
    "allowJs": true, 
    "sourceMap": true
  }
}

重启服务器,终端没有抛出错误,浏览器控制台成功输出 ”hello, TypeScript 解析成功。

Pug

Pug 是一个高性能模板引擎,深受 Haml 影响,使用 JavaScript 实现,适用于 Node.js 和浏览器;

Pug 是一种用于编写 html 的干净、对空格敏感的语法

在 App.vue 中使用 pug,重新打包,停住了:

<template>
  <div class="example">
    ...
  </div>
</template>

<!-- 多个 template,会以最后一个 template 为准-->
<template lang="pug">
div
  h1 I am pug!
</template>
// 打包
test-vue-loader> npm run build

> test-vue-loader@1.0.0 build
> webpack

不动了...

猜测可能是没有配置 pug 导致的。于是安装依赖,并修改配置:

> npm i -D pug@3 pug-plain-loader@1
// webpack.config.js -> module.rules
{
  test: /\.pug$/,
  loader: 'pug-plain-loader'
}

重新启动服务器,浏览器页面显示 I am pug!,pug 解析成功。

// 浏览器查看源码
<div>
  <h1>I am pug!</h1>
</div>

Scoped Css

Tip: 为了减少影响,方便学习和测试,可以将 App.vue 的代码全部注释,就像这样<!-- App.vue 的所有代码 -->

<style> 标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素,它有一些注意事项,但不需要任何 polyfill。

修改 App.vue 内容,给 style 增加 scoped:

<template>
  <div class="example">hi</div>
</template>

<style scoped>
.example {
  color: red;
}
</style>

通过浏览器检查:

.example[data-v-7ba5bd90] {
    color: red;
}
<div data-v-7ba5bd90="" class="example">hi</div>

Tip:文档说它通过使用 PostCSS 来实现转换,但目前我的 postcss 只结合 less 使用,这里使用的明显是 css,所以猜想 postCss 难道内置了!

混用本地和全局样式

<style scoped>
.example {
  color: red;
}
</style>

<style>
.example {
  font-size: 2em;
}
</style>

转换结果:

<style>
.example[data-v-7ba5bd90] {
  color: red;
}
</style>

<style>
.example {
  font-size: 2em;
}

子组件的根元素

使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。—— 官网

什么意思?我们做个测试就明白了

新建一个子组件:

// Box.vue
<template>
<div class='m-box'>
    children
    <p>m-box p1</p>
</div>
</template>

在 App.vue 中引用子组件:

<template>
  <div class="s-c1">
    <p>hi</p>
    <Box/>
  </div>
</template>

<script>
import Box from './Box.vue'
export default {
  data () {
    return {
      msg: 'Hello world!'
    }
  },
  components:{
    Box
  }
}
</script>

<style scoped>
.s-c1 {
  color: red;
}
</style>

页面中三行文字全是红色。

hi
children
m-box p1

子组件明明没有写样式,而且父组件的样式也写在 scope 中,为什么子组件的文字也变成红色?

浏览器查看代码:

<style>
.s-c1[data-v-7ba5bd90] {
  color: red;
}
</style>

<div data-v-7ba5bd90="" class="s-c1">
    <p data-v-7ba5bd90="">hi</p> 
    <div data-v-1461803c="" data-v-7ba5bd90="" class="m-box">
        children
        <p data-v-1461803c="">m-box p1</p>
    </div>
</div>

原来我们写的代码转成这种形式,子组件的文字确实应该是红色。

而上面提到:”不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响“

指的应该是子组件的根元素上既有子组件的标记,也有父组件的标记,即同时有 data-v-1461803c=""data-v-7ba5bd90=""

将 App.vue 的 style 改成下面代码,在页面中会看得更清晰:

<style scoped>
.s-c1 {
  color: red;
  margin:10px;padding:10px;
}
div{
  border: 1px solid blue;
}
</style>

不仅父组件有边框,子组件的的根(div)也会有蓝色边框。

深度作用选择器

如果你希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>> 操作符:

只调整父组件中 p 元素的字号,可以这么写:

<style scoped>
...
div p{font-size:2em;}
</style>

转换成:

div p[data-v-7ba5bd90]{font-size:2em;}

如果也希望调整子组件中 p 元素的字号:

div >>> p{font-size:2em;}

转换成:

div[data-v-7ba5bd90] p{font-size:2em;}

如果希望只作用于 div 的孩子节点:

div >>> > p{font-size:2em;}

转换成:

div[data-v-7ba5bd90] > p{font-size:2em;}

>>>>不会生效

有些像 Sass 之类的预处理器无法正确解析 >>>。这种情况下你可以使用 /deep/ 或 ::v-deep 操作符取而代之——两者都是 >>> 的别名,同样可以正常工作。

动态生成的内容

通过 v-html 创建的 DOM 内容不受 scoped 样式影响,但是你仍然可以通过深度作用选择器来为他们设置样式。

我们做个测试

我们给 App.vue 和 Box.vue 都增加 v-html,代码如下:

// App.vue
<template>
  <div class="s-c1">
    <p>hi</p>
    <!-- + -->
    <span v-html='aHtml'></span>
    <Box/>
  </div>
</template>

<script>
...
export default {
  data () {
    return {
      // +
      aHtml: '<p>i am aHtml</p>'
    }
  },
}
</script>

<style scoped>
...
div  >>>  p{font-size:2em;}
</style>
// Box.vue
<template>
<div class='m-box'>
    children
    <span v-html='bHtml'></span>
    <p>m-box p1</p>
</div>
</template>

<script>
export default {
  data () {
    return {
      bHtml: '<p>i am bHtml</p>'
    }
  }
}
</script>

i am aHtmli am bHtml字号都是 2em

浏览器查看转换后的代码:

div[data-v-7ba5bd90] p{font-size:2em;}
<div data-v-7ba5bd90="" class="s-c1" style="margin: 10px; padding: 10px;">
  <p data-v-7ba5bd90="">hi</p>
  <span data-v-7ba5bd90="">
    <p>i am aHtml</p>
  </span>
  <div data-v-1461803c="" data-v-7ba5bd90="" class="m-box">children2
    <span data-v-1461803c="">
      <p>i am bHtml</p>
    </span>
    <p data-v-1461803c="">m-box p1</p></div>
</div>

将深度作用选择器删除后测试:

div p{font-size:2em;}

i am aHtmli am bHtml字号都不在是 2em。

转换后的代码是:

div p[data-v-7ba5bd90]{font-size:2em;}
<div data-v-7ba5bd90="" class="s-c1">
  <p data-v-7ba5bd90="">hi</p> 
      <span data-v-7ba5bd90="">
      <p>i am aHtml</p>
  </span>
  <div data-v-7ba5bd90="" class="m-box">
      children
      <span>
          <p>i am bHtml</p>
      </span>
      <p>m-box p1</p>
  </div>
</div>

v-html生成的元素都不会有特殊的标记,比如这里的 data-v-7ba5bd90

至此,我们就理解了开头的话。

还有一些要留意

Scoped 样式不能代替 class。考虑到浏览器渲染各种 CSS 选择器的方式,当 p { color: red } 是 scoped 时 (即与特性选择器组合使用时) 会慢很多倍。如果你使用 class 或者 id 取而代之,比如 .example { color: red },性能影响就会消除。

div{}
.div{}

转换成:

div[data-v-7ba5bd90]{}
.div[data-v-7ba5bd90]{}

在递归组件中小心使用后代选择器!

  • 对选择器 .a .b 中的 CSS 规则来说,如果匹配 .a 的元素包含一个递归子组件,则所有的子组件中的 .b 都将被这个规则匹配。

其他章节请看:

vue 快速入门 系列

posted @ 2021-07-01 15:23  彭加李  阅读(2826)  评论(0编辑  收藏  举报