vue 原理
vue是一MVVM渐进式框架,在vue框架中数据会自动驱动视图。
MVVM设计模式
- View:视图,即DOM,UI组件,负责将数据转换成UI展示
- Model:模型,是vue组件里的data,或者vuex里数据
- ViewModel:视图模型,负责监听数据变化,控制视图行为,处理用户交互。
View和Model之间无直接联系,而是通过ViewModel进行交互,ViewModel通过双向数据绑定把View和Model连接起来,ViewModel通常要实现一个observer观察者,当数据发生变化,ViewModel能够监听到数据的这种变化,然后通知到对应的视图做自动更新,而当用户操作视图,ViewModel也能监听到视图的变化,然后通知数据做改动,因此,开发者只需关注业务逻辑,不需手动操作DOM,也不需关注数据状态的同步问题,vue是响应式的
响应式
Vue的响应式原理:通过Object.defineProperty
中访问器属性中get
和set
方法。data
中声明的属性都被添加了访问器属性,当读取data中数据时调用get
方法,当修改data中数据时,自动调用set
方法,在检测到数据变化,会通知观察者(Watcher),观察者(Watcher)会触发 重新渲染当前组件,生成新的虚拟DOM树,Vue遍历并对比新旧虚拟DOM树不同,并记录下来,最后,重新渲染视图,将局部修改到真实DOM树上。
每个组件实例都对应一个watcher实例,它会在组件渲染的过程中把“接触”过的数据property记录为依赖(getter函数通知watcher对象将其声明为依赖),之后当依赖项的
setter
触发时,会通知watcher,从而使它关联的组件重新渲染。
Vue 在 3.x 版本之后改用 Proxy 进行实现
变化检测问题
对于对象,vue无法检测到对象属性的添加或移除。由于vue会在初始化实例时对property执行 getter/setter转化,所有 property 必须在
data
对象上存在 才能让vue将它转换为响应式的。使用 Vue.set(object, propertyName, value)方法向对象添加响应式的property
使用
Object.assign()
或_.extend()
这样混合进到对象的新property不会触发更新。应该使用原对象与要混合进去的对象的property一起创建一个新对象,并替换原对象1
2this.someObj = Object.assign({}, this.someObj, {a: 1, b: 22})
// 替代, Object.assign(this.someObj, {a: 1, b: 22})
对于数组,以下2种数组变动,vue无法检测
利用索引直接设置数组项时,例如:
vm.items[index] = newValue
。解决方法:1
2
3
4// Vue.set()
Vue.set(vm.items, index, newValue)
// 数组 中 变异方法 Array.prototype.splice()
vm.items.splice(index, 1, newValue)直接修改数组的length时,例如:
vm.items.length = newLength
。解决方法:1
2// 删除 新 长度 以后的数组项
vm.items.splice(newLength)
声明响应式property
由于Vue不允许动态添加 根级响应式property,所以必须在初始化实例前 声明所有根级响应式property,哪怕是一个空值。好处:
- 可维护性上,因
data
对象就像是组件状态的结构,所以提前声明好,可以让组件代码在未来修改或阅读时更易于理解
异步更新 队列
Vue 在更新DOM时是
异步
执行的。只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生
的所有数据变更。如果一个watcher被多次触发,只会被推入到队列中一次。在缓冲时去掉 重复数据,避免了不必要的计算和DOM操作。
简而言之,就是在一个事件循环期间 发生的 所有数据改变 都会在 下一个事件循环的Tick
中来触发 视图更新。关于事件循环 可参考博客: EventLoop梳理
Vue在内部对异步队列尝试使用原生的
Promise.then
,MutationObserver
和setImmediate
.如果环境不支持,则会采用setTimeout(fn, 0)
代替响应数据 发生变更后,该组件不会立即重新渲染,而会在下一次事件循环”tick”中更新。如果想基于更新后的DOM状态做点什么,需要在数据变化后使用
Vue.nextTick(callback)
,回调函数将在DOM更新完成后被调用,回调函数中的this
将自动绑定到调用它的 Vue 实例上
1 | <div id="example">{{message}}</div> |
因为 $nextTick()
返回一个Promise
对象,所以可以使用 async/await
语法
1 | methods: { |
Vue 生命周期
Vue在整个生命周期中会有8个钩子函数供我们在不同时刻进行操作。注意,最后不要在生命周期钩子函数内使用箭头函数,因为箭头函数并没有this
,this
会作为变量一直向上级词法作用域查找,直至找到为止。在普通函数内部,this
就是该Vue 实例
Creation (Initialization)
创建钩子 在Vue实例开始初始化过程中运行,它允许我们在 将Vue实例添加到 DOM之前执行操作。同其他钩子不同,创建钩子也是在服务器呈现期间运行的,在此期间,不能访问DOM或this.$el
- beforeCreate:
data
未被激活,events
还没被设置 - created:vue实例已经初始化完成,
data
,computed
,watch
,methods
,events
,已激活。常用于,通过网络请求,为组件获取一些必要数据
Mounting (DOM Insertion )
将vue实例挂载到 指定元素时运行(即组件第一次渲染呈现之前、之后运行)。常用于在组件初始呈现之前或之后立即访问或修改组件中的DOM
- beforeMount: vue实例挂载到DOM之前以及模板或render函数已经编译好之后,此时,不能操作DOM,
this.$el
仍不可用,页面还是旧的。 - mounted:vue实例已被挂载,可以访问组件、模板和DOM,常用于操作DOM和集成非Vue库
Updating (Diff & Re-render)
每当组件使用的响应数据发生更改,或其他原因导致其重新渲染时,就会调用更新钩子函数。
可使用: 想要知道组件何时重新渲染(re-redner),可能用于调试或分析
不可使用:想知道组件内的data数据时何时更改的,建议用computed
或watch
替代
- beforeUpdate: 组件中data数据更改之后,修补和重新渲染DOM(patched and re-redner)之前运行。此时 data数据是最新的,但页面中尚未和最新数据同步
- updated: vue实例中数据更改和DOM也重新渲染呈现 之后运行。如果需要在属性更改后访问DOM,这里是最安全的
Destruction (Teardown)
销毁钩子允许在组件被消耗是执行操作,例如清理或分析发送。当组件被拆下并从DOM中移除是时,它们会触发。
- beforeDestroy: 在销毁实例之前触发,此阶段,该实例和其所有功能仍然可用,常用于执行资源管理,删除变量,清理事件、取消订阅等。
- destroyed: 实例别销毁,此阶段,所有的子vue实例也已经被销毁,事件监听器,所有指令都被解除绑定。调用
vm.$destroy()
会触发
render 函数
每一个vue组件都会实现一个render函数,大多数情况下,该函数由vue编译器创建。当在组件上指定一个模板(template
),该模板的内容将被vue编译器处理,并返回一个redner函数。render函数实际上会返回一个虚拟DOM节点,并由vue在浏览器DOM中呈现。再结合vue响应系统,vue能够智能的计算处最少需要重新渲染多少组件,并把 DOM操作次数减到最少。
每当组件的响应性属性更像时,都会再次调用redner函数
虚拟DOM
Vue通过渲染函数返回的虚拟DOM节点(在vue中通常成为VNode,包含vue所需的所有信息)建立起一个虚拟DOM,并用其来追踪自己要如何改变真实DOM。当vue要更新真实DOM时,vue会将更新后的虚拟DOM与之前的DOM进行比较,并仅使用修改的部分来更新真实DOM,这意味较少的元素被修改,从而提高性能。
createElement
在Vue生态中通常使用h
作为createElement
的别名
1 | Vue.component('anchored-heading', { |
createElement
,更准确的名字可能是createNodeDescription
,因为它返回VNode
,就是节点的描述信息。createElement
接受3个参数
HTML标签名,组件的选项对象,或 resolve 了 二者中 任一种的 一个的 async函数
{String | Object | Function}
1
2
3
4
5
6
7// 组件的选择对象
import App from "./App.vue"
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");一个与模板中 attribute 对应 的 数据对象
{Object}
1 | { |
- 子级虚拟节点 列表(VNodes),也是由
createElement()
构建而成,使用字符串直接生成”文本虚拟节点”{Array | String }
模板功能的替代
v-if 和 v-for
1 | <ul v-if="items.length"> |
1 | props: ['items'], |
v-model
1 | <input v-model="bar" /> |
1 | props: ['value'], |
事件&按键 修饰符
.passive
、capture
、once
这些事件修饰符,vue提供了对应的前缀可用于on
修饰符 | 前缀 |
---|---|
.passive (滚动事件默认行为(滚动行为),立即触发,而非等待‘onScroll 完成) |
& |
.capture |
! |
.once |
~ |
.capture.once 或 .once.capture |
~! |
1 | on: { |
其他修饰符,不需前缀,可在事件处理函数中找到等价的操作
修饰符 | 处理函数中等价操作 |
---|---|
.stop |
event.stopPropagation() |
.prevent |
event.preventDefault() |
.self |
if (event.target !== event.curentTarget) return |
按键:.enter ,.12 |
if (event.keyCode !== 12) return |
修饰键:.ctrl 、.alt 、.shift 、meta |
if (!event.ctrlKey) return // altKey shiftKey metaKey |
插槽
通过
this.$slots
访问静态 插槽,{[name: string]: ?Array<VNode>}
,每个插槽都是一个VNode数组。每个具名插槽有其相对应的property
,例如,v-slot:foo
中的内容,在this.$slots.foo
中,this.$slots.defalut
内容为 所有没有名字插槽中的节点1
2
3
4
5
6
7
8
9
10<blog-post>
<template v-slot:header>
<h1>Header</h1>
</template>
<p>content 1</p>
<template v-slot:footer>
<h2>Footer</h2>
</template>
<p>content 2</p>
</blog-post>1
2
3
4
5
6
7
8
9
10
11
12Vue.component('blog-post', {
redner: h => {
let header = this.$slots.header
let footer = this.$slots.footer
let body = this.$slots.default
return h('div', [
h('header', header),
h('main', body),
h('footer', footer)
])
}
})通过
this.$scopedSlots
访问作用域插槽,{[name:string]: props => Array<VNode> | undefined}
,每个作用域插槽都是一个返回若干 VNode 的函数1
2
3
4
5
6
7
8
9
10props: ['message'],
render: h => {
cosnt slef = this
// `<div><slot :text="message"></slot></div>`
return h('div', [
slef.$scopedSlots.defalut({
text: this.message
})
])
}
从2.6.0开始,所有$slots
都会作为函数暴露$scopedSlots
中 。所有在使用render函数,不论当前插槽是否带有作用域,都始终通过$scopedSlots
访问它们
Slot 插槽
在2.6.0后,具名插槽和作用域插槽使用统一语法: v-slot
指令,它取代了slot
和slot-scope
的attribute
父级模板里的所有内容都在父级作用域中编译,同理,子模版亦是如此。
具名插槽
当需要向组件添加多个插槽时,则插槽需要有名称,可使用<slot>
元素的一个特殊的attribute: name
来定义。一个不带name
的<slot>
,name
默认为default
1 | <div class="container"> |
在向具名插槽提供内容时,可在一个<template>
元素上使用v-slot
指令,用以v-slot
参数的形式 指定插槽名称
1 | <base-layout> |
作用域插槽
作用域插槽,可让插槽内容访问子组件中的数据。
1 | <span> |
想替换备用的内容,用 firstName 而不是 lastName 。**因只有在<current-user>
组件内部才可以访问到user
,为了让user
在父级的插槽内容中可用 ,需要将user
作为<slot>
元素的一个 attrubute 绑定 **
绑定在<slot>
元素上的 attrubute 称为 插槽prop
,这样就可以暴露给 插槽内容 。 现在 在父级作用域中,可以使用带值的v-slot
来定义插槽prop
对象 的名字,从而接收 插槽prop
内容。
1 | <current-user> |
在上例中,将包含所有插槽prop
的对象命名为slotProps
风格指南
必要的 (可规避错误)
组件名应该始终时多个单词,根组件
App
及Vue内置组件除外。避免跟现有、或未来的HTML元素相冲突,因为所有的HTML元素名称都是单个单词组件的
data
必须是一个函数。如果是对象,它会在组件的所有实例之间共享。为了避免如此,每个实例必须生成一个独立的数据对象,在函数返回对象即可。prop定义应该尽量详细。细致的prop定义可很容易看懂组件的用法,在开发环境中,不符合的prop,vue会告警。
1
2
3
4
5
6
7
8
9
10
11
12Vue.component('my-component', {
props: {
propA: {
type: Number,
defalut: 100,
required: true,
validator: function(value) {
return value < 1000
}
}
}
})必须用
key
配合v-for
, 且不要用idnex
作为key
.key
的类型number | string
。key
主要用在Vue的虚拟DOM算法,在新、旧 虚拟DOM对比时 辨别 Vnodes,从而高效的更新虚拟DOM- 不使用key,Vue会使用一种最大限度减少动态元素,并尽可能的尝试就地修改/复用相同类型元素的算法。
- 使用key, Vue会基于key的变化重新排列元素顺序,并且移除 key 不存在的元素
避免在同一个元素上使用
v-if
,v-for
。因为
v-for
比v-if
具有更高的优先级,所有在每次重新渲染的时候回遍历整个列表。另外 在 渲染层的 添加了逻辑,可维护性不强。为了 过滤一个列表中的项目,应该使用
计算属性
,让其返回过滤后的列表1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// bad
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
// good
<ul>
<li v-for="user in activeUsers" :key="user.id">
{{user.name}}
</li>
</ul>
<script>
computed: {
activeUsers: function () {
return this.users.filter(user => user.isActive)
}
}
</script>为了避免渲染本应该被隐藏的列表,将
v-if
移至容器元素,这样只需检查一次,而不会对列表中的每项都检查1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// bad
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
// good
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
为 组件 css d样式 设置 作用域
scoped
,避免冲突。 除 顶级App
组件,布局组件中css样式可以时全局的,其它的组件 都应该是 有作用域的。 覆写子组件的样式,可使用>>>
操作符, 对于less
或sass
等预编译css,可使用/deep/
私有property名,在使用模块作用域时要保持不允许外部访问的函数的 私有性。可在不考虑作为对外公共API的自定义私有property 使用
$_
前缀,并附带一个命名空间以避免同其他作者冲突。1
2
3
4
5
6
7
8
9
10
11
12
13// bad
var myMixin = {
methods: {
update: function() {}
}
}
// good
var myMixin = {
methods: {
$_myMixin_update: function() {}
}
}
强烈推荐 (可增强可读性)
将每个组件都单独分出文件,这样在需要编辑或查找一个组件时,更快速找到。
单文件组件的文件名应该 始终 是 单词大小开头( 对自动补全友好 ),或 短横线连接(对大小写不敏感的文件系统 )
应用特定样和约定的基础组件(即,展示类的,无逻辑,无状态的组件)名称应该全部以一个特定的前缀开头,比如
Base
,App
基础组件的名称*通常包含所包裹元素的名称**,比如
BaseButton
,BaseTable
。1
2
3
4
5// good
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue只应该有单个活跃的实例的组件(单例组件)应该以
The
前缀命名,以表示其唯一性。单例组件不意味着该组件只用于一个单页应用中,而是每个页面只使用一次。这些组件永远不接受
prop
和父组件紧密耦合的子组件应该以父组件名作为前缀命名
1
2
3
4
5
6
7
8
9
10
11
12// bad
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
// good
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoLListItemButton.vue组件名中的单词顺序,以高级别的(通常是一般化的描述)单词开头,以描述性的修饰符结尾
1
2
3
4
5
6
7
8
9// bad
components/
|- ClearSearchButton.vue
|- RunSearchButton.vue
//good
components/
|- SearchButtonClear.vue
|- SerchButtonRun.vue自闭合组件,在单文件组件、字符串模板、JSX中没有内容的组件应该是自闭合的; 但在DOM模板中永远不要,因为HTML不支持自闭合的自定义元素,只用官方的”空”元素(
<br/> <hr/> <input/>
)可以。自闭合组件表示它们不仅没有内容,而是刻意没有内容
1
2
3
4
5
6
7
8
9
10
11// bad
<!-- 在 单文件组件、字符串模板、JSX中 -->
<MyComponent></MyComponent>
<!-- 在 DOM 模板-->
<my-component>
// good
<!-- 在 单文件组件、字符串模板、JSX中 -->
<MyComponent/>
<!-- 在 DOM 模板-->
<my-component></my-component>模板中的组件名大小写,对大多数项目,在单文件组件和字符串模板中组件名总是
PascalCase
,在DOM模板中总是kebab-case
的。 另外,在所有的地方都使用kebab-case
亦可在JS/JSX中的组件名应该始终是
PascalCase
的。对于只通过Vue.component
定义的全局组件,推荐使用kebab-case
1
2
3
4
5
6
7
8
9
10// good
Vue.component('my-component', {
// ...
})
import MyComponent from './MyComponent,vue'
export default {
name: 'MyComponent'
}prop名大小写,在声明是,使用
camelCase
,在模板或JSX中使用kebab-case
1
2
3
4
5
6// good
props: {
greetingText: String
}
<WelcomeMessage greeting-text="hi">组件模板应该只包含简单的表达式,复杂的表达式应该重构为计算属性 或 方法
应该把复杂的计算属性分割为尽可能多的简单计算属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// bad
computed: {
price: function () {
var basePrice = this.manufactureCost / (1 - this.profitMargin)
return (
basePrice -
basePrice * (this.discountPercent || 0)
)
}
}
// good
computed: {
basePrice: function() {
return this.manufactureCost / (1 - this.profitMargin)
},
discount: function() {
return this.basePrice * (this.discountPercent || 0)
},
finalPrice: function() {
return this.basePrice - this.discount
}
}
Vue 组件
组件注册
组建名
组建名就是Vue.component的第一个参数
,当直接在DOM中使用一个组件时,推荐使用W3C规范中的自定义组件名,使用kebab-case
,在引用该组件时也必须使用kebab-case
1 | Vue.component('my-component', {}) |
全局注册
通过Vue.component
来创建的组件是全局注册的,该组件可用在任何新创建的Vue根(new Vue)实例的模板中。全局组成的行为必须在根Vue实例创建之前发生
被频繁使用的基础 组件可配置webpack
,实现自动化的全局注册。使用require.context
来全局注册这些通用的基础组件。以下是在应用入口文件(src/main.js
)中全局 注册/导入基础组件
1 | import Vue from 'vue'; |
局部注册
在 components
选项中定义想要使用的组件
1 | var ComponentA = {} |
Prop
Prop大小写
因HTML中的attribute名是大小写不敏感的,所以浏览器会把所有的大写字符解析为小写字符。所有在使用DOM模板时,camelCase的prop名 需要 使用其等价的 kebab-case
Prop类型检查和验证
1 | props: { |
类型检测,type
可以是以下原生构造函数,也可以是自定义的构造函数,并且通过instanceof
来进行检查确认。
注意:Prop检查和验证 是 在一个组件实例创建之前,所有实例的property 在 default
或 validator
函数中是不可用的
- String
- Number
- Boolean
- Object
- Function
- Data
- Array
- Symbol
传递静态或动态Prop
在传递非字符串类型
时,即便是静态的,仍然要使用v-bind
来告诉Vue,其是JavaScript表达式,而不是一个字符串
1 | <!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue --> |
传入一个对象的所有property
使用不带参数的v-bind
,可将一个对象的所有property都作为prop传入
1 | post: { |
单项数据流
prop数据都是从父级流向子级的,即父级prop的更新会向下流到子组件。例外,每次父级组件发生变更时,子组件中所有的prop也将更新为最新的值。这意味不应该在子组件内部改变prop。但有2种常见的视图变更prop的情况:
- prop用来传递一个初值,子组件内后续可能会变更。此情况,最好定义一个本地的data property 并将这个prop 用作其初始值
1 | props: ['initialCounter'], |
prop作为原始的值传入并需要进行转换。此情况,最好使用这个prop来定义一个计算属性
1
2
3
4
5
6
7props: ['name'],
computed: {
normalizedName: function() {
return this.name.trim().toLowerCase()
}
}
非 Prop的 Attribute
非prop的 attribute 会被添加到组件的根元素上
对于绝大多数 attribute 来说,从外部提供给组件的值会替换到组件内部 根元素 设置好的值。但 class
和 style
会被合并。
如果不希望组件的根元素继承 attribute,可以在组件的选项中设置 iheritAttrs: false
,从而禁用 attribute 继承
1 | Vue.component('my-component', { |
例外,实例的$attrs
,包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 ,class,style 除外,在通过v-bind="$attrs"
传入内部组件
1 | Vue.component('base-input', { |
使用该组件就像使用原始的input
元素一样设置attribute
,因为这些attribute
都被设置在组件内的 input
元素上,而非根元素上
1 | <base-input |
自定义事件
事件名
触发事件的事件名必须要完全匹配 监听该事件所用的名称,并且v-on
事件监听器在DOM模板中会别自动转换为全小写,v-on:myEvent
被转换成v-on:myevent
,因此推荐始终使用kebab-case
的事件名
自定义组件的v-model
组件的v-model
默认利用名为value
的prop,和名为input
的事件,·**model
选项可以定制prop 和 event**,这样就可以将value
prop用作其他地方。
1 | <template> |
将原生事件绑定到组件
v-on
的.native
修饰符,监听原生事件。
$listeners
是一个对象,包含了作用于该组件上 v-on
监听器,不包括带.native
修饰符的.可通过v-on="$isteners"
传入内部组件的某个特定的子元素
1 | Vue.component('base-input', { |
此时<base-input>
组件就是一个完全透明的<input>
包裹器,可以完全像一个普通的<input>
元素一样 使用。
1 | <!-- 监听 foucs,失败,$listeners 不包含带`.native`的`v-on`监视器 --> |
动态组件
<component>
vue的内置组件,动态组件,依据is
的值,来决定那个组件被渲染.is
的值,可以是已注册组件的名字,或 一个组件的选项对象。
使用<keep-alive>
元素将动态组件包裹起来,可使失活的组件被缓存
1 | <keep-alive> |
异步组件
在大型 应用中,需要将应用分割成多个小模块,并且只在需要的时候才从服务器加载该模块。Vue允许 以一个工厂函数的方式定义组件,并异步解析。只在该组件 被渲染的时候才会触发该工厂函数,并会把结果缓存起来 供以后重新渲染。
也可以在工厂 函数返回一个Proomise
,通过webpack
和import
语法,可写成 如下:
1 | // 全局注册 |
处理异步组件的加载状态
1 | const AsyncComponent = () => ({ |
过渡 、动画
Vue在插入、更新、移除DOM时,提供了4种方式的应用过渡效果。
- 在CSS 过渡和动画中自动应用class
- 配合使用第三方CSS动画库,如 Animate.css
- 在过渡钩子函数中使用JavaScript直接操作DOM
- 配合使用第三方JavaScript动画库,如 Velocity.js
基本
过渡的类名
在进入/离开的过渡中,会有6个class切换
v-enter
: 过渡开始状态v-enter-active
:过渡生效状态,此类被用来定义进入过渡的过程时间、延迟、曲线函数v-enter-to
:过渡结束状态v-leave
: 离开过渡的开始状态v-leave-active
:离开过渡的生效状态,此类被用来定义 离开过渡的 过程时间、延迟、曲线函数v-leaver-to
: 离开过渡的结束状态
没有设置name
attribute 的<transition>
时,v-
这些类名的默认前缀。设置了name
attribute 的 <transition name="myTransitionName">
,则name
的值会替换V
, 即myTransitionName-
是这些类名的前缀
自定义过渡的类名
通过<transition>
一些的 attrubute 可定义过渡的类名。自定义过渡的类名优先级 _高于_普通类名,因此可轻松的使用其他第三方CSS过渡/动画库。
- enter-class
- enter-active-class
- enter-to-class
- leave-class
- leave-active-class
- leave-to-class
结合Animate.css
1 | <div class="example-2"> |
JavaScript钩子函数
JS钩子函数,其实就是transition
组件上的事件:
before-enter
,befroe-leave
enter
,leave
afer-enter
,after-leave
当只用javaScript过渡时,在enter
和leave
中必须使用done
进行回调,否则,它们被同步调用,过渡会立即完成。
对于仅使用JavaScript过渡的元素,transition
组件的css
prop设置为false
(v-bind:css="false"
),Vue会跳过CSS检查,将只通过事件 触发 注册的JavaScript钩子。
结合Velocity.js
1 | <div id="example-3" style="width:150px"> |
过渡模式
<transition>
的默认行为-进入和离开同时发生,但是在滑动过渡时不能满足要求,所以Vue提供了过渡模式, mode
1 | <transition name="fade" mode="out-in"> |
in-out
: 新元素先进行过渡,完成后 当前元素再过渡离开out-in
: 当前元素先进行过渡,完成后 新元素再过渡进入
单元素/组件的 过渡
Vue提供 transition
的封装组件,以下情况,可以元素/组件 添加 进入/离开 过渡
- 条件渲染(
v-if
) - 条件展示(
v-show
) - 动态组件 (
is
) - 组件根节点
简单例子:
1 | <template> |
多个元素的过渡
当有相同标签名的元素时,需要通过给key
设置唯一的值来标记,以让Vue区分它们,否则Vue未来效率会替换相同标签内部的内容。所以,在给<transition>
组件中多个元素设置key
是一个更好的实践。
通过给同一个元素的key
设置不同状态来代替v-if
和v-else
1 | <transition> |
例外,多个v-if
的多元素的过渡,也可重写为绑定了动态的key
的单个元素
1 | <transition> |
多个组件的过渡
多个组件不需要使用key
,只需要使用动态组件即可
1 | <div id="example-4"> |
列表过渡
transition
的过渡,只是
- 单个节点
- 同一时间渲染多个节点中的一个
<transition-group>
,同时渲染整个列表,或使用v-for
时同时渲染多个节点。该组件的特点:
- 不同
transition
,它会以一个 真实元素呈现,默认span
,通过tag
attribute 更换为其他元素 - 内部元素总需要提供唯一的
key
值 - CSS过渡的类会应用到内部元素中,而不是这个组件/容器本身
- 内部元素在改变定位的过程中,也可以添加过渡效果。使用新增的过渡的类名
v-move
,通过name
来自定义前缀,通过move-class
attribute 手动设置
可复用的过渡
要创建一个可复用的过渡组件,只需将<transition>
,transition-group
作为根组件,然后将任何子组件放置其中即可
动态过渡
- 过渡 attribute 都时可以动态绑定的,通过
name
来绑定动态值,从而在不同的过渡间切换 - 通过事件钩子获取上下文中的所有数据,即 可根据组件的状态,设置不同的Javascript过渡表现
状态过渡
对于数据元素本身的动效,例如 _数字和运算_,可以结合Vue的响应式和组件系统,使用第三方库来实现过渡状态。
使用GreenSock,通过watch
,监听数据更新,应用过渡状态
混入 Mixin
混入一般用来分发Vue组件中可复用的功能。一个混入对象可以包含任意组件的选项。当组件属于混入对象时,所有混入对象将混入“进该组件本身的选项。
选项合并
当组件和混入对象含有同名选项时,这些选项按选项合并策略进行”合并“
- 数据对象
data
在内部会进行递归合并,在发生冲突时以组件内数据优先 - 同名钩子函数将合并成一个数组,因此都将被调用。且混入对象的钩子函数将在组件自身钩子之前被调用
- 值为对象的选项,例如
methods
,components
,directives
,computed
,将被合并为同一对象,当有键名冲突时,取组件对象的键值对。
自定义选项合并策略
如果想要自定义选项合并策略,可以向Vue.configin.optionMergeStrategies
添加一个函数:
1 | const merge = Vue.config.optionMergeStrategies.computed |
自定义指令
如果需要对普通DOM元素进行底层操作,可使用自定义指令。全局注册使用Vue.directive()
,局部注册, 使用组件的选项directives
利用指令,实现输入框聚焦
1 | // 全局注册 |
钩子 函数
指令对象可以提供5个可选的钩子函数
bind
: 只调用一次,指令在第一次 绑定 到元素时调用。在这里通常 进行 一次性的初始化设置inserted
:被绑定元素插入父节点时调用,但不一定被插入文档,仅仅保证了父节点的存在unpdate
: 所在组件的VNode更新时调用,可能发生在其子VNode更新之前componentUpdated
:所在组件的VNode及其子VNode全部更新后调用unbind
: 只调用一次,指令与元素解绑时调用
钩子函数可接受4个参数,除了el
外,其他参数都应该是可读的,切勿修改
el
: 指令所绑定的元素,可用来直接操作DOMbinding
: 一个对象,包含如下propertyname
:指令名 不包含v-
前缀value
:指令绑定的值oldValue
:指令绑定的前一个值expression
:字符串形式的指令表达式arg
: 传给指令的参数。 例如:v-my-directive:foo
,参数 为foo
modifiers
:包含修饰符的对象。例如:v-my-directive:foo.bar.zar
,修饰符对象为{bar:true, zar:true}
vnode
: Vue编译生成的虚拟节点oldVnode
:上一个虚拟节点