【Vue】详解 SFC 与 vue-loader

vue-loader 是一个Webpack的 loader,使用 vue-loader 就可以用 Vue Single-File Component (SFC) 即单文件组件的形式编写一个组件,vue-loader 会将.vue文件转换为 JS模块。

如果在项目开始阶段为前端工程化复杂的配置而困惑,不妨试试使用 IDE 自动构建,推荐使用 WebStorm 新建 Vue 项目,参考 WebStorm 2018 的破解、汉化与设置使用

.vue 单文件组件 (SFC) 规范

1. <template>模板块

一个SFC中最多一个< template >块;
其内容将被提取为字符串传递给 vue-template-compiler ,然后webpack将其编译为js渲染函数,并最终注入到从 <script> 导出的组件中;

2. <script>脚本块

一个SFC最多一个<script>块;
它的默认导出应该是一个 Vue.js 的组件选项对象,也可以导出由 Vue.extend() 创建的扩展对象。
思考:Vue.extend() 中 data 必须是函数,所以在.vue SFC的script中,export中的data是函数

3. <style>样式块

一个 .vue 文件可以包含多个 <style> 标签;
可以使用scope和module进行封装;
具有不同封装模式的多个 <style> 标签可以在同一个组件中混合使用;

4. 自定义语言块

vue-loader 将会使用块名来查找对应的 loader 进行处理,需要配置webpack.config

5. 所有语言块支持 src 导入

导入路径遵循和 webpack 模块请求相同的路径解析规则
// 相对路径需要以../开始
<template src="./template.html"></template>
<style src="./style.css"></style>
<script src="./script.js"></script>

vue-loader 特性概述

  • 支持组件的各个 template,script,style 模块使用其他非默认语言,比如:<style lang=“less”> 使用 less/sass 编译语言,< template >使用 JADE(jade是一个高性能的模板引擎,用JS实现,也有其他语言的实现—php,scala,yuby,python,java,可以供给node 使用)
  • 除了默认的<template>等语言块,还可以加自定义块,然后使用自定义的 loader 处理他们
  • 为组件中的 css 模拟局部作用域,直接<style scope>声明
  • 将 style 和 template 中的静态资源(图片等)当作模块依赖,并通过 webpack loader 处理
  • 在开发模式下的热重载

预处理

Vue 支持各类型的预处理器,这些预处理可以编译语言块,例如 Vue 默认使用 PostCSS 处理 css,你可以用 sass,less,stylus 等, 对于 js 部分 Vue 默认使用 babel 处理,还可以用 coffee、typescript 等,只需要安装相应的 loader 加载器,Vue 会根据语言块的 lang 属性和 webpack 配置的 option rules 推断对应的 loader。
小栗子:css 使用 sass 预处理
$ npm install sass-loader node-sass  --save-dev
// webpack.config.js -> module.rules
{
    test: /\.sass$/,
    use: [
        'vue-style-loader',
        'css-loader',
        {
            loader: 'sass-loader',
            options: {
                indentedSyntax: true  //sass-loader 默认解析 SCSS 语言
            }
        }
    ]
}
<!--  .vue -> style 增加lang属性并赋值 -->
<style lang="sass">
/* write SASS here */
</style>
对于模版<template>的处理方式略有不同,因为大多数 Webpack 模版处理器(比如 pug-loader)会返回模版处理函数,而不是编译的 HTML 字符串,我们可以原始的 pug 替代 pug-loader。
<!--  .vue -> template -->
<template lang="pug">
div
  h1 Hello world!
</template>

CSS 作用域:scope

当<style>标签带 scope 属性时,创造出 css 的“局部作用域”,css 只作用于当前组件的元素,类似 Shadow Dom 封装。可以在同一个组件中使用 scoped 跟 non-scoped styles,如下所示:

<style>
/* global styles */
</style>

<style scoped>
/* local styles */
.example {
  color: red;
}
</style>
<template>
  <div class="example">hi</div>
</template>

vue-loader 处理的 CSS 输出,都是通过 PostCSS 进行作用域重写,PostCSS 处理后如下:

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

<template>
  <div class="example" data-v-f3f3eg9>hi</div>
</template>
  • 使用 scope 时,子组件的根节点将受父组件作用域 CSS 影响

