目录
前言
Vue.js(读音 /vjuː/, 类似于 view)是一个构建数据驱动的 web 界面的库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。
Vue.js 自身不是一个全能框架——它只聚焦于视图层。因此它非常容易学习,非常容易与其它库或已有项目整合。另一方面,在与相关工具和支持库一起使用时,Vue.js 也能完美地驱动复杂的单页应用。
1、响应的数据绑定
Vue.js 的核心是一个响应的数据绑定系统,它让数据与 DOM 保持同步非常简单。在使用 jQuery 手工操作 DOM 时,我们的代码常常是命令式的、重复的与易错的。Vue.js 拥抱数据驱动的视图概念。通俗地讲,它意味着我们在普通 HTML 模板中使用特殊的语法将 DOM “绑定”到底层数据。一旦创建了绑定,DOM 将与数据保持同步。每当修改了数据,DOM 便相应地更新。这样我们应用中的逻辑就几乎都是直接修改数据了,不必与 DOM 更新搅在一起。这让我们的代码更容易撰写、理解与维护。
2、组件系统
组件系统是 Vue.js 另一个重要概念,因为它提供了一种抽象,让我们可以用独立可复用的小组件来构建大型应用。如果我们考虑到这点,几乎任意类型的应用的界面都可以抽象为一个组件树:
实际上,一个典型的用 Vue.js 构建的大型应用将形成一个组件树。
你可能已经注意到 Vue.js 组件非常类似于自定义元素——它是 Web 组件规范的一部分。实际上 Vue.js 的组件语法参考了该规范。例如 Vue 组件实现了 Slot API 与 is 特性。但是,有几个关键的不同:
Web 组件规范仍然远未完成,并且没有浏览器实现。相比之下,Vue.js 组件不需要任何补丁,并且在所有支持的浏览器(IE9 及更高版本)之下表现一致。必要时,Vue.js 组件也可以放在原生自定义元素之内。
Vue.js 组件提供了原生自定义元素所不具备的一些重要功能,比如组件间的数据流,自定义事件系统,以及动态的、带特效的组件替换。
组件系统是用 Vue.js 构建大型应用的基础。另外,Vue.js 生态系统也提供了高级工具与多种支持库,它们和 Vue.js 一起构成了一个更加“框架”性的系统。
这里简单介绍下vue最常用也较重要的两块:响应式原理和组件系统。
2.1响应式原理
如何追踪到到变化
Vue.js采用的则是基于依赖收集的观测机制。从原理上来说,和老牌MVVM框架Knockout是一样的。依赖收集的基本原理是:
1.将原生的数据改造成 “可观察对象”。一个可观察对象可以被取值,也可以被赋值。
2.在watcher的求值过程中,每一个被取值的可观察对象都会将当前的watcher注册为自己的一个订阅者,并成为当前watcher的一个依赖。
3.当一个被依赖的可观察对象被赋值时,它会通知所有订阅自己的watcher重新求值,并触发相应的更新。
4.依赖收集的优点在于可以精确、主动地追踪数据的变化,不存在上述提到的脏检查的两个问题。但传统的依赖收集实现,比如Knockout,通常需要包裹原生数据来制造可观察对象,在取值和赋值时需要采用函数调用的形式,在进行数据操作时写法繁琐,不够直观;同时,对复杂嵌套结构的对象支持也不理想。
Vue.js利用了ES5的Object.defineProperty方法,直接将原生数据对象的属性改造为getter和setter(这是ES5的特性,需要js解释引擎的支持,无法通过各种打shim补丁来实现,至少现在的babel pollyfill和各种shim是没法的。这也是为什么Vue不支持IE8及以下版本的原因,因为IE8的defineProperty只支持DOM对象,纯Object会报错。),在这两个函数内部实现依赖的收集和触发,而且完美支持嵌套的对象结构。对于数组,则通过包裹数组的可变方法(比如push)来监听数组的变化。这使得操作Vue.js的数据和操作原生对象几乎没有差别[注:在添加/删除属性,或是修改数组特定位置元素时,需要调用特定的函数,如obj.$add(key, value)才能触发更新。这是受ES5的语言特性所限。在操作对象类型数据的时候一定要注意这点,否则无法实现响应。
变化检测问题
受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。例如:
|
对于 Vue 实例,可以使用 $set(key, value) 实例方法:
|
对于普通数据对象,可以使用全局方法 Vue.set(object, key, value):
|
有时你想向已有对象上添加一些属性,例如使用 Object.assign() 或 _.extend() 添加属性。但是,添加到对象上的新属性不会触发更新。这时可以创建一个新的对象,包含原对象的属性和新的属性:
|
异步更新队列
可能还没有注意到,Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际(已去重的)工作。Vue 在内部尝试对异步队列使用原生的 Promise.then
和 MutationObserver
,如果执行环境不支持,会采用 setTimeout(fn, 0)
代替。
例如,当设置 vm.someData = 'new value'
,该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback)
。这样回调函数在 DOM 更新完成后就会调用。例如:
|
在组件内使用 vm.$nextTick()
实例方法特别方便,因为它不需要全局 Vue
,并且回调函数中的 this
将自动绑定到当前的 Vue 实例上
计算属性的奥秘
你应该注意到 Vue.js 的计算属性不是简单的 getter。计算属性持续追踪它的响应依赖。在计算一个计算属性时,Vue.js 更新它的依赖列表并缓存结果,只有当其中一个依赖发生了变化,缓存的结果才无效。因此,只要依赖不发生变化,访问计算属性会直接返回缓存的结果,而不是调用 getter。
为什么要缓存呢?假设我们有一个高耗计算属性 A,它要遍历一个巨型数组并做大量的计算。然后,可能有其它的计算属性依赖 A。如果没有缓存,我们将调用 A 的 getter 许多次,超过必要次数。
由于计算属性被缓存了,在访问它时 getter 不总是被调用。考虑下例:
|
计算属性 example 只有一个依赖: vm.msg 。 Date.now() 不是 响应依赖,因为它跟 Vue 的数据观察系统无关。因而,在访问 vm.example 时将发现时间戳不变,除非 vm.msg 变了。
有时希望 getter 不改变原有的行为,每次访问 vm.example 时都调用 getter。这时可以为指定的计算属性关闭缓存:
|
现在每次访问 vm.example 时,时间戳都是新的。但是,只是在 JavaScript 中访问是这样的;数据绑定仍是依赖驱动的。如果在模块中这样绑定计算属性 {{example}} ,只有响应依赖发生变化时才更新DOM。
计算属性不可在data里面定义,如果定义会报错:变量不可被重复定义和赋值; 应用场景:一个数据受多个数据影响
侦听器watch
首先确认 watch是一个对象,一定要当成对象来用。
对象就有键,有值。
键:就是你要监控的那个家伙,比如说$route,这个就是要监控路由的变化。或者是data中的某个变量。
值可以是函数:就是当你监控的家伙变化时,需要执行的函数,这个函数有两个形参,第一个是当前值,第二个是变化后的值。
值也可以是函数名:不过这个函数名要用单引号来包裹。
第三种情况厉害了。
值是包括选项的对象:选项包括有三个:
-
handler:其值是一个回调函数。即监听到变化时应该执行的函数。
-
deep:其值是true或false;确认是否深入监听。(一般监听时是不能监听到对象属性值的变化的,数组的值变化不需要使用deep)
-
immediate:其值是true或false;确认是否以当前的初始值执行handler的函数。
var vm =
new
Vue({
data: {
a:
1
,
b:
2
},
watch: {
a: function (val, oldVal) {
console.log(
'new: %s, old: %s'
, val, oldVal)
},
// 方法名
b:
'someMethod'
,
// 选项的对象
c: {
handler: function (val, oldVal) {
/* ... */
},
deep:
true
,
immediate:
true
}
}
})
data里面需要定义;应用场景:一个数据影响多个数据
2.1组件系统
组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。
1、导入组件
|
现在 ComponentA
和 ComponentC
都可以在 ComponentB
的模板中使用了。
2、props
组件A 使用组件B ,那么A组件和B组件的关系就是父子关系,父组件通过props向子组件传递数据
|
3、props类型
|
4、props验证
我们可以为组件的 prop 指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤其有帮助。
为了定制 prop 的验证方式,你可以为 props
中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:
|
5、.sync修饰符
在说vue 修饰符sync前,我们先看下官方文档:vue .sync 修饰符,里面说vue .sync 修饰符以前存在于vue1.0版本里,但是在在 2.0 中移除了 .sync 。但是在 2.0 发布之后的实际应用中,我们发现 .sync 还是有其适用之处,比如在开发可复用的组件库时。我们需要做的只是让子组件改变父组件状态的代码更容易被区分。从 2.3.0 起我们重新引入了 .sync 修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。
示例代码如下:
|
当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:
|
猛一看不明白,下边我么通过一个实例(弹窗的关闭事件)来说明这个代码到底是怎么运用的。
|
vue 修饰符sync的功能是:当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定。如果我们不用.sync,我们想做上面的那个弹窗功能,我们也可以props传初始值,然后事件监听,实现起来也不算复杂。这里用sync实现,只是给大家提供一个思路,让其明白他的实现原理,可能有其它复杂的功能适用sync。
这只是一个简单的例子,看完这个不知你是不觉得有个指令跟这个很相似,v-model?对,就是v-model在组件上使用的时候。
具体的可以查看:vue 自定义组件使用v-model,可以实现同样的功能。也是以这个弹窗事件做演示的
6、父子组件/兄弟组件的通信
父子组件:由于prop是父组件向子组件传递的单项数据流,所以子组件想要更改父组件的值,可以用到下面的方法
-
使用 $on() 监听事件;
-
使用 $emit() 在它上面触发事件;
兄弟组件:可以使用数据总线来传递数据,具体方法是先实例化一个空的vue对象,如下:
|
使用的时候在相应的组件里:import bus from 'xxx',
- 使用bus.$on监听事件
- 使用bus.$emit在它上面出发事件
7、动态组件
多个组件可以使用同一个挂载点,然后动态地在它们之间切换。使用保留的 <component> 元素,动态地绑定到它的 is 特性,is绑定的是组件导入进来的名字
|
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:
|
应用场景:1、组件动态切换;2、有些 HTML 元素,诸如 <ul>、<ol>、<table> 和 <select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>、<tr> 和 <option>,只能出现在其它某些特定的元素内部。
3、生命周期
可以看到在vue一整个的生命周期中会有很多钩子函数提供给我们在vue生命周期不同的时刻进行操作, 下面是所有生命周期钩子函数及应用场景:
生命周期钩子 | 组件状态 | 最佳实践 |
---|---|---|
beforeCreate | 实例初始化之后,this指向创建的实例,不能访问到data、computed、watch、methods上的方法和数据 | 常用于初始化非响应式变量 |
created | 实例创建完成,可访问data、computed、watch、methods上的方法和数据,未挂载到DOM,不能访问到$el属性,$ref属性内容为空数组 | 常用于简单的ajax请求,页面的初始化 |
beforeMount | 在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数 | - |
mounted | 实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问 | 常用于获取VNode信息和操作,ajax请求 |
beforeupdate | 响应式数据更新时调用,发生在虚拟DOM打补丁之前 | 适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器 |
updated | 虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作 | 避免在这个钩子函数中操作数据,可能陷入死循环 |
beforeDestroy | 实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例 | 常用于销毁定时器、解绑全局事件、销毁插件对象等操作 |
destroyed | 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 | - |
4、vuex
在大型应用中,状态管理常常变得复杂,因为状态分散在许多组件内,在不同的作用域内。以vue来说,当使用vue-router以及组件化开发(.vue)来构建大型单页应用的时候,组件之间状态的数据的传递会很困难,虽然props、emit等能够进行跨组件的数据传递,但是大量使用它们会使组件之间的耦合程度很高,组件越多,层级越多,维护起来就越复杂。怎么办呢?能否在全局提供一个状态管理构架?
什么是Vuex?
官方说法:Vuex 是一个专为 Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
个人理解:Vuex是用来管理组件之间通信的一个插件
vuex 包含有五个基本的对象:
- state:存储状态。也就是变量;
- getters:派生状态。也就是set、get中的get,有两个可选参数:state、getters分别可以获取state中的变量和其他的getters。外部调用方式:store.getters.personInfo()。就和vue的computed差不多;
- mutations:提交状态修改。也就是set、get中的set,这是vuex中唯一修改state的方式,但不支持异步操作。第一个参数默认是state。外部调用方式:store.commit('SET_AGE', 18)。和vue中的methods类似。
- actions:和mutations类似。不过actions支持异步操作。第一个参数默认是和store具有相同参数属性的对象。外部调用方式:store.dispatch('nameAsyn')。
- modules:store的子模块,内容就相当于是store的一个实例。调用方式和前面介绍的相似,只是要加上当前子模块名,如:store.a.getters.xxx()。
附件
vue组件官方文档 :https://cn.vuejs.org/v2/guide/components-registration.html
vuex官方文档 : https://vuex.vuejs.org/zh/guide/state.html
vuex谷歌插件:Vue.js devtools
vue-demo : https://github.com/PanJiaChen/vue-element-admin.git