本文使用的Vue版本:2.6.10

Vue为我们提供了很多高级特性,学习和掌握它们有助于提高你的代码水平。

一、watch进阶

从我们刚开始学习Vue的时候,对于侦听属性,都是简单地如下面一般使用:

watch:{
a(){
//doSomething
}
}

实际上,Vue对watch提供了很多进阶用法。

handler函数

以对象和handler函数的方式来定义一个监听属性,handler就是处理监听变动时的函数:

watch:{
a:{
handler:'doSomething'
}
},
methods:{
doSomething(){
//当 a 发生变化的时候,做些处理
}
}

handler有啥用?是多此一举么?用途主要有两点:

  • 将处理逻辑抽象出去了,以method的方式被复用
  • 给定义下面两个重要属性留出了编写位置

deep属性

不知道你注意到了没有?

当watch的是一个Object类型的数据,如果这个对象内部的某个值发生了改变,并不会触发watch动作!

也就是说,watch默认情况下,不监测内部嵌套数据的变动。但是很多情况下,我们是需要监测的!

为解决这一问题,就要使用deep属性:

watch:{
obj:{
handler:'doSomething',
deep:true
}
},
methods:{
doSomething(){
//当 obj 发生变化的时候,做些处理
}
}

deep属性默认为false,也就是我们常用的watch模式。

immediate属性

watchhandler函数通常情况下只有在监听的属性发生改变时才会触发。

但有些时候,我们希望在组件创建后,或者说watch被声明和绑定的时候,立刻执行一次handler函数,这就需要使用immediate属性了,它默认为false,改为true后,就会立刻执行handler。

watch:{
obj:{
handler:'doSomething',
deep:true,
immediate:true
}
},
methods:{
doSomething(){
//当 obj 发生变化的时候,做些处理
}
}

同时执行多个方法

使用数组可以设置多项,形式包括字符串、函数、对象

  watch: {
// 你可以传入回调数组,它们会被逐一调用
a: [ 'handle1', function handle2 (val, oldVal) { /* ... */ }, {
handler: function handle3 (val, oldVal) { /* ... */ },
/* ... */
} ], }

二、$event的不同表现

$event 是事件对象的特殊变量,在两种场景下,它有不同的意义,代表不同的对象。

  • 在原生事件中表示事件本身。可以通过$event.target获得事件所在的DOM对象,再通过value进一步获取具体的值。
<template>
<div>
<input type="text" @input="inputHandler('hello', $event)" />
</div>
</template> export default {
methods: {
inputHandler(msg, e) {
console.log(e.target.value)
}
}
}
  • 而在父子组件通过自定义事件进行通信时,表示从子组件中传递出来的参数值

看下面的例子:

//blog-post组件的模板

<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>

在父级组件监听这个事件的时候,可以通过 $event 访问到blog-post子组件传递出来的0.1这个值:

<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>

此时,$event的值就是0.1,而不是前面的事件对象。

三、异步更新队列

  • Vue 在更新 DOM 时是异步执行的。
  • 只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
  • 如果同一个 watcher 被多次触发,只会被推入到队列中一次。

这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserversetImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。

多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。

虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)

这样回调函数将在 DOM 更新完成后被调用。例如:

<div id="example">{{message}}</div>
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})

在组件内使用 vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:

Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function () {
return {
message: '未更新'
}
},
methods: {
updateMessage: function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '已更新'
})
}
}
})

因为 $nextTick() 返回一个 Promise 对象,所以你可以使用新的 ES2017 async/await 语法完成相同的事情:

methods: {
updateMessage: async function () {
this.message = '已更新'
//在这里可以看出,message并没有立刻被执行
//要理解页面刷新和代码执行速度的差别
//通常我们在页面上立刻就能看到结果,那是因为一轮队列执行其实很快,感觉不出DOM刷新的过程和所耗费的时间
//但对于代码的执行,属于即刻级别,DOM没更新就是没更新,就是会有问题
console.log(this.$el.textContent) // => '未更新' await this.$nextTick()
console.log(this.$el.textContent) // => '已更新'
}
}

