Vue.js
是一个渐进式框架,意味着你可以把 Vue 作为你应用的一部分嵌入到其中。通俗点举例,比如公司正在用 jQuery,但这时候需要项目重构,你可以从新项目开始使用 Vue,然后再一点一点地替换掉其他的项目,一点点地替换,就是渐进。
基本使用
(注意,操作与指令都必须在 Vue 挂载的元素内),全文代码基本是伪代码
插值表达式 mustache
mustache 翻译过来就是胡子, 为什么 {{}}
叫 mustache, 看这个图 :{
就懂了吧。
用 可以获取到 Vue 实例的 data 属性中的 msg 值,只能在标签内使用,如
<div></div>
当 data 中的 msg 值被改变时,对应的 渲染出来的值也会发生改变,即数据是响应式的。
{{}}
内部可以写表达式,如三元表达式,但不能写语句,如 if 、 for 语句
题外: 与 mustache 命名类似的还有 toast(吐司),它经常在各种 UI 库中见到,它表示轻弹窗,就像烤面包时候的烟一样,冒出来一会儿就消失,所以得名 toast。
基本指令
v-once
v-once
元素只在第一次加载的时候去获取 data 中的数据,如 <div v-once></div>
只会在第一次渲染的时候把 data.msg 的值渲染到页面上,之后再修改 data.msg 是不会发生改变的。
v-for
v-for
列表渲染指令,迭代 data 中的值,如
1 | <ul> |
这个操作会把 data.list 遍历一次,可以得到值和它对应的索引,之后再这个标签内部其他属性都可以访问到这两个变量。使用这个指令之后,一定要记得加上:key="index"
key 的值应该是唯一的,不应该是 index,这样可能会使 diff 优化失效,这里只是演示一下,可以绑定数据请求之后返回的 id, 它一般都是唯一且不变的,在整个生命周期中都保持稳定。
有 key 在内存在实际就是链表了,所以插入效率变高,key 的作用主要是为了高效的更新 DOM.
注意,重复的 key 将会使 Vue 出现警告, 可能会错误渲染 DOM。
v-html
v-html
类似于 innerHTML,但是绑定在标签属性上,如
1 | <!-- html --> |
v-text
v-text
基本与 mustache 一致,但是它会覆盖标签内的全部内容,如 <div v-text="msg">被覆盖的内容</div>
div 标签内只会显示 data.msg 的值
v-pre
v-pre
原封不动地显示标签内的内容,不做任何解析,无视插值表达式,其实与 <pre></pre>
标签有点相似,如 <div v-pre></div>
,div 内将会显示 ,不做任何解析
v-cloak
v-cloak
在 Vue 实例未挂载到页面上时,元素上会有 v-cloak 属性,当 Vue 渲染好了之后这个属性就会被删除。常用于解决请求时间过长而导致的闪现问题,某些情况下,页面会直接显示未编译的 Mustache 标签(如 用户可能看到 ,然后才会变成 data.msg )。实际上只是为元素指定了一个属性名,然后可以在 css 中选中这个元素,在页面渲染完成的时候删除这个属性。 cloak (斗篷)
1 | <!-- css --> |
这样设置之后,用户就不会再网络请求速度较慢的情况下,看到 一闪而过的画面了。
v-bind
v-bind
相当于属性的 mustache ,例如,在标签的属性中,我们不能直接写 <img src=""></img>
,而是要写成 <img v-bind:src="imgUrl"></img>
,这样就会去 data 里找到 imgUrl 这个变量并将值赋给 img 的 src 属性。同时,如果修改了 imgUrl 的值,也会同步反应到 src 属性上。 相当于在 Vue 实例上做了一个中转。 简写为 :
,同样,双引号内能写表达式,如三元表达式,但不能写语句,如 if 、 for 语句。注意,被v-bind 绑定的 ""
内的内容,是一个表达式,里面的不带 ''
的值都不再是字符串了。如 <div :class="[class1, class2]"></div>
,class1 和 class2 都是变量,会去 data 中找到之后传入数组,数组中的值都会被传到 class 上。但如果加上 ''
就会变成字符串。如 :class=['class1',class]"
下面是用 v-bind 通过对象的方式动态绑定 class 的例子
1 | <!-- html --> |
根据 data 中 isActive 的布尔值决定是否有 active 的类名, 根据 isLine 的布尔值决定是否有 line 的类名,而原本的 class 不会被覆盖,上面将会显示 <div class="title active"></div>
,而此时如果想删除原本的 title,就会比较困难,还是要获取 DOM,所以,以后把不会修改的 class 可以定死,而可能需要修改的 class 用 v-bind 绑定。如果 class 过于复杂,可以放到计算属性中,仍然用 v-bind 绑定。
如果 class 过于复杂,可以放到 computed (推荐) 或 methods 中,仍然用 v-bind 绑定。但这时候注意要写 this,同时注意 computed 不需要加 (),并且不要把名字写的像个函数比如带个 getXXXByXXX,而 methods 需要 ()
1 | <!-- html --> |
为了看得更清楚,再举个简单的例子 v-bind 绑定 style,以对象的形式,以后会经常这样写,当然,如果写的过于复杂,可以放到计算属性中,返回一个对象即可。
1 | <!-- html --> |
可以看到 font-size 值与 data 中的 yourSize 绑定,注意驼峰命名,而 width 是 500px,注意单引号内的内容才表示字符串,表示写死。如果 500px 不加 ''
,将会被当作变量去解析,然后 data 中找不到 50px 这个变量就会报错,而且对象的属性也不能以数字开头。
初始值的定义
如果 v-bind 一个对象(对象 a)中的对象(对象 b)的某个属性(属性 c),并且对象 a 的初始值只是定义了 {},对象 b 是异步获取的情况下,需要把对象 b 定义一下。同理,如果有一个嵌套数组,也应该定义一下初始值,如 arr[0][0]
,arr[0] 是一个数组,如果没定义,那么它将返回 undefined,undefined[0] 将会报错。如果在 data 中某个值将会被异步赋值成一个函数,并在页面中调用,也需要注意这个问题,应该初始化成一个函数。
其他时候,并不需要定义初始值。注意引用类型的初始值即可。
1 | <template> |
v-show
v-show
决定元素是否显示在页面上,当值为 false 时,仅仅是将元素行内样式的 display 属性设置为 none,从而控制显示与否。切换频繁时用该指令控制。
v-if
v-if
是条件渲染,按 v-if 绑定的值为 true 或 false 来决定是否渲染在页面中, 当值为 false 时,将不会有该元素存在于 DOM 中,这也是与 v-show 的区别,如果没有出现在 DOM 中,它的 ref 属性是无效的,无法在 $ref 中找到元素。只有少数切换时用该指令控制。
如 <div v-if="isShow"></div>
data: { isShow: true }
由于 v-if 渲染的时候也会经过 diff 算法,所以可能 v-if 前后同位置的元素可能是同一个,而不是替换了新元素。例子如下
1 | <span v-if="isUser"> |
v-model
v-model
实现表单元素的数据双向绑定。例举比较少用但可能用到的, text、password 这些常见的不再举例。
本质是用 v-bind:value + v-on:input
实现
radio
1 | <!-- 正常原生写法,两个 radio 框的 name 需要相同才能让这两个 radio 只能选中一个,否则两个框都可以点 --> |
checkbox
1 | <input type="checkbox" value="多选框1" v-model="CB1"> |
select
1 | <!-- select 也分成单选和多选的情况 --> |
v-model 的拆解
v-model 实际是一个语法糖,可以拆解为 props: value 和 events: input。也就是说,只要某个组件能提供一个名为 value 的 prop,以及名为 input 的自定义事件,就能在该组件上使用 v-model。
vue2.2 之后,可以定制 v-model 指令的 prop 和 event 名称。
1 | <template> |
v-model 修饰符
lazy
由于默认情况下每次修改 input 的值,data 中的数据都会同步改变,改变频率非常高,我们希望只在 input 失焦或按下回车的时候才修改 data 中的值,就可以加上 lazy
修饰符,写法 <input type="text" v-model.lazy="lazyData">
number
由于 v-model 默认赋给 data 中绑定的数据的值为 String 类型,通过 number
修饰符,可以变为数字 Number 类型。实际上是判断输入框的值第一个字符是否为数值,如果是数值,就调用 parseFloat(val)
方法,把这个值传入,data 中数据为这个方法的返回值,如果第一个字符不为数值,则无事发生,返回正常的字符串,所以不能保证为 Number 类型,除非写在 type="number"
的 input 中,因为这种类型的 input 只允许输入数字,但它也会把绑定的值赋值为 String 类型。
text input 写法 <input type="text" v-model.number="numberData">
number input 写法 <input type="number" v-model.number="numberData">
trim
去掉要传入到 data 中的值首尾的空格
v-on
v-on
相当于事件绑定,可以绑定已经设定好的事件或者自定义事件,如 <button v-on:click="btnClick"></button>
简写为 @
当通过 methods 中定义方法,以供 @click 调用时,需要注意参数问题:
情况一,如果该方法不需要额外参数,那么方法之后的
()
可以不添加。但是如果方法本身中有参数,这种情况下会默认将原生事件 event 对象当作参数传递过去@click=clickHandle
methods:{ clickHandle(e){ /* 方法需要参数,但 @ 绑定的事件回调函数不带括号时,默认第一个参数接收到 event 对象 ... */ } }
情况二,如果需要传入某个参数,同时需要 event 时,可以通过 $event 传入事件对象。
@click="clickHandle(val, $event)"
methods:{ clickHandle(val, e){ /* ... */ } }
事件修饰符
stop
阻止冒泡 @click.stop="clickHandle"
相当于调用了 event.stopPropagation()
prevent
阻止默认事件,如 a 标签的默认点击跳转,相当于调用了 event.preventDefault()
.
1 | <form action="baidu"> |
enter
当按下 enter 键时才触发事件 @keyup.enter="enterHandle"
, 还有一种是写成键代码的形式@keyup.13="enterHandle"
native
监听组件的点击事件 <cpn @click.native="cpnClick"></cpn>
,<cpn>
是自定义的组件,直接写 @click
是无法触发组件的点击事件的
once
只触发一次事件之后就不触发了 @click.once
串联修饰符: 修饰符可以串起来用,如 @click.stop.prevent="clickHandle"
自定义指令
Vue.directive
在 Vue2.0
中,代码复用和抽象的主要形式是组件。有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
基本写法
1 | // 全局注册自定义指令 v-focus |
简单使用
1 | <div id="root"> |
可以利用它来实现图片懒加载指令,v-lazyload
,具体代码不展示了。
计算属性
computed
对象中的属性,都要写成函数形式,并且有一个返回值。而在页面中用 mustache 时,不需要像 methods 中定义的方法那样加上 ()
,就当作 data 中的值来用即可。计算属性与方法的区别是计算属性是有缓存的,而方法每次都需要重新执行。
它的本质是 set 和 get,如 fullName: { set(), get() }
,只是我们一般不定义 set(),所以默认指定 get()
下面是个简单的例子,写出了 computed 内属性的简写形式与完整形式
1 | <!-- html --> |
其实计算属性也能实现类似 methods 的效果,它可以返回一个函数。
1 | <div id="app"> |
过滤器
filter
Vue3.0 移除了,鸽了。
留坑
watch
watch
可以
响应式
Vue 内部重写了部分数组方法,将会监听数组数据的变化。从而达到响应式的目的。只有通过这些方法修改数组,数据才会是响应式的。 注意:直接修改对象的属性是响应式的,而直接修改数组的属性不是。准确地说,直接修改已在 data 中声明的对象属性是响应式的,而直接修改数组的属性或 data 中未声明的对象属性不是响应式的。
直接通过索引,如 this.arr[o] = 'no render'
这样通过索引值来修改数组中的元素,页面绑定的该值(arr[o])是不会发生改变的。如果想要响应式修改数据,可以这样写
1 | this.arr.splice(0, 1, 'render') |
push()
将元素添加到最后 可以 push 多个元素,如this.arr.push('aa', 'bb', 'cc')
pop()
删除最后一个元素shift()
删除第一个unshift()
将元素添加到最前面 同样可以 unshift 多个元素,如this.arr.unshift('aa', 'bb', 'cc')
,源码 lib.es5.d.ts (d 就是 define 定义的意思) 文件中是这样定义的unshift(...items: T[]):number
看到参数是...
,所以可以传多个参数。 push 同理。 T[] 是 ts 语法,泛型,先不用管这个。splice(start, num, ele, ele, ..., ele)
在指定位置开始,替换指定个数元素,变成新传入的元素。写成splice(start)
将会 删除索引为 start 之后的所有元素.sort()
reverse()
Vue.set(obj, key, newVal)
Vue.set(obj, key, newVal)
, 这是 Vue 官方提供的,与我们直接修改的区别就是,它是响应式的,因为通过这种方式添加或修改的数据上面会增加一个 Dep 对象,这个对象会检测值是否发生改变,如果发生改变,查找一个数组,这个数组的内容是页面中所有用到这个值的地方,类似这样 [Watcher, Watcher, … , Watcher],里面是一个个 Watcher 对象,一旦 Dep 监听的属性发生了变化,就会通知这些 Watcher,告诉它们要重新渲染了。
Vue.delete(obj, key)
Vue.delete(obj, key)
,同样也是 Vue 官方提供的,与我们直接使用 delete 删除对象的方法(如 delete this.obj.num
)的区别是,它是响应式的,如果我们直接 delete,页面中通过这个属性被渲染地方仍然存在,不会因为这个属性消失了而消失。
混入
在生命周期钩子中调用时,如果有同名方法,优先执行混入对象的方法再调用它自己的方法。
合并策略
如果在 methods 中有和混入对象同名的方法,默认执行组件自己的方法,而不是混入对象的方法。
可以对 Vue.config.optionMergeStrategies
对象进行配置自定义选项合并策略。详情见官方文档。
1 | Vue.config.optionMergeStrategies.myOptions = function (toVal, fromVal) { |
使用
慎用全局混入方法 Vue.mixin
,它将会使所有新建的 Vue 实例对象上都有 mixin
的内容,包括第三方模板。
1 | // common/mixin.js |
组件
组件化是 Vue.js 中的重要思想,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。当我们开始新项目的时候,可以直接使用之前封装好的组件来快速开发。任何的应用都会被抽象成一颗组件树。
组件使用步骤
- 创建组件构造器
Vue.extend()
传入一个对象,里面有 template 这个属性,包含了 html 模板字符串。 但这种方式已经非常少见了,一般使用语法糖(主要是省去了Vue.extend()
这一步,由 Vue 内部来执行) - 注册全局组件
Vue.component()
传入两个参数,组件名 和 组件构造器对象,如果要局部注册,则在 Vue 实例对象中的 components 属性 以'组件名': 组件构造器对象
形式传入要注册的组件。注意,组件名应该加上引号!否则将被识别为一个变量。当然,局部注册的时候,可以不加,因为是对象的属性名,但如果这个属性名里带有特殊字符如-
这种,也必须加引号。 - 使用组件
在 Vue 实例的作用范围内使用组件
基本使用
简单举例
1 | <!-- 假如我们的页面中有这样几个重复的部分 --> |
把上面的例子用父子组件来改写
1 | <div id="app"> |
语法糖形式
1 | <!-- 古老写法 全局注册 --> |
抽离 template 的写法
1 | <!-- html 的抽离写法 --> |
每个组件可以有自己的 data 和 methods, 而且这个 data 必须写成函数的形式,原因见下面的例子
1 | <div id="app"> |
父子组件传值(组件通信)
父传子
使用场景:页面加载进来时,可能会有很多请求,一般只在最外层组件中进行一次请求,之后把数据下发到各个子组件中,子组件再下发到更下层的组件,而不是每个子组件独立请求,这样服务器压力较大,而且性能也不好。
子组件通过 props 接收来自父组件传递的值,父组件用 v-bind:propname="ParentData"
来向子组件传递值,由于要用 v-bind 来绑定 props 中的变量名,而 v-bind 不支持驼峰,会默认转为小写,所以 props 中的变量名不要大写,如 props: ['aBc']
之后,html 中写成 <cp :aBc="pData">
,由于 html 会转小写,就变成了 :abc
因 此 aBc 无法接收到来自父组件的值,但如果写成 <cp :a-bc="pData">
这样是可以的,这时候使用这个值还是写 ,而不是
NaN
,这是无法识别的,它会将a 和 bc 当成两个不同的变量,中间用连字符隔开。所以 props 中的变量名就写小写。但是父组件传递来的值可以是大写的,如果还带有连字符,那么只能用 this 来获取了。如 <cp :abc="this['pDa-ta']">
主要用到 props 属性接收来自父组件的值,它是单词 properties 的简写,推荐写成对象形式而不是数组形式,props 如果验证不通过(type、validator), Vue 会报错,但不会中止运行
1 | <div id="app"> |
子传父
子组件使用 $emit('eventName', val)
来向父组件传值,父组件用 @eventName="eventHandle"
来接受子组件的值,捕获到子组件传值事件 eventName,并定义处理的回调函数 eventHandle 。 eventName 是自定义事件名。自定义事件名在非 vue-cli 的项目中不能写成大写,回调函数允许驼峰。
1 | <div id="app"> |
综合小例子
1 | <div id="app"> |
父子组件的访问方式(组件访问)$refs
$parent
子组件可以用 $parent 来获取父组件的引用,返回父组件的实例对象,可能是 Vue 实例, 也可能是 VueComponent 实例。由于可能同一个子组件会被注册在不同的父组件下,所以获取的 $parent
也会不一致,可能会出现一些问题,所以不太方便。比如在该组件的某个方法中,用到了 $parent.name
,可能这个组件的父组件有 name 属性,另一个组件的父组件没有这个属性,就出问题了。
$children
父组件可以通过 $children 来取得包含了所有子组件的引用的数组,由于每次获取的是数组,所以需要通过子组件在数组中的索引来取得子组件,而且索引又不太好确定,所以这个方法不太方便。
以上两种获取引用方式都不太常用。一般使用 $refs
$refs
给组件加上 ref="refName"
属性,之后通过 $refs.refName
来及获取指定的组件对象,这是最常用的方法。注意,不要用 $refs 做数据绑定和计算属性,因为 ref 本身是作为渲染结果拿到的,初始渲染时不能访问。此时再通过 $el
可以拿到组件的 DOM ,写法 $refs.refName.$el
。
当 $refs
绑定给一个 html 元素时,this.$refs.refName
获取的是该 DOM 元素,在 Vue 中,如果真的想用 DOM 元素,不应该使用原生的获取 DOM 的方法,而是通过这种方式获取,因为如果在组件外部有一个同名的(比如 class),通过原生方法获取,将会拿到外部元素,而不是真正想拿到的 DOM 元素,通过 this.$refs.refName
可以避免这一问题。
$root
获取根组件 Vue 实例,也不太用。
注意,在 created
生命周期函数中,拿不到 $refs
里的对象。必须要到 mounted
挂载 DOM 之后再获取 $refs
。
组件补充
.native
父级组件想对子组件绑定父组件的方法需要加上修饰符 .native
1 | <div id="app"> |
父组件向子组件传递处理函数
可以把父组件的处理函数直接传入子组件处理,这样就不用每次都让子组件 $emit
出事件让父组件监听并处理。
1 | <!-- 父组件 --> |
事件总线($bus)
new 一个 Vue 实例对象,并将它挂载到真正使用的 Vue 实例上,新 new 出来的实例对象充当中转站的作用,因为每个 Vue 实例对象上都有 $emit
和 $on
方法。
1 | // 挂载 |
需要注意的是,如果某个组件中使用了 $bus.$emit
,并且在主页监听 $bus.$on
这个事件。当在 App.vue
中用 <keep-alive>
包裹 <router-view>
,并切换到其他页面时,主页仍然被缓存,若此时这个页面中也引入了这个组件,并且触发了它的 $bus.$emit
,那么被 <keep-alive>
保留的主页中,仍然会用 $bus.$on
监听到 这个事件,并调用指定的回调函数。也就是说,在其他页面中的操作,可能会导致主页的修改,这一点需要注意。
所以解决方法可以是用 route, 判断是否为主页决定是否 $bus.$emit
1 | /* 某组件中 */ |
依赖注入(provide/inject)
inject
和 provide
是为了解决组件间通信过于复杂的问题, 比如 父组件向孙组件传递的时候, 需要传两层 prop, 再更多层次的时, 如果不用 Vuex 烦不胜烦, 但我们必然希望我们的组件有高复用性而不依赖于 Vuex, 通过这组属性, 可以直接向孙组件中注入数据, 但用的很少, 官方也并不推荐直接应用于程序代码。因为开发中必然使用 Vuex, 这个方法可能常见于 UI 库, 如 ELEMENT-UI.
provide/inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
它们要求的值类型应该是
provide
: 返回一个对象的函数或一个对象(就是data(){return {}}
这样的写法, 不推荐直接写一个对象, 除非传递不在当前实例上的静态数据,即不需要this.data
获取的数据)inject
: 一个字符串数组(数组里每项都是字符串, 并对应 provide 的名字),或 一个对象,对象的 key 是本地的绑定名
1 | <!-- 只演示一下父子组件, 展示一下功能 --> |
组件传值注意点
组件传值是异步的,并且子组件渲染 DOM 可能比父组件慢,所以有时候需要在父组件中设置延迟。
比如,封装轮播图的时候,可能 SwiperItem
的 DOM 还未生成,而 Swiper
已经生成 DOM,并进入 mounted
阶段,但此时无法获取到 SwiperItem
这个组件的元素,也就无法确定数量,因此会导致轮播小点的数量为 0,无法加载。
或者在子组件 DOM 渲染完毕时,给父组件一个标志,让它去执行初始化。
插槽
基本使用
匿名插槽
当插槽不指定 name 属性的时候,是匿名插槽,如果组件标签没有包裹 DOM 内容的话,默认自动显示 <slot>中间包裹的值</slot>
中间包裹的内容,如果 slot 标签中间没有内容,那就不显示。如果在组件标签中包裹了 DOM 内容,那么<slot></slot>
就显示包裹的 DOM 内容,它自己中间包裹的内容不再显示。相当于把 <slot>
标签所在的位置替换为传入的 DOM 内容。插槽也是会被 整个替换 的内容,就像 <div id="app" />
被 App
组件整个替换一样,所以不要在插槽上写 什么属性(如 class)或是什么指令(如 v-if ,如果想要对它使用 v-if 或是 @click,在它外面包裹一层 div 标签,对这个 div 标签使用 v-if 或 @click 吧)。
注意,可以写多个匿名插槽,只是组件标签中传入的 DOM 内容会被反应在所有的匿名插槽上,所以一般不写。
1 | <div id="app"> |
具名插槽
指定了插槽的 name 属性后,它就是具名插槽。同样地,可以指定两个同名的插槽,都会显示,被选中的时候也都会被替换,所以一般不会出现同名插槽。匿名插槽其实是有 name 属性的,它的值默认是 default。
我们插入到匿名插槽中的内容,如果没有指定 slot 属性,也默认会带上 slot="default"
属性。你也可以插入多个内容都修改同一个插槽位置。
新版中使用 v-slot:slotName
指定来指定要替换的插槽,简写形式是 #slotNmae
,与旧版非常大的区别是,必须要在 <template>
标签上使用这个指令,这一点和旧版的 slot 属性不同,slot 在任何标签都可以使用。而且如果定义了多个 <template>
标签指向同一个插槽,只会显示最后一个 template 标签的内容
注意 slot
属性和 slot-scoped
属性在 vue3.0 以后将无法使用,目前已被官方废弃。
注意, v-slot 只有在
<template>
元素上才有效, slot 则都有效。
template
的直接子元素允许多个。之前一直以为只能写一个 div
,这是错误的认知,但这样写好不好就是另外一回事了。
1 | <!-- HTML 内容 --> |
编译作用域
官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译,子组件模板的所有东西都会在子级作用域内编译。(原文:父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。)
1 | <div id="app"> |
作用域插槽
这是比较难以理解的一个点,而且官方文档说的不够直白。在这里用一句话对其进行总结:父组件替换插槽展示的格式,但是内容由子组件来提供,即从子组件把数据传给父元素,父元素重新组合 DOM 结构后传回子组件。
例如,子组件中包含一组数据: fLanguages: ['English', 'Chinese', 'Japanese', 'French', 'Spanish']
,插槽默认按 li 标签展示,但此时如果希望它用 -
分隔后做横排展示呢? 代码如下
1 | <!-- HTML 部分 --> |
插槽注意点
如果仅仅是文本变化,就不要使用插槽了,直接使用 props
组件传值即可。比较复杂的变化,如结构改变,元素改变之类的再使用插槽。
生命周期
Vue 源码中总共有 12 个生命周期钩子函数,官网提到了常用的 8 个钩子。另外四个分别是 errorCaptured(2.5新增,处理异常的钩子)
和 serverPrefetch(2.6新增 处理 ssr 的钩子)
。 activated
和 deactivated
(拿到的 this.$route 是跳转后的 $route 和组件内守卫 beforeRouteLeave 拿到的 this.$route 是不一样的,守卫拿到的是跳转前的 $route)专用于 keep-alive,当组件在<keep-alive></keep-alive>
这个抽象组件内被切换,才会触发这两个钩子,并且 不会 触发 created
和 destroyed
钩子,因为切换时组件只是缓存而不是被销毁。根据业务选择合适的钩子。
1 | new Vue({ |
Vue 的 DOM 更新
Vue 在下一个事件循环开始执行更新时才会进行必要的 DOM
更新。
进入页面中,会先渲染根组件,再一步步渲染子组件,也因此,可能根组件已经到了 mounted 阶段,而子组件仍然处于 created 阶段。而此时,如果获取 DOM,就会出现一些问题。如果想要等到子组件也渲染完毕才获取 DOM,可以在 this.$nextTick()
中操作。
this.$nextTick()
tick: 事件循环
在下次 DOM
更新循环结束之后执行延迟回调。因此可以满足上面的全部组件渲染完毕之后回调的需求。
1 | { |
另外的用法是,在修改数据之后立即使用这个方法,可以获取更新后的 DOM
。因为只要监听到数据变化, Vue 将开启一个队列,并缓冲同一事件循环中发生的所有数据变更(在这个队列中还包括了去重功能,比如同一个 watcher 被多次触发,只会被推入到队列中一次,避免了不必要的计算和 DOM 操作),也就是说,在一定时间之后 Vue 根据该队列的内容更新 DOM。
1 | Vue.component('example', { |
与 updated 的比较
上面我们说到,在发生数据修改的时候都会触发 beforeUpdate
和 updated
这两个钩子函数,如果要对特定的修改执行 updated
函数非常麻烦。官方为此提供了另外一种解决办法,那就是$nextTick
,它只会触发一次, updated
将会一直监听。
this.$nextTick
执行的比 updated 更早,它是在某个或某几个数据(调用 nextTick
方法之前的数据修改)被修改之后,被影响的 DOM 渲染完成后调用, updated
是在受数据影响的 DOM 全部重新渲染完毕时,才触发。
在 created 中修改数据,不会触发 updated
钩子,但会触发 $nextTick
。
1 | { |
额外
Vue.use
Vue.use 实际是调用了传入对象的 install 方法,同时会传进来 Vue。
1 | // 将会打印定义的内容 |
Vue.$option.data()
可以通过 Vue.$option.data() 拿到最初传入组件的 data 对象,可以用于初始化。调用的时候如果用来访问 props 或 methods,注意 this 的指向,应该写成 Vue.$options.data.call(this)
1 | { |
vue-cli 做的事,一切皆组件。
Vue 实例上 template 和 el 的关系,编译时 template 中的内容将完全替换 el 挂载的元素,此时页面中不会有 id 为 app 的元素了。
1 | <!-- |
此时看上去,vm 的代码复杂又冗余,到时候修改起来非常不方便,这时候就需要抽离 template
1 | <div id="app"> |
这大概就是 vue-cli 最初的想法。
之后为了把 template 和 js 代码分离开来,出现了 .vue 文件,把 template 与 js 区分,顺便带上了 css, 它需要 webpack 安装 vue-loader(让 webpack 认识 vue 文件,只负责加载,无法解析) 和 vue-template-complier(用于编译模板,解析模板) 支持
1 | <template> |