使用 scope 作用域时,父组件的样式不会泄漏到子组件中。 但子组件的根节点将受父级作用域 CSS 和子级作用域 CSS 的影响。 这是为了父级可以设置子组件根元素的样式以进行布局。

  • 使父组件可以使用‘ >>> ’或‘ /deep/ ’ 这种深度选择器作用于子组件
<style scoped>
.a >>> .b { /* ... */ }
</style>

编译为

.a[data-v-f3f3eg9] .b { /* ... */ }
  • 动态生成的 DOM 内容不受 scope style 的影响,但可以使用深度选择器进行样式改变
  • 使用 scope 作用域不能弃用 class 或 id 等

考虑到浏览器渲染各种 CSS 选择器的方式,当使用 scoped 时,选择属性选择器如 p { color: red } 在作用域中会慢很多倍(即当与属性选择器组合时)。如果你使用 class 或者 id 代替,比如 .example { color: red },这样几乎没有性能影响

  • 在递归组件中注意后代选择器

对于带有 .a .b 的 CSS 选择器,如果匹配 .a 的元素包含递归子组件,则该子组件中的所有 .b 将与其匹配。

小思考:在 template 中只包含一个外层节点,不能多个节点并列,这个设计思路遵循父组件可以操作子节点的一个根节点,即使在 CSS 局部作用域下依然有效

CSS 模块模式:module

一个 CSS Module 其实就是一个 CSS 类型的文件,其编写方式与 CSS 相同,但在编译时会编译为 ICSS 低级交换格式。
其默认所有的类名/动画名都在本地作用域,当从 JS 模块导入 CSS 模块时,它会导出包含从本地名称到全局名称的所有映射的一个对象

在 CSS Module 中,所有的 url 和 @import 都是被看成模块依赖,例如 url(./image.png) 会被转换为 require(‘./image.png’)
CSS 模块处理是通过 css-loader,请求的资源可以是在 node_modules 中。

CSS Module 配置

Vue loader 集成了 CSS Module,使用前需要在 css-loader 中设置 modules: true,如下:

// webpack.config.js -> module.rules
{
  test: /\.css$/,
  use :[
    'vue-style-loader',
    {
      loader: 'css-loader',
      options: {
        // enable CSS Modules
        modules: true,
        // customize generated class names
        localIdentName: '[local]_[hash:base64:8]'
      }
    }
  ]
}    

然后就在<style>中增加 module 属性

<style module>
.red {
  color: red;
}
</style>
CSS Module 使用

这样 Vue loader 会将 css module 本地对象编译为计算属性注入到组件中,默认值为 $style。template 可以通过动态类绑定使用:

<template>
  <p :class="$style.red">
    This should be red
  </p>
</template>

JS 也可以通过 this.$style访问:

<script>
export default {
  created () {
    console.log(this.$style.red)
    // -> "red_1VyoJ-uZ"
    // an identifier generated based on filename and className.
  }
}
</script>
为多个 style 自定义标识符

一个 .vue 组件可以有多个<style>,为避免 module 注入样式覆盖,可以通过给属性 module 赋值的方式自定义计算属性的名称:

<style module="a">
  /* 标识符注入为 a */
</style>
<style module="b">
  /* 注入为 b */
</style>
结合预处理

CSS Module 还可以在预处理(SASS,LESS等)中使用,在配置 webpack rules 时加上 modules: true,例如:

// webpack.config.js -> module.rules test: /\.scss$/ 
use[{
      loader: 'css-loader',
      options: { modules: true }
 }]

.vue 中自定义节点/语言块

除了默认的<template>等节点,还可以加自定义节点,并使用自定义的 loader 处理他们;
可以给节点加 lang 属性,此时节点 rule 匹配 lang 的扩展;
如果没有标记 lang,则自定义节点的 name 和 rule 需要在 webpack 中声明。

小栗子:自定义语言块 <myblock>

首先需要 loader。为了将自定义块注入到组件中,自定义一个 loader 如下:

// myblock-loader.js 
module.exports = function (source, map) {
  this.callback(
    null,
    `export default function (Component) {
      Component.options.__myblock = ${
        JSON.stringify(source)
      }
    }`,
    map
  )
}