通俗的解释

  • Vue的DOM刷新机制是个异步队列,并不是你想象中的立刻、马上、即时更新!

  • 这个异步队列是一轮一轮的执行并刷新

  • 上面带来的问题是,一些依赖DOM更新完毕才能进行的操作(比如对新增加的DOM元素进行事件绑定),无法立刻执行,必须等待一轮队列执行完毕

  • 最容易碰到上面问题的地方:created生命周期钩子函数中对DOM进行操作

  • 解决办法:使用this.nextTick(回调函数)方法,将对DOM的操作作为它的回调函数使用。

四、函数式组件

因为传统编写模板的能力不足,我们引入了渲染函数createElement。我们又希望获得更多的灵活度,于是引入了JSX。最后,我们发现有些简单的模板可以更简单更小巧的实现,于是引入了函数式组件。Vue总是试图为每一种场景提供不同的能力。

有这么一类组件,它的特点是:

  • 比较简单
  • 没有管理任何状态,也就是说无状态,没有响应式数据
  • 没有监听任何传递给它的状态
  • 没有写生命周期方法
  • 本质上只是一个接收一些prop的函数
  • 没有实例,没有this上下文

那么这个组件可以定义为函数式组件。与普通组件相比,函数式组件是无状态的,无法实例化,没有任何的生命周期和方法,适合只依赖于外部数据的变化而变化的组件,因其轻量,渲染性能会有所提高。

创建函数式组件

  • 以定义全局组件的方式
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
})

注意其中的functional: true,

在 Vue 2.3.0 或以上的版本中,你可以省略 props 选项,所有组件上的 attribute 都会被自动隐式解析为 prop。

当使用函数式组件时,该引用将会是 HTMLElement,因为他们是无状态的也是无实例的。

  • 对于单文件组件,创建函数式组件的方式是在模板标签内,添加functional属性
<template functional>
...
</template> <script>
...
</script> <style>
...
</style>

最重要的context参数

因为无状态,没有this上下文,所以函数式组件需要的一切都是通过 context 参数来传递,它是一个包括如下字段的对象:

  • props:提供所有 prop 的对象
  • children:VNode 子节点的数组
  • slots:一个函数,返回了包含所有插槽的对象
  • scopedSlots:(2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
  • data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
  • parent:对父组件的引用
  • listeners:(2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
  • injections:(2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的 property。

应用场景

函数式组件的一个典型应用场景是作为包装组件,比如当你碰到下面需求时:

  • 程序化地在多个组件中选择一个来代为渲染;
  • 在将 childrenpropsdata 传递给子组件之前操作它们。

下面是一个 smart-list 组件的例子,它能根据传入 prop 的值来代为渲染更具体的组件:

var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ } Vue.component('smart-list', {
functional: true,
props: {
items: {
type: Array,
required: true
},
isOrdered: Boolean
},
render: function (createElement, context) {
function appropriateListComponent () {
var items = context.props.items if (items.length === 0) return EmptyList
if (typeof items[0] === 'object') return TableList
if (context.props.isOrdered) return OrderedList return UnorderedList
} return createElement(
appropriateListComponent(),
context.data,
context.children
)
}
})

五、监听子组件的生命周期

假如我们有父组件Parent和子组件Child,如果在父组件中需要监听子组件的mounted这个生命周期函数,并做一些逻辑处理,常规写法可能如下:

// Parent.vue
<Child @mounted="doSth" /> //Child.vue
mounted(){
this.$emit('mounted');
}

但是,Vue给我们提供了一种更简便的方法,子组件无需做任何处理,只需要在父组件引用子组件时使用@hook事件来监听即可,代码如下:

// Parent.vue

<Child @hook:mounted="doSth" />  

methods:{
doSth(){
//some codes here
}
}

核心是@hook:mounted="doSth"的写法!

当然这里不仅仅可以监听mounted,其他生命周期都可以监听,例如created、updated等。

六、样式穿透

我们知道,在单文件组件的style中使用 scoped 属性后,父组件的样式将不会渗透到子组件中。

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

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

<style scoped>
.a >>> .b { /* ... */ }
</style>

上述代码将会编译成:

.a[data-v-f3f3eg9] .b { /* ... */ }

但是,有些像 Sass 之类的预处理器无法正确解析 >>>。这种情况下你可以使用 /deep/::v-deep 操作符,这两者都是 >>> 的别名,实现同样的功能。

我们都知道,通过 v-html 创建的 DOM 内容不受 scoped 样式影响,可以通过深度作用选择器>>>来为他们设置样式。

七、路由的props属性

一般在组件内使用路由参数,大多数人会这样做:

export default {
methods: {
getParamsId() {
return this.$route.params.id
}
}
}

当你随便用用,临时凑手,这没什么问题,毕竟解决了需求。

可我们要随时谨记:组件是用来复用的!组件应该有高度的封闭性!

在组件中使用 $route 会使它与路由系统形成高度耦合,从而使组件只能在使用了路由功能的项目内,或某些特定的 URL 上使用,限制了其灵活性。

试想一下,如果你的组件被人拿去复用了,但是那个人并没有使用路由系统,而是通过别的方式传递id参数,那么他该怎么办?

正确的做法是通过 props 解耦

首先,为组件定义一个叫做id的prop:

export default {
props: ['id'],
methods: {
getParamsId() {
return this.id
}
}
}

如果组件没有对应路由,那么这个id也可以通过父组件向子组件传值的方式使用。

如果使用了路由,可以通过路由的prop属性,传递id的值:

const router = new VueRouter({
routes: [{
path: '/user/:id',
component: User,
props: true
}]
})

将路由的 props 属性设置为 true 后,组件内可通过 props 接收到 params 参数

另外,你还可以通过函数模式来返回 props

const router = new VueRouter({
routes: [{
path: '/user/:id',
component: User,
props: (route) => ({
id: route.query.id
})
}]
})

其实,上面的技巧,在VueRouter的官档都有说明。

八、异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。

为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:

Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})

