目录
Vue
之前做项目是前后端都会做的,主要用的是Vue,定期来温故而知新一下。
在官网的基础上整理了一下,并总结了一些常见的知识点。
基础
Vue是什么
是一套基于构建用户界面的渐进式框架;
自底向上逐层应用;
vue的核心库只关注图层;
完全能够单页应用提供驱动;
安装
npm install vue
介绍
声明式渲染
Vue的核心是一个允许采用简洁的模板语法来声明式的将数据渲染进DOM的系统。
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
// Hello Vue!
指令:带有前缀v-,表示是Vue提供的特殊特性。
<div id="app-2">
<span v-bind:title="message">
鼠标悬停几秒钟查看此处动态绑定的提示信息!
</span>
</div>
var app2 = new Vue({
el: '#app-2',
data: {
message: '页面加载于 ' + new Date().toLocaleString()
}
})
条件与循环
v-if、v-else、v-for
<div id="app-3">
<p v-if="seen">现在你看到我了</p>
<p v-else>看不到我了</p>
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>
var app3 = new Vue({
el: '#app-3',
data: {
seen: true,
todos: [
{ text: '学习 JavaScript' },
{ text: '学习 Vue' },
{ text: '整个牛项目' }
]
}
})
// 现在你看到我了
// 1. 学习 JavaScript
// 2. 学习 Vue
// 3. 整个牛项目
处理用户输入
v-on指令添加一个事件监听器。
v-model实现表单输入和应用状态之间的双向绑定。
<div id="app-5">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">反转消息</button>
<input v-model="message">
</div>
var app5 = new Vue({
el: '#app-5',
data: {
message: 'Hello Vue.js!'
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')
}
}
})
// Hello Vue!
组件化应用构建
在Vue里,一个组件本质上是一个拥有预定义选项的一个Vue实例。
// 定义名为 todo-item 的新组件
Vue.component('todo-item', {
template: '<li>这是个待办项</li>'
})
var app = new Vue(...)
<ol>
<!-- 创建一个 todo-item 组件的实例 -->
<todo-item></todo-item>
</ol>
// 通过从父作用域将数据传到子组件
Vue.component('todo-item', {
// todo-item 组件现在接受一个
// "prop",类似于一个自定义特性。
// 这个 prop 名为 todo。
props: ['todo'],
template: '<li>{{ todo.text }}</li>'
})
<div id="app-7">
<ol>
<!--
现在我们为每个 todo-item 提供 todo 对象
todo 对象是变量,即其内容可以是动态的。
我们也需要为每个组件提供一个“key”,稍后再
作详细解释。
-->
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id"
></todo-item>
</ol>
</div>
var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其它什么人吃的东西' }
]
}
})
Vue实例
1. 创建一个Vue实例;
2. vue实例创建后,会将data对象中的所有属性加入到Vue的响应式系统中,当属性的值发生改变时,视图将会产生‘响应’,匹配更新后的值;
var data = { a: 1 } 数据对象:初始值
// Object.freeze(obj) 会阻止修改现有的属性,意味着响应系统无法再追踪变化。
var vm = new Vue({
// 选项
data: data
})
实例生命周期钩子
beforeCreate
新对象诞生。
在对象初始化之前执行。
created
创建具有默认特性的对象。
beforeMount
对象在DOM中适合形状。
检查是否有任何模板可用于要在DOM中呈现的对象。
没有找到,会将HTML视为模板。
mounted
已安装。DOM已准备就绪并放置在页面内。
它将数据放入模板并创建可呈现元素。
beforeUpdate
更改已完成,但尚未准备好更新DOM。
updated
更新,在DOM中呈现的更改。
beforeDestroy
对象准备销毁。
destoryed
销毁。对象停止并从内存中删除。
模板语法
插值
文本
数据绑定最常见的形式就是用双大括号Mustache语法的文本插值
<span>Message: {{ msg }}</span>
使用v-once指令,也能一次性的插值,当数据改变时,插值处的内容不会更新。
<span v-once>这个将不会改变: {{ msg }}</span>
原始HTML
为了输出真正的HTML,需要使用v-html指令。
Attribute
Mustache语法不能作用在Html attribute上,,应使用v-bind指令。
<div v-bind:id="dynamicId"></div>
使用JavaScript表达式
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
指令
DIrectives是带有v-前缀的特殊attribute。
指令的职责是,当表达式的值改变时,将其产生的连带影响,相应式的作用于DOM。
参数
一些指令接收一个参数,在指令名称之后以冒号表示。
<a v-bind:href="url">...</a>
在这里 href 是参数,告知 v-bind 指令将该元素的 href attribute 与表达式 url 的值绑定。
v-on也可以用于监听DOM事件。
<a v-on:click="doSomething">...</a>
动态参数
<a v-bind:[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
动态参数的值的约束:
动态参数预期会求出一个字符串,异常为null,null值会被显性的用于移除绑定。
对动态参数表达式的约束:
空格、引号放在Html attribute名里是无效的。
修饰符
修饰符以半角句号.指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。
<form v-on:submit.prevent="onSubmit">...</form>
缩写
v-前缀用来识别模板中Vue特殊的attribute。
v-bind缩写
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
v-on缩写
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
计算属性和侦听器
计算属性
计算属性缓存VS方法
可以将同一函数定义为一个方法而不是一个计算属性。
计算属性是基于它们的响应式依赖进行缓存的。
计算属性VS侦听属性
当一些数据随着其它数据的变动而改变时,更好的做法是使用计算属性。
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
计算属性的setter
计算属性默认只有getter,不过根据需要可以提供一个setter
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
侦听器
watch选项来响应数据的变化,当需要再数据变化时执行异步或开销较大的操作比较有用。
Class与Style
绑定HTML Class
对象语法
传给v-bind:class一个对象(可以传入更多的属性),以动态切换class
<div v-bind:class="{ active: isActive }"></div>
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
数组语法
<div v-bind:class="[activeClass, errorClass]"></div>
三元表达式
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
用在组件上
1. 声明组件
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})
2. 给组件添加class
<my-component class="baz boo"></my-component>
3. HTML被渲染为
<p class="foo bar baz boo">Hi</p>
绑定内联样式
对象语法
v-bind:style
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
数组语法
<div v-bind:style="[baseStyles, overridingStyles]"></div>
自动添加前缀
当v-bind:style使用需要添加浏览器引擎前缀的CSS属性时,vue会自动侦测并添加相应的前缀。
多重值
2.3.0后可以为style绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值。
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
条件渲染
v-if
必须添加到一个元素上
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
用key管理可复用的元素
v-show
带有v-show的元素始终会被渲染并保留在DOM中。
<h1 v-show="ok">Hello!</h1>
v-if VS v-show
v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当的被销毁和重建。
v-if是惰性的:如果初始渲染时条件为假,则什么也不做,直到条件第一次为真时,才会开始渲染条件块。
v-show则是不管初始条件是什么,元素总是会被渲染,并且只是简单的基于CSS进行切换。
一般来说,v-if有更高的切换开销,v-show有更高的初始渲染开销。
如果频繁切换,使用v-show较好;
如果运行时条件很少改变,使用v-if较好。
v-if与v-for一起使用
一起使用时,v-for会有更高的优先级。
列表渲染
v-for
v-for把一个数组(或对象)对应为一组元素。
<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
用of也可以代替in
<div v-for="item of items"></div>
维护状态
Vue正在更新使用v-for渲染的元素列表,默认使用“就地更新”策略。
如果数据项的顺序被改变,Vue将不会移动DOM元素来皮牌数据项的顺序,而是就地更新每个元素,并确保他们在每个索引位置正确渲染。
数组更新监测
变异方法
Vue将被侦听的数组的变异方法进行了包裹,所以它们也会触发视图更新:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
替换数组
非变异方法则不会改变原始数组,总是返回一个新的数组,可使用新数组替换旧数组,如:filter() concat() slice()。
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
注意事项
由于JavaScript的限制,Vue不能检测以下数组的变动:
-
当你利用索引直接设置一个数组项时
-
当修改数组的长度时
var vm = new Vue({ data: { items: ['a', 'b', 'c'] } }) vm.items[1] = 'x' // 不是响应性的 vm.items.length = 2 // 不是响应性的
通过以下方式可以实现和vm.items[indexOfItem] = newValue相同的效果,同时也将在响应式系统内触发状态更新
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
vm.items.splice(newLength)
对象变更检测注意事项
由于JavaScript的限制,vue不能检测对象属性的添加或删除。
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` 现在是响应式的
vm.b = 2
// `vm.b` 不是响应式的
对于已经创建的实例,Vue不允许动态添加根级别的响应式属性。
可以通过Vue.set(object, propertyName, value)方法向嵌套对象添加响应式的属性。
var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})
Vue.set(vm.userProfile, 'age', 27)
vm.$set(vm.userProfile, 'age', 27)
如果为已有对象赋值多个新属性,应该用两个对象的属性创建一个新的对象
不要这样:
Object.assign(vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
应该这样:
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
显示过滤/排序后的结果
可以创建一个计算属性来返回过滤或排序后的数组。
<li v-for="n in evenNumbers">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
计算属性不适用时,使用方法:
<li v-for="n in even(numbers)">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
v-for与v-if一起使用
v-for的优先级比v-if高
在组件上使用v-for
2.2.0+版本里,v-for使用时,key是必须的。
事件处理
监听事件
v-on指令监听DOM事件
事件处理方法
v-on:click接收一个需要调用的方法名
内联处理器中的方法
<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})
事件修饰符
使用修饰符时,顺序很重要;
相应的代码会以同样的顺序产生。
- .stop
- .prevent
- .capture
- .self
- .once
- .passive
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
阻止所有的点击
v-on:click.prevent.self
阻止对元素自身的点击
v-on:click.self.prevent
2.1.4新增
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
2.3.0新增
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
【不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,
同时浏览器可能会向你展示一个警告。
请记住,.passive 会告诉浏览器你不想阻止事件的默认行为。】
按键修饰符
v-on监听键盘事件时添加按键修饰符
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
.exact修饰符
允许你控制由精确的系统修饰符组合触发的事件。
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>
鼠标按钮修饰符
- .left
- .right
- .middle
会限制处理函数仅响应特定的鼠标按钮。
为什么在Html中监听事件?
- 扫一眼html模板便能轻松定位在JavaScript代码里对应的方法
- 因为你无需在JavaScript里手动绑定事件,你的ViewModel代码可以是非常纯粹的逻辑,和DOM完全解耦,更易于测试
- 当一个ViewModel被销毁时,所有的事件处理器都会自动被删除,无需担心如何清理它们
表单输入绑定
基础用法
v-model负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model会忽略所有表单元素的value、checked、selected特性的初始值而总是将Vue实例的数据作为数据来源。
v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件:
- text、textarea元素使用value属性和input属性;
- checkbox、radio使用checked属性和change事件;
- select字段将value作为prop并将change作为事件;
修饰符
.lazy
默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步,添加lazy修饰,从而转变为使用change事件进行同步
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >
.number
自动将用户的输入值转为数值类型
<input v-model.number="age" type="number">
.trim
自动过滤用户输入的首尾空白字符
<input v-model.trim="msg">
组件基础
基本示例
组件是可复用的Vue实例,且带有一个名字
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
组件的复用
可以复用任意多次
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
data必须是一个函数
一个组件的data必须是一个函数
data: function () {
return {
count: 0
}
}
组件的组织
组件的注册类型:
- 全局注册:可以用在其被注册之后的任何新创建的vue根实例,也包括其组件树中的所有子组件的模板中;
- 局部注册
通过Prop向子组件传递数据
单个根元素
每个组件必须只有一个根元素。
监听子组件事件
$emit
$event
在组件上使用v-model
<input v-model="searchText">
等价于:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
<custom-input v-model="searchText"></custom-input>
通过插槽分发内容
:向一个组件传递内容
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})
动态组件
在不同组件之间进行动态切换,vue的元素加一个特殊的is特性来实现
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
深入了解组件
组件注册
组件名
字母全小写,且包含一个连字符。
组件名大小写
**使用kebab-case
短横线分割命名
Vue.component('my-component-name', { /* ... */ })
使用PascalCase**
首字母大写命名
Vue.component('MyComponentName', { /* ... */ })
全局注册
全局注册组件之后,可以用在任何新创建的Vue根实例的模板中。
局部注册
局部注册的组件在其子组件中不可用。
模块系统
import/require
在模块系统中局部注册
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
基础组件的自动化全局注册
全局注册的行为必须在根Vue实例创建之前发生。
Prop
Prop的大小写
驼峰命名法的prop名需要使用其等价的短横线命名。
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
Prop类型
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
传递静态或动态Prop
v-bind动态赋值
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>
传入一个数字
传入一个布尔值
传入一个数组
传入一个对象
传入一个对象的所有属性
单向数据流
改变prop:
- 这个prop用来传递一个初始值,这个子组件接下来希望将其作为一个本地的prop数据来使用。
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
- 这个prop以一种原始的值传入且需要进行转换
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
Prop验证
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
类型检查
type类型:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
非Prop的Attribute
一个非prop的attribute是指传向一个组件,但该组件并没有相应prop定义的attribute。
替换/合并已有的Attribute
从外部提供给组件的值会替换掉组件内部设置好的值。
class与style attribute会使值合并起来。
禁用Attribute继承
如果不希望组件的根元素继承attribute,可以在组件的选项中设置inheritAttrs: false,
Vue.component('my-component', {
inheritAttrs: false,
// ...
})
配合实例$attrs属性使用,可以手动决定这些attribute会被赋予哪个元素。
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})
inheritAttrs: false选项不会影响style和class的绑定。
该模式允许你在使用基础组件的时候更像是使用原始的HTML元素,而不会担心哪个元素是根元素。
<base-input
v-model="username"
required
placeholder="Enter your username"
></base-input>
自定义事件
事件名
事件名不存在任何自动化大小写转换。
推荐使用kebab-case事件名。
自定义组件的v-model
一个组件上的v-model默认会利用名为value的prop和名为input的事件。
但是单选框和复选框等输入控件可能会将value特性用于不同目的。
model可以避免这样的冲突
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
lovingVue值将会传入这个名为checked的prop
<base-checkbox v-model="lovingVue"></base-checkbox>
将原生事件绑定到组件
v-on.native修饰符
<base-input v-on:focus.native="onFocus"></base-input>
有时候这种情况不是很好的,比如.native监听器将静默失败
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
解决这个问题,Vue提供了一个$listeners属性,是一个对象,里面包含了作用在这个组件上的所有监听器
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
.sync修饰符
双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。
通过.sync修饰符:
<text-document v-bind:title.sync="doc.title"></text-document>
带有.sync修饰符的v-bind不能和表达式一起使用
当用一个对象同时设置多个prop的时候,也可以将.sync修饰符和v-bind配合使用。
doc对象中的每个属性都作为一个独立的prop传进去,然后各自添加用于更新的v-on监听器。
<text-document v-bind.sync="doc"></text-document>
将v-bind.sync用在一个字面量的对象上,如:v-bind.sync=”{ title: doc.title }”是无法工作的,因为要考虑很多边缘因素。
插槽
插槽内容
Vue实现了一套内容分发的API,这套API设计将元素作为承载分发内容的出口。
合成组件
<navigation-link url="/profile">
Your Profile
</navigation-link>
<navigation-link>模板中可能写成:
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
当组件渲染的时候,<slot></slot>就会替换为“Your Profile”,插槽内可以包含任何模板代码
如果<navigation-link>没有包含一个<slot>元素,则该组件起始标签和结束标签之间的任何内容都会抛弃。
编译作用域
父级模板里的所有内容都是在父级作用域中编译的;
子模板里的所有内容都是在子作用域中编译的。
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!--
这里的 `url` 会是 undefined,因为 "/profile" 是
_传递给_ <navigation-link> 的而不是
在 <navigation-link> 组件*内部*定义的。
-->
</navigation-link>
后备内容
为一个插槽设置具体的后备(默认的)内容是很有用的,它只会在没有提供内容的时候被渲染。
<button type="submit">
<slot>Submit</slot>
</button>
具名插槽
v-slot只能添加在上
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
作用域插槽
绑定在元素上的特性被称为插槽prop。
将包含所有插槽prop的对象命名为slotProps。
在父级作用域中,我们可以使用带值的v-slot来定义我们提供的插槽prop的名字。
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
独占默认插槽的缩写语法
当被提供的内容只有默认插槽时,组件的标签才可以被当做插槽的模板来使用。
未指明的内容对应默认插槽,不带参数的v-slot被假定对应默认插槽。
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
默认插槽的缩写语法不能和具名插槽混用,会导致作用域不明确
只要出现多个插槽,始终为所有的插槽使用完整的基于语法
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
解构插槽Prop
作用域插槽的内部工作原理是将你的插槽内容包含在一个传入单个参数的函数里
可以使用ES5解构来传入具体的插槽prop
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
动态插槽名
动态指令参数也可以来定义动态的插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
具名插槽的缩写
v-slot缩写,把参数之前的所有内容替换为#
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
其他示例
插槽prop允许我们将插槽转换为可复用的模板,这些模板可以基于输入的prop渲染出不同的内容。
动态组件&异步组件
在动态组件上使用keep-alive
在多标签的界面中使用is特性来切换不同的组件。
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
要求被切换到的组件都有自己的名字,不论是通过组件的name选项还是局部/全局注册。
异步组件
Vue允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。
Vue只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})
当使用局部注册的时候,也可以直接提供一个返回Promise的函数。
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
处理加载状态
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
处理边界情况
访问元素&组件
访问根实例
在每个new Vue实例的子组件中,其根实例可以通过$root属性进行访问。
// Vue 根实例
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
所有的子组件都可以将这个实例作为一个全局的store来访问或使用。
// 获取根组件的数据
this.$root.foo
// 写入根组件的数据
this.$root.foo = 2
// 访问根组件的计算属性
this.$root.bar
// 调用根组件的方法
this.$root.baz()
访问父级组件实例
$parent属性可以用来从一个子组件访问父组件的实例。
访问子组件实例或子元素
ref特性为这个子组件赋予一个ID引用。
<base-input ref="usernameInput"></base-input>
通过this.$refs.usernameInput 访问<base-input>实例
使用一个类似的ref提供对内部这个指定元素的访问
<input ref="input">
通过父级组件定义方法
methods: {
// 用来从父级组件聚焦输入框
focus: function () {
this.$refs.input.focus()
}
}
这样就允许父级组件通过下面的代码聚焦 <base-input> 里的输入框:
this.$refs.usernameInput.focus()
ref和v-for一起使用的时候,得到的引用将会是一个包含了对应数据源的这些子组件的数组。
r e f s 只 会 在 组 件 渲 染 完 成 之 后 生 效 , 并 且 他 们 不 是 响 应 式 的 。 仅 用 于 一 个 直 接 操 作 子 组 件 的 “ 逃 生 舱 ” , 尽 量 避 免 在 模 板 或 计 算 属 性 中 访 问 refs只会在组件渲染完成之后生效,并且他们不是响应式的。仅用于一个直接操作子组件的“逃生舱”, 尽量避免在模板或计算属性中访问 refs只会在组件渲染完成之后生效,并且他们不是响应式的。仅用于一个直接操作子组件的“逃生舱”,尽量避免在模板或计算属性中访问refs.
依赖注入
provide选项允许我们指定我们想要提供给后代组件的数据/方法。
<google-map>
<google-map-region v-bind:shape="cityBoundaries">
<google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
</google-map-region>
</google-map>
provide: function () {
return {
getMap: this.getMap
}
}
任何后代组件中,可以使用inject选项来接收指定的我们想要添加在这个实例上的属性。
inject: ['getMap']
依赖注入使我们不用担心我们可能会改变/移除一些子组件依赖的东西,
同时这些组件之间的接口是始终明确定义的,就和props一样。
负面影响:它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。
同时所提供的属性是非响应式的。
程序化的事件侦听器
- 通过 $on(eventName, eventHandler) 侦听一个事件
- 通过 $once(eventName,eventHandler) 一次性侦听一个事件
- 通过 $off(eventName, eventHandler) 停止侦听一个事件
循环引用
递归组件
组件是可以在它们自己的模板中调用自身的,通过name选项。
确保递归调用是条件性的
name: 'unique-name-of-my-component'
组件之间的循环引用
如果需要构建一个文件目录树,像访达或资源管理器那样,可以有一个组件
<p>
<span>{{ folder.name }}</span>
<tree-folder-contents :children="folder.children"/>
</p>
模板
<ul>
<li v-for="child in children">
<tree-folder v-if="child.children" :folder="child"/>
<span v-else>{{ child.name }}</span>
</li>
</ul>
模板定义的替代品
内联模板
当 inline-template 这个特殊的特性出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容。这使得模板的撰写工作更加灵活。
内联模板需要定义在Vue所属的DOM元素内。
<my-component inline-template>
<div>
<p>These are compiled as the component's own template.</p>
<p>Not parent's transclusion content.</p>
</div>
</my-component>
x-template
x-template模板需要定义在Vue所属的DOM元素外。
<script type="text/x-template" id="hello-world-template">
<p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
template: '#hello-world-template'
})
控制更新
强制更新
$forceUpdate
通过v-once创建低开销的静态组件
在根元素中添加v-once特性确保这些内容只计算一次然后缓存起来。
Vue.component('terms-of-service', {
template: `
<div v-once>
<h1>Terms of Service</h1>
... a lot of static content ...
</div>
`
})
过渡&动画
进入/离开&列表过渡
单元素/组件的过渡
Vue提供了transition的封装组件,下列情形,可以给任何元素和组件添加进入/离开过渡:
- 条件渲染v-if
- 条件展示v-show
- 动态组件
- 组件根节点
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#demo',
data: {
show: true
}
})
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
过渡的类名
在进入/离开的过渡中,会有6个class切换。
- v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入后的下一帧移除;
- v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
- v-enter-to:2.1.8版以上,定义进入过渡的结束状态。在元素被插入之后下一帧生效,在过渡/动画完成之后移除。
- v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
- v-leave-active:定义来开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
- v-leave-to:2.1.8版以上,定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效,在过渡/动画完成之后移除。
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 ,则 v- 是这些类名的默认前缀。如果你使用了 ,那么 v-enter 会替换为 my-transition-enter。
CSS过渡
<div id="example-1">
<button @click="show = !show">
Toggle render
</button>
<transition name="slide-fade">
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#example-1',
data: {
show: true
}
})
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
CSS动画
和css过渡的区别是,在动画中v-enter类名在节点插入DOM后不会立即删除,而是在animationend事件触发时删除。
自定义过渡的类名
通过以下特性来自定义过渡的类名:
- enter-class
- enter-active-class
- enter-to-class
- leave-class
- leave-active-class
- leave-to-class
优先级高于普通的类名
同时使用过渡和动画
使用type特性并设置animation或transition来明确声明你需要Vue监听的类型。
显性的过渡持续时间
默认情况下,Vue会等待其在过渡效果的根元素的第一个transitioned或animationed事件。
组件上的duration属性定制一个显性的过渡持续时间(以毫秒计)
<transition :duration="1000">...</transition>
定制进入和移出时间
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
JavaScript钩子
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>
// ...
methods: {
// --------
// 进入中
// --------
beforeEnter: function (el) {
// ...
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
enter: function (el, done) {
// ...
done()
},
afterEnter: function (el) {
// ...
},
enterCancelled: function (el) {
// ...
},
// --------
// 离开时
// --------
beforeLeave: function (el) {
// ...
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
leave: function (el, done) {
// ...
done()
},
afterLeave: function (el) {
// ...
},
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {
// ...
}
}
当只用JavaScript过渡的时候,在enter和leave中必须使用done进行回调,否则,它们将被同步调用,过渡会立即完成。
对于禁用JavaScript过渡的元素添加v-bind:css=“false”,Vue会跳过Css的检测,可以避免过程中Css的影响。
初始渲染的过渡
通过appear特性设置节点在初始渲染的过渡
<transition appear>
<!-- ... -->
</transition>
自定义css类名
<transition
appear
appear-class="custom-appear-class"
appear-to-class="custom-appear-to-class" (2.1.8+)
appear-active-class="custom-appear-active-class"
>
<!-- ... -->
</transition>
自定义JavaScript钩子
<transition
appear
v-on:before-appear="customBeforeAppearHook"
v-on:appear="customAppearHook"
v-on:after-appear="customAfterAppearHook"
v-on:appear-cancelled="customAppearCancelledHook"
>
<!-- ... -->
</transition>
多个元素的过渡
多个组件的过渡,对于原生标签可以使用v-if/v-else。
<transition>
<table v-if="items.length > 0">
<!-- ... -->
</table>
<p v-else>Sorry, no items found.</p>
</transition>
当有相同标签名的元素切换时,需要通过key特性设置唯一的值来标记以让Vue区分它们,
否则Vue为了效率只会替换相同标签内部的内容。
<transition>
<button v-if="isEditing" key="save">
Save
</button>
<button v-else key="edit">
Edit
</button>
</transition>
<transition>
<button v-bind:key="isEditing">
{{ isEditing ? 'Save' : 'Edit' }}
</button>
</transition>
<transition>
<button v-bind:key="docState">
{{ buttonMessage }}
</button>
</transition>
// ...
computed: {
buttonMessage: function () {
switch (this.docState) {
case 'saved': return 'Edit'
case 'edited': return 'Save'
case 'editing': return 'Cancel'
}
}
}
过渡模式
- in-out:新元素先进行过渡,完成之后当前元素过渡离开;
- out-in:当前元素先进行过渡,完成之后新元素过渡进入;
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
多个组件的过渡
不需要使用key,而是使用动态组件。
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
new Vue({
el: '#transition-components-demo',
data: {
view: 'v-a'
},
components: {
'v-a': {
template: '<div>Component A</div>'
},
'v-b': {
template: '<div>Component B</div>'
}
}
})
.component-fade-enter-active, .component-fade-leave-active {
transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to
/* .component-fade-leave-active for below version 2.1.8 */ {
opacity: 0;
}
列表过渡
渲染整个列表,使用组件,特点:
- 不同于,它会以一个真实元素呈现:默认为一个,也可以通过tag特性更换为其他元素;
- 过渡模式不可用,因为我们不再相互切换特有的元素;
- 内部元素总是需要提供唯一的key属性值;
- css过渡的类将应用在内部的元素中,而不是这个组/容器本身。
列表的进入/离开过渡
列表的排序过渡
组件不仅可以可以进入和离开动画,还可以改变定位。
v-move特性会在元素的改变定位的过程中应用,对于设置过渡的切换时机和过渡曲线非常有用。
Vue使用一个叫FLP简单的动画队列,使用transforms将元素从之前的位置平滑过渡新的位置。
FLP过渡的元素不能设置为display:inline,可以设置为display:inline-block或放flex中。
列表的交错过渡
通过data属性与JavaScript通信,就可以实现列表的交错过渡。
可复用的过渡
过渡可以通过Vue的组件系统实现复用。
要创建一个可复用过渡组件,需要将或作为根组件,然后将子组件放置在其中就可以 。
使用template
Vue.component('my-special-transition', {
template: '\
<transition\
name="very-special-transition"\
mode="out-in"\
v-on:before-enter="beforeEnter"\
v-on:after-enter="afterEnter"\
>\
<slot></slot>\
</transition>\
',
methods: {
beforeEnter: function (el) {
// ...
},
afterEnter: function (el) {
// ...
}
}
})
函数式组件:
Vue.component('my-special-transition', {
functional: true,
render: function (createElement, context) {
var data = {
props: {
name: 'very-special-transition',
mode: 'out-in'
},
on: {
beforeEnter: function (el) {
// ...
},
afterEnter: function (el) {
// ...
}
}
}
return createElement('transition', data, context.children)
}
})
动态过渡
动态过渡最基本的是通过name特性来绑定动态值。
<transition v-bind:name="transitionName">
<!-- ... -->
</transition>
创建动态过渡的最终方案是通过接受props来动态修改之前的过渡。
状态过渡
状态动画与侦听器
通过侦听器我们能监听到任何数值属性的数值更新。
动态状态过渡
数据背后状态过渡会实时更新。
把过渡放到组件里
管理太多的状态过渡会很快的增加Vue实例或组件的复杂性,很多的动画可以提取到专用的子组件。
赋予设计以生命
可复用性&组合
混入
基础
混入mixin提供了一种灵活的方式来分发Vue组件中的可复用功能。
一个混入对象可以包含任意组件选项。
当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
选项合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
数据对象在内部会进行递归合并,发生冲突时以组件数据优先
var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
new Vue({
mixins: [mixin],
data: function () {
return {
message: 'goodbye',
bar: 'def'
}
},
created: function () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
同名钩子函数将合为为一个数组,因此都将被调用。
混入对象的钩子将在组件自身钩子之前调用。
var mixin = {
created: function () {
console.log('混入对象的钩子被调用')
}
}
new Vue({
mixins: [mixin],
created: function () {
console.log('组件钩子被调用')
}
})
// => "混入对象的钩子被调用"
// => "组件钩子被调用"
值为对象的选项,如:methods、components、directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
var mixin = {
methods: {
foo: function () {
console.log('foo')
},
conflicting: function () {
console.log('from mixin')
}
}
}
var vm = new Vue({
mixins: [mixin],
methods: {
bar: function () {
console.log('bar')
},
conflicting: function () {
console.log('from self')
}
}
})
vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"
全局混入
一旦全局混入,将影响每一个之后创建的Vue实例。
/ 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
自定义选项合并策略
自定义选项将使用默认策略,即简单的覆盖已有值。
如果想让自定义选项以自定义逻辑合并,可以向Vue.config.optionMergeStrategies添加一个函数。
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// 返回合并后的值
}
对于多数值为对象的选项,可以使用与methods相同的合并策略
var strategies = Vue.config.optionMergeStrategies
strategies.myOption = strategies.methods
自定义指令
简介
Vue允许注册自定义指令
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
注册局部指令
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
使用
<input v-focus>
钩子函数
一个指令定义对象可以提供如下几个钩子函数:
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置;
- inserted:被绑定元素插入父节点时调用(仅保证父节点存在,不一定已被插入文档中);
- update:所在组件的VNode更新时调用,但可能发生在其子VNode更新之前。指令的值可能发生变化也可能没有。
- componentUpdated:指令所在组件的VNode及其子VNode全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
钩子函数参数
- el:指令所绑定的元素,可以用来直接操作 DOM
- binding:一个对象,包含以下属性:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2
- oldValue:指令绑定的前一个值,仅在 update 和componentUpdated 钩子中可用。无论值是否改变都可用。
- expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
- arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
- modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。 - vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
【除了el,其它参数都是只读的,不可修改】
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
动态指令参数
指令的参数是可以动态的。
函数简写
在bind和update时触发相同行为,而不关心其它钩子
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
对象字面量
如果需要传入多个值
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
渲染函数&JSX
基础
render函数渲染
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // 标签名称
this.$slots.default // 子节点数组
)
},
props: {
level: {
type: Number,
required: true
}
}
})
节点、树以及虚拟DOM
每个元素都是一个节点,每段文字也是一个节点,注释也是节点
一个节点就是页面的一部分,每个节点都可以有孩子节点。
你只需要告诉Vue你希望页面上的HTML是什么,这可以是在一个模板里:
<h1>{{ blogTitle }}</h1>
或render函数里:
render: function (createElement) {
return createElement('h1', this.blogTitle)
}
虚拟DOM
Vue通过建立一个虚拟的DOM来追踪自己要如何改变真实DOM。
return createElement('h1', this.blogTitle)
返回的并不是一个实际的DOM,可能是createNodeDescription,因为它所包含的信息
会告诉Vue页面上需要渲染什么样的节点,包括及其子节点的描述信息,
这样的节点就是“虚拟节点”,VNode。
“虚拟DOM”是我们对由Vue组件树建立起来的整个VNode树的称呼。
createElement参数
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中属性对应的数据对象。可选。
{
// (详情见下一节)
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
深入数据对象
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': {
foo: true,
bar: false
},
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML attribute
attrs: {
id: 'foo'
},
// 组件 prop
props: {
myProp: 'bar'
},
// DOM 属性
domProps: {
innerHTML: 'baz'
},
// 事件监听器在 `on` 属性内,
// 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
// 需要在处理函数中手动检查 keyCode。
on: {
click: this.clickHandler
},
// 仅用于组件,用于监听原生事件,而不是组件内部使用
// `vm.$emit` 触发的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
// 赋值,因为 Vue 已经自动为你进行了同步。
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// 作用域插槽的格式为
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其它特殊顶层属性
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。
refInFor: true
}
约束
VNode必须唯一:
组件树中的所有VNode必须是唯一的。
如果需要重复很多次的元素/组件,可以使用工厂函数来实现。
使用JavaScript代替模板功能
v-if和v-for
<ul v-if="items.length">
<li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>
等于:
props: ['items'],
render: function (createElement) {
if (this.items.length) {
return createElement('ul', this.items.map(function (item) {
return createElement('li', item.name)
}))
} else {
return createElement('p', 'No items found.')
}
}
v-model
渲染函数没有与v-model的直接对应–需要自己实现响应的逻辑
props: ['value'],
render: function (createElement) {
var self = this
return createElement('input', {
domProps: {
value: self.value
},
on: {
input: function (event) {
self.$emit('input', event.target.value)
}
}
})
}
事件&按键修饰符
对于.passive, .capture, .once这些事件修饰符,Vue提供了响应的前缀可以用于on
修饰符 | 前缀 |
---|---|
.passive | & |
.capture | ! |
.once | ~ |
.capture.once或.once.capture | ~! |
on: {
'!click': this.doThisInCapturingMode,
'~keyup': this.doThisOnce,
'~!mouseover': this.doThisOnceInCapturingMode
}
对于所有其它的修饰符,私有前缀都不是必须的,可以在事件处理函数中使用事件方法。
插槽
通过this.$slots访问静态插槽的内容,每个插槽都是一个VNode数组。
render: function (createElement) {
// `<div><slot></slot></div>`
return createElement('div', this.$slots.default)
}
也可以通过this.$scopedSlots访问作用域插槽,每个作用域插槽都是一个返回若干VNode的函数。
props: ['message'],
render: function (createElement) {
// `<div><slot :text="message"></slot></div>`
return createElement('div', [
this.$scopedSlots.default({
text: this.message
})
])
}
用渲染函数向子组件中传递作用域插槽,可以利用VNode数据对象中的scopedSlots字段。
render: function (createElement) {
return createElement('div', [
createElement('child', {
// 在数据对象中传递 `scopedSlots`
// 格式为 { name: props => VNode | Array<VNode> }
scopedSlots: {
default: function (props) {
return createElement('span', props.text)
}
}
})
])
}
JSX
Babel插件,用于在Vue中使用JSX语法,它可以让我们回到更接近于模板的语法上。
import AnchoredHeading from './AnchoredHeading.vue'
new Vue({
el: '#demo',
render: function (h) {
return (
<AnchoredHeading level={1}>
<span>Hello</span> world!
</AnchoredHeading>
)
}
})
将 h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。
我们会在以 ES2015 语法声明的含有 JSX 的任何方法和 getter 中 (不是函数或箭头函数中)
自动注入 const h = this.$createElement,这样你就可以去掉 (h) 参数了。
函数式组件
一个函数式组件应该像这样:
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
})
2.3.0之前版本,如果一个函数式组件想要接受prop,props选项时必须的。
2.3.0或以上的版本,可以省略props,所有组件的attribute都会自动隐式解析为prop
当使用函数式组件时,该引用会是HTMLElement,因为他们是无状态的也是无实例的。
组件需要的一切都是通过context参数传递的,包括以下字段:
- props:提供所有 prop 的对象
- children: VNode 子节点的数组
- slots: 一个函数,返回了包含所有插槽的对象
- scopedSlots: (2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
- data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
- parent:对父组件的引用
- listeners: (2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
- injections: (2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的属性。
因为函数式组件只是函数,所以渲染开销也低很多。
在作为包装组件时,也非常有用,如:
- 程序化地在多个组件中选择一个来代为渲染;
- 在将children、props、data传递给子组件之前操作他们;
向子元素或子组件传递attribute和事件
在普通组件中,没有被定义为 prop 的 attribute 会自动添加到组件的根元素上,将已有的同名 attribute 进行替换或与其进行智能合并。
然而函数式组件要求你显式定义该行为:
Vue.component('my-functional-button', {
functional: true,
render: function (createElement, context) {
// 完全透传任何 attribute、事件监听器、子节点等。
return createElement('button', context.data, context.children)
}
})
slots()和children对比
插件
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
- 添加全局方法或者属性。如: vue-custom-element
- 添加全局资源:指令/过滤器/过渡等。如 vue-touch
- 通过全局混入来添加一些组件选项。如 vue-router
- 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现
- 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
使用插件
通过全局方法Vue.use()使用插件,在调用new Vue之前完成
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
new Vue({
// ...组件选项
})
Vue.use会自动阻止多次注册相同插件,即使多次调用也只会注册一次该插件。
开发插件
Vue的插件应该暴露一个install方法,这个方法的第一个参数是Vue构造器,第二个参数是一个可选的选项对象。
过滤器
Vue可以自定义过滤器,用于文本格式化。
过滤器用在两个地方:
双花括号插值和v-bind表达式
过滤器应该添加在JavaScript表达式的尾部,由“管道”符号指示
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
或在一个组件的选项中定义本地的过滤器:
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
或在创建Vue实例之前全局定义过滤器:
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({
// ...
})
当全局过滤器和局部过滤器重名时,会采用局部过滤器。
过滤器总接收表达式的值作为第一个参数。
过滤器可以串联:
{{ message | filterA | filterB }}
filterA 被定义为接收单个参数的过滤器函数,
表达式 message 的值将作为参数传入到函数中。
然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,
将 filterA 的结果传递到 filterB 中。
过滤器是JavaScript函数,因此可以接收参数
{{ message | filterA('arg1', arg2) }}
filterA 被定义为接收三个参数的过滤器函数。
其中 message 的值作为第一个参数,
普通字符串 'arg1' 作为第二个参数,
表达式 arg2 的值作为第三个参数
TypeScript支持
推荐配置
// tsconfig.json
{
"compilerOptions": {
// 与 Vue 的浏览器支持保持一致
"target": "es5",
// 这可以对 `this` 上的数据属性进行更严格的推断
"strict": true,
// 如果使用 webpack 2+ 或 rollup,可以利用 tree-shake:
"module": "es2015",
"moduleResolution": "node"
}
}
开发工具链
工程创建
# 1. 如果没有安装 Vue CLI 就先安装
npm install --global @vue/cli
# 2. 创建一个新工程,并选择 "Manually select features (手动选择特性)" 选项
vue create my-project-name
基础用法
import Vue from 'vue'
const Component = Vue.extend({
// 类型推断已启用
})
const Component = {
// 这里不会有类型推断,
// 因为TypeScript不能确认这是Vue组件的选项
}
基于类的Vue组件
import Vue from 'vue'
import Component from 'vue-class-component'
// @Component 修饰符注明了此类为一个 Vue 组件
@Component({
// 所有的组件选项都可以放在这里
template: '<button @click="onClick">Click!</button>'
})
export default class MyComponent extends Vue {
// 初始数据可以直接声明为实例的属性
message: string = 'Hello!'
// 组件方法也可以直接声明为实例的方法
onClick (): void {
window.alert(this.message)
}
}
增加类型以配合插件使用
// 1. 确保在声明补充的类型之前导入 'vue'
import Vue from 'vue'
// 2. 定制一个文件,设置你想要补充的类型
// 在 types/vue.d.ts 里 Vue 有构造函数类型
declare module 'vue/types/vue' {
// 3. 声明为 Vue 补充的东西
interface Vue {
$myProperty: string
}
}
// ComponentOptions 声明于 types/options.d.ts 之中
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
myOption?: string
}
}
标注返回值
Vue的声明文件天生就有循环性,Ts可能在推断某个方法的类型的时候存在困难。
因此,可能需要在render或computed里的方法上标注返回值。
import Vue, { VNode } from 'vue'
const Component = Vue.extend({
data () {
return {
msg: 'Hello'
}
},
methods: {
// 需要标注有 `this` 参与运算的返回值类型
greet (): string {
return this.msg + ' world'
}
},
computed: {
// 需要标注
greeting(): string {
return this.greet() + '!'
}
},
// `createElement` 是可推导的,但是 `render` 需要返回值类型
render (createElement): VNode {
return createElement('div', this.greeting)
}
})
生成环境部署
开启生产环境模式
不使用构建工具
使用构建工具
webpack
在webpack4+中,使用mode选项
module.exports = {
mode: 'production'
}
在webpack3及更低版本,使用DefinePlugin:
var webpack = require('webpack')
module.exports = {
// ...
plugins: [
// ...
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
}
模板预编译
当使用DOM内模板或JavaScript内的字符串模板时,模板会在运行时被编译为渲染函数。
预编译模板最简单的方式就是使用单文件组件—相关的构建设置会自动把预编译处理好,
所以构建好的代码已经包含了编译出来的渲染函数,而不是原始的模板字符串。
使用webpack,可以用vue-template-loader。
提取组件CSS
webpack+vue-loader
跟踪运行时错误
Vue.config.errorHandler
路由
官方路由
vue-router
从零开始简单的路由
const NotFound = { template: '<p>Page not found</p>' }
const Home = { template: '<p>home page</p>' }
const About = { template: '<p>about page</p>' }
const routes = {
'/': Home,
'/about': About
}
new Vue({
el: '#app',
data: {
currentRoute: window.location.pathname
},
computed: {
ViewComponent () {
return routes[this.currentRoute] || NotFound
}
},
render (h) { return h(this.ViewComponent) }
})
状态管理
类Flux状态管理的官方实现
简单状态管理起步使用
store模式
所有store中state的改变,都放置在store自身的action中去管理。
这种集中式状态管理能够被更容易理解哪种类型的mutation将会发生,以及他们是如何被触发。
每个实例/组件仍然可以拥有和管理自己的私有状态。
不应该在action中替换原始的状态对象,组件和store需要引用同一个共享对象,mutation才能够被观察。
安全
报告安全漏洞
Vue的安全措施
HTML
不论使用模板还是渲染函数,内部都会被自动转义,避免了脚本注入。
Attribute绑定
动态attribute绑定也会自动被转义
潜在危险
注入HTML
Vue会自动转义Html内容,以避免向应用意外注入可执行的Html。然某些情况下,你清楚这些html是安全的,可以显示渲染Html内容:
1. 使用模板
<div v-html="userProvidedHtml"></div>
2. 使用渲染函数
h('div', {
domProps: {
innerHTML: this.userProvidedHtml
}
})
3. 使用基于JSX的渲染函数
<div domPropsInnerHTML={this.userProvidedHtml}></div>
注入URL
类似:
<a v-bind:href="userProvidedUrl">
click me
</a>
如果未对该URL进行过滤以防止通过JavaScript:来执行JavaScript,会有潜在安全问题。
注入样式
类似:
<a
v-bind:href="sanitizedUrl"
v-bind:style="userProvidedStyles"
>
click me
</a>
sanitizedUrl假如已被过滤,是一个真实的url且没有JavaScript,但通过userProvidedStyles,恶意用户扔可以提供CSS来进行“点击诈骗”。
推荐只允许在一个iframe沙盒内进行CSS的完全控制,或让用户通过一个样式绑定来控制,推荐使用对象语法且只允许用户提供特定的可以安全控制的property的值。
<a
v-bind:href="sanitizedUrl"
v-bind:style="{
color: userProvidedColor,
background: userProvidedBackground
}"
>
click me
</a>
注入JavaScript
我们强烈不鼓励使用 Vue 渲染
每个 HTML 元素都有接受 JavaScript 字符串作为其值的 attribute,如 onclick、onfocus 和 onmouseenter。将用户提供的 JavaScript 绑定到它们任意当中都是一个潜在的安全风险,因此应该避免。
内在
深入响应式原理
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
检测变化的注意事项
Vue无法检测到对象属性的添加或删除。
由于Vue会在初始化实例时对属性执行getter/setter转化,所以属性必须在data对象上存在才能让Vue将它转化为响应式的。
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的
对于已经创建的实例,Vue不允许动态添加根级别的响应式属性,可以使用Vue.set(Object, propertyName, value)方法向嵌套对象添加响应式属性。
也可以使用vm.$set实例方法,即全局this.set的别名
对已有对象赋值多个新属性,使用Object.assign()或_.extend(),并将原对象与混合进去的对象一起创建一个新的对象。
Vue.set(vm.someObject, 'b', 2)
this.$set(this.someObject,'b',2)
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
声明响应式属性
Vue不允许动态添加跟级别响应式属性,需要再初始化实例前声明所有根级别响应式属性,哪怕是空值
var vm = new Vue({
data: {
// 声明 message 为一个空值字符串
message: ''
},
template: '<div>{{ message }}</div>'
})
// 之后设置 `message`
vm.message = 'Hello!'
异步更新队列
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个 watcher 被多次触发,只会被推入到队列中一次。