配置到 webpack

// webpack.config.js -> module.rules
{
    resourceQuery: /blockType=myblock/,
    loader: require.resolve('./myblock-loader.js')
}

在组件中使用

<!-- ComponentB.vue -->
<template>
  <div>Hello</div>
</template>

<myblock>
This is the documentation for component B.
</myblock>

组件间的复用

<!-- ComponentA.vue -->
<template>
  <div>
    <ComponentB/>
    <p>{{ myblock }}</p>
  </div>
</template>

<script>
import ComponentB from './ComponentB.vue';

export default {
  components: { ComponentB },
  data () {
    return {
      myblock: ComponentB.__myblock
    }
  }
}
</script>

热重载:不刷新页面的更新

当更改 .vue 的 template 或 style 时,页面不刷新也可实现内容动态的变化,并保留着程序及其组件的状态。在以下情况下会保持热重载:

  1. 编辑组件的<style>,热重载通过 vue-style-loader 自行运行,不会影响应用程序状态
  2. 编辑组件的<template>,实例会重现,这是因为<template>被编译成新的渲染函数,不会产生副作用。
  3. 编辑组件的部分<script>,编辑的实例将销毁后重建,这是因为<script>可能包含产生副作用的生命周期钩子,因此需要“重新加载”以确保一致的行为。如果组件产生全局副作用则需要整页的重新加载。
热加载是默认启动的,当以下情况下关闭:
  • webpack target is node (SSR)
  • webpack 压缩代码
  • 生产模式:process.env.NODE_ENV === 'production'

也可以手动禁用热重载:

// webpack.config.js -> module.rules
{
    test: /\.vue$/,
    loader: 'vue-loader',
    options: {
        hotReload: false // disables Hot Reload
    }
}

功能组件

一个简单的.vue组件可以声明为无状态的(没有反应数据)和无实例的(没有此上下文)的功能组件/函数式组件。

vue.js 2.5版本以上,在 template 中加上 functional 属性即可声明:

<template functional>

</template>

在 script 中的结构为:

Vue.component('my-component', {
  functional: true,
  props: { //也可不定义props
    // ...
  },
  render: function (createElement, context) {
    // createElement, context 为上下文参数
    // ...
  }
})

组件加上 functional: true 后,更新渲染函数需要添加 context 参数,故 this.$slots.default 更新为 context.children,this.level 更新为 context.props.level。
功能组件通过 context 对象传递/获取所有的内容,例如:

  • context.props: 包含功能组件的 props 的对象,在vue 2.3+ 如未定义的 props 属性会自动加到组件根元素上;
  • context.children: 包含 VNode 子节点的数组
  • context.slots: 返回 slots 对象的函数
  • context.data: 整个数据对象,作为 createElement 的第二个参数传递给组件
  • context.parent: 指向父组件
  • context.listeners: (2.3.0+) data.on 的别名,包含父级注册事件侦听器的对象。
  • context.injections: (2.3.0+) 包含组件的注入项

由于功能组件只是功能,缺少持久化实例,因此渲染的成本要低得多,也不会出现在 Vue devtools 组件树中;
适用于作为封装组件封装子节点/props/data 后传递给子组件;
功能组件也支持预处理/scope/热重载等。

静态资源 URL 是如何处理的

webpack 配置 module.exports 需要 vue-loader,同时也会有各种静态资源的 loader。当 Vue Loader 编译 SFC 中的<template>时,将会把所有关联的静态资源 url 转为 webpack module 请求。例如:

<img src="../image.png">

会编译为:

createElement('img', {
  attrs: {
    src: require('../image.png') // this is now a module request
  }
})
URL 路径解析规则:
  1. 绝对路径原样保存;
  2. 以“.”开头,则将其解释为相对模块请求,并根据文件系统上的文件夹结构解析;
  3. 以“~”开头,则将其后的内容解析为模块请求,可以在节点模块引用这些内容;
  4. 以“@”开头,则其后的内容也被解释为模块请求,@在 vue-cli 创建的项目中默认指向/ src,可以使用 webpack 配置 @ 别名
官网API: https://vue-loader.vuejs.org/zh/

继续加油哦~ 少年!
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值