如你所见,这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。

你也可以调用 reject(reason) 来表示加载失败。这里的 setTimeout 是为了演示用的,如何获取组件取决于你自己。

一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:

Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})

你也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以写成这样:

Vue.component(
'async-webpack-example',
// 这个 `import` 函数会返回一个 `Promise` 对象。
() => import('./my-async-component')
)

当使用局部注册组件的时候,你也可以直接提供一个返回 Promise 的函数:

new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})

如果你想实现异步加载组件的功能,提高首屏显示速度,那么可以使用上面例子中的定义组件的方法,也就是:箭头函数+import语句!

处理加载状态

2.3.0+ 新增

异步组件的工厂函数也可以返回一个如下格式的对象,用来灵活定制异步加载过程:

const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})

注意如果你希望在 Vue Router 的路由组件中使用上述语法的话,必须使用 Vue Router 2.4.0+ 版本。

九、批量导入组件

很多时候我们会编写一些类似输入框或按钮之类的基础组件,它们是相对通用的组件,称为基础组件,它们会在更大一些的组件中被频繁的用到。

这很容易导致大的组件里有一个很长的导入基础组件的语句列表,例如:

import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'
//更多导入 export default {
components: {
BaseButton,
BaseIcon,
BaseInput
}
}

当你的基础组件很多的时候,这个过程将非常重复、麻烦和无聊。

require.context()

如果你恰好使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context 方法批量导入这些组件,然后将它们注册为全局组件,这样就可以在任何地方直接使用它们了,再也不用为导入的事情烦恼了!

下面是一个示例代码:

import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase' const requireComponent = require.context(
// 其组件目录的相对路径
'./components',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式
/Base[A-Z]\w+\.(vue|js)$/
) requireComponent.keys().forEach(fileName => {
// 获取组件的配置,也就是具体内容,具体定义,组件的本身代码
const componentConfig = requireComponent(fileName) // 获取组件的 PascalCase 命名,用来规范化组件名
const componentName = upperFirst(
camelCase(
// 获取和目录深度无关的文件名
fileName
.split('/')
.pop()
.replace(/\.\w+$/, '')
)
) // 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})

记住全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生

更多内容请访问: https://www.liujiangblog.com

更多视频教程请访问: https://www.liujiangblog.com/video/

你应该知道的Vue高级特性的更多相关文章

  1. 你应该要知道的vue.js

    前言 小组同事最近都在学习前端,目前我们小组前端技术栈主要是vue.在和同事交流过程成,发现他们对vue都不了解,所以整理了问的比较多的问题. 组件data为什么必须是函数? 因为组件可能被多处使用, ...

  2. 升级到WP8必需知道的13个特性

    http://www.cnblogs.com/sonic1abc/archive/2012/11/28/2792467.html   Windows phone 8 SDK 已经发布一段时间了, 已经 ...

  3. Git / 程序员需要知道的12个Git高级命令

    众所周知,Git目前已经是分布式版本控制领域的翘楚,围绕着Git形成了完整的生态圈.学习Git,首先当然是学习Git的基本工作流.相比于SVN等传统版本控制系统来说,Git是专为分布式版本控制而生的强 ...

  4. 开发者所需要知道的iOS7 SDK新特性

    iOS 7 春风又绿加州岸,物是人非又一年.WWDC 2013 keynote落下帷幕,新的iOS开发旅程也由此开启.在iOS7界面重大变革的背后,开发者们需要知道的又有哪些呢.同去年一样,我会先简单 ...

  5. 程序员必须要知道的Hadoop的一些事实

    程序员必须要知道的Hadoop的一些事实.现如今,Apache Hadoop已经无人不知无人不晓.当年雅虎搜索工程师Doug Cutting开发出这个用以创建分布式计算机环境的开源软...... 1: ...

  6. 转:你需要知道的NoSQL数据库10件事

    你需要知道的NoSQL数据库10件事 NoSQL的5个优势 1.弹性扩展 多年来,数据库管理员一直依赖于向上扩展(scale up)-随着数据库负载的增加购买更大的数据库服务器―而不是向外扩展-随着负 ...

  7. 面试必须要知道的SQL语法,语句

    面试必须要知道的SQL语法,语句 收藏 asc 按升序排列desc 按降序排列下列语句部分是Mssql语句,不可以在access中使用.SQL分类: DDL—数据定义语言(Create,Alter,D ...

  8. C#开发人员应该知道的13件事情

    本文讲述了C#开发人员应该了解到的13件事情,希望对C#开发人员有所帮助. 1. 开发过程 开发过程是错误和缺陷开始的地方.使用工具可以帮助你在发布之后,解决掉一些问题. 编码标准 遵照编码标准可以编 ...

  9. 关于Jmeter3.0,你必须要知道的5点变化

    2016.5.18日,Apache 发布了jmeter 3.0版本,本人第一时间上去查看并下载使用了,然后群里或同事都会问有什么样变化呢?正好在网上看到一遍关于3.0的文章,但是是英文的.这里翻译一下 ...

随机推荐

  1. alg-查找只出现一次的数

    //只有2个数出现1次,其余的数都出现2次 class Solution { public: vector<int> singleNumber(const vector<int> ...

  2. tf.train.AdamOptimizer 优化器

    adaptive moment estimation(自适应矩估计) tf.train.AdamOptimizer( learning_rate=0.001, beta1=0.9, beta2=0.9 ...

  3. Arthas-Java的线上问题定位工具

    Arthas(阿尔萨斯) 能为你做什么? Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱. 当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决: 这个类从哪个 jar ...

  4. 【Server】Windows系统安装Tomcat服务器

    安装Tomcat服务器 Tomcat服务器地址:https://tomcat.apache.org/download-80.cgi 当前版本点选8以上版本,最新的可能不稳定,所以选8或者9版本 直接解 ...

  5. CodeForces - 876B H - 差异的可分割性

    现在有n个整数,在这n个数中找出k个数,保证这k个数中任意两个数差的绝对值可以被m整除. Input第一行输入三个整数n,k,m(2<=k<=n<=100000,1<=m< ...

  6. Candy Distribution

    Kids like candies, so much that they start beating each other if the candies are not fairly distribu ...

  7. Joomla 3.4.6 RCE 分析

    Joomla 3.4.6 RCE 漏洞分析,首发先知社区: https://xz.aliyun.com/t/6522 漏洞环境及利用 Joomla 3.4.6 : https://downloads. ...

  8. 【题解】P1972 [SDOI2009]HH的项链 - 树状数组

    P1972 [SDOI2009]HH的项链 声明:本博客所有题解都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。 题目描述 \(HH\) 有一串由各种 ...

  9. 你知道什么是 GitHub Action 么?

    本文是 GitHub Action 的入门教程,如您已有相关使用经验可以直接关掉. GitHub Action 是 GitHub 于 2018 年 10 月推出的一个 CI\CD 服务. 之前一直都是 ...

  10. 通用Mapper使用

    通用Mapper介绍 产生背景 使用Mybatis的开发者大多会因为繁多的XML映射配置而头痛不已