之前做项目是前后端都会做的,主要用的是Vue,定期来温故而知新一下。
在官网的基础上整理了一下,并总结了一些常见的知识点。
是一套基于构建用户界面的渐进式框架;
自底向上逐层应用;
vue的核心库只关注图层;
完全能够单页应用提供驱动;
npm install vue
Vue的核心是一个允许采用简洁的模板语法来声明式的将数据渲染进DOM的系统。
{{ message }}
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
// Hello Vue!
指令:带有前缀v-,表示是Vue提供的特殊特性。
鼠标悬停几秒钟查看此处动态绑定的提示信息!
var app2 = new Vue({
el: '#app-2',
data: {
message: '页面加载于 ' + new Date().toLocaleString()
}
})
v-if、v-else、v-for
现在你看到我了
看不到我了
-
{{ todo.text }}
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实现表单输入和应用状态之间的双向绑定。
{{ message }}
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: '这是个待办项 '
})
var app = new Vue(...)
// 通过从父作用域将数据传到子组件
Vue.component('todo-item', {
// todo-item 组件现在接受一个
// "prop",类似于一个自定义特性。
// 这个 prop 名为 todo。
props: ['todo'],
template: '{{ todo.text }} '
})
var app7 = new Vue({
el: '#app-7',
data: {
groceryList: [
{ id: 0, text: '蔬菜' },
{ id: 1, text: '奶酪' },
{ id: 2, text: '随便其它什么人吃的东西' }
]
}
})
1. 创建一个Vue实例;
2. vue实例创建后,会将data对象中的所有属性加入到Vue的响应式系统中,当属性的值发生改变时,视图将会产生‘响应’,匹配更新后的值;
var data = { a: 1 } 数据对象:初始值
// Object.freeze(obj) 会阻止修改现有的属性,意味着响应系统无法再追踪变化。
var vm = new Vue({
// 选项
data: data
})
新对象诞生。
在对象初始化之前执行。
创建具有默认特性的对象。
对象在DOM中适合形状。
检查是否有任何模板可用于要在DOM中呈现的对象。
没有找到,会将HTML视为模板。
已安装。DOM已准备就绪并放置在页面内。
它将数据放入模板并创建可呈现元素。
更改已完成,但尚未准备好更新DOM。
更新,在DOM中呈现的更改。
对象准备销毁。
销毁。对象停止并从内存中删除。
数据绑定最常见的形式就是用双大括号Mustache语法的文本插值
Message: {{ msg }}
使用v-once指令,也能一次性的插值,当数据改变时,插值处的内容不会更新。
这个将不会改变: {{ msg }}
为了输出真正的HTML,需要使用v-html指令。
Mustache语法不能作用在Html attribute上,,应使用v-bind指令。
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
DIrectives是带有v-前缀的特殊attribute。
指令的职责是,当表达式的值改变时,将其产生的连带影响,相应式的作用于DOM。
一些指令接收一个参数,在指令名称之后以冒号表示。
...
在这里 href 是参数,告知 v-bind 指令将该元素的 href attribute 与表达式 url 的值绑定。
v-on也可以用于监听DOM事件。
...
...
...
动态参数的值的约束:
动态参数预期会求出一个字符串,异常为null,null值会被显性的用于移除绑定。
对动态参数表达式的约束:
空格、引号放在Html attribute名里是无效的。
修饰符以半角句号.指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。
v-前缀用来识别模板中Vue特殊的attribute。
...
...
...
...
可以将同一函数定义为一个方法而不是一个计算属性。
计算属性是基于它们的响应式依赖进行缓存的。
当一些数据随着其它数据的变动而改变时,更好的做法是使用计算属性。
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
计算属性默认只有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选项来响应数据的变化,当需要再数据变化时执行异步或开销较大的操作比较有用。
传给v-bind:class一个对象(可以传入更多的属性),以动态切换class
三元表达式
1. 声明组件
Vue.component('my-component', {
template: '
3. HTML被渲染为
'
})
2. 给组件添加class
v-bind:style
data: {
activeColor: 'red',
fontSize: 30
}
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
当v-bind:style使用需要添加浏览器引擎前缀的CSS属性时,vue会自动侦测并添加相应的前缀。
2.3.0后可以为style绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值。
必须添加到一个元素上
A
B
C
Not A/B/C
带有v-show的元素始终会被渲染并保留在DOM中。
Hello!
v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当的被销毁和重建。
v-if是惰性的:如果初始渲染时条件为假,则什么也不做,直到条件第一次为真时,才会开始渲染条件块。
v-show则是不管初始条件是什么,元素总是会被渲染,并且只是简单的基于CSS进行切换。
一般来说,v-if有更高的切换开销,v-show有更高的初始渲染开销。
如果频繁切换,使用v-show较好;
如果运行时条件很少改变,使用v-if较好。
一起使用时,v-for会有更高的优先级。
v-for把一个数组(或对象)对应为一组元素。
-
{{ parentMessage }} - {{ index }} - {{ item.message }}
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
用of也可以代替in
Vue正在更新使用v-for渲染的元素列表,默认使用“就地更新”策略。
如果数据项的顺序被改变,Vue将不会移动DOM元素来皮牌数据项的顺序,而是就地更新每个元素,并确保他们在每个索引位置正确渲染。
Vue将被侦听的数组的变异方法进行了包裹,所以它们也会触发视图更新:
非变异方法则不会改变原始数组,总是返回一个新的数组,可使用新数组替换旧数组,如: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'
})
可以创建一个计算属性来返回过滤或排序后的数组。
{{ n }}
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
计算属性不适用时,使用方法:
{{ n }}
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
v-for的优先级比v-if高
2.2.0+版本里,v-for使用时,key是必须的。
v-on指令监听DOM事件
v-on:click接收一个需要调用的方法名
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})
使用修饰符时,顺序很重要;
相应的代码会以同样的顺序产生。
...
...
阻止所有的点击
v-on:click.prevent.self
阻止对元素自身的点击
v-on:click.self.prevent
2.1.4新增
2.3.0新增
...
【不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,
同时浏览器可能会向你展示一个警告。
请记住,.passive 会告诉浏览器你不想阻止事件的默认行为。】
v-on监听键盘事件时添加按键修饰符
允许你控制由精确的系统修饰符组合触发的事件。
会限制处理函数仅响应特定的鼠标按钮。
v-model负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model会忽略所有表单元素的value、checked、selected特性的初始值而总是将Vue实例的数据作为数据来源。
v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件:
默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步,添加lazy修饰,从而转变为使用change事件进行同步
自动将用户的输入值转为数值类型
自动过滤用户输入的首尾空白字符
组件是可复用的Vue实例,且带有一个名字
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: ''
})
可以复用任意多次
一个组件的data必须是一个函数
data: function () {
return {
count: 0
}
}
组件的注册类型:
每个组件必须只有一个根元素。
$emit
$event
等价于:
Vue.component('custom-input', {
props: ['value'],
template: `
`
})
:向一个组件传递内容
Vue.component('alert-box', {
template: `
Error!
`
})
在不同组件之间进行动态切换,vue的元素加一个特殊的is特性来实现
字母全小写,且包含一个连字符。
**使用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名需要使用其等价的短横线命名。
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '{{ postTitle }}
'
})
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
v-bind动态赋值
改变prop:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
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类型:
一个非prop的attribute是指传向一个组件,但该组件并没有相应prop定义的attribute。
从外部提供给组件的值会替换掉组件内部设置好的值。
class与style attribute会使值合并起来。
如果不希望组件的根元素继承attribute,可以在组件的选项中设置inheritAttrs: false,
Vue.component('my-component', {
inheritAttrs: false,
// ...
})
配合实例$attrs属性使用,可以手动决定这些attribute会被赋予哪个元素。
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
`
})
inheritAttrs: false选项不会影响style和class的绑定。
该模式允许你在使用基础组件的时候更像是使用原始的HTML元素,而不会担心哪个元素是根元素。
事件名不存在任何自动化大小写转换。
推荐使用kebab-case事件名。
一个组件上的v-model默认会利用名为value的prop和名为input的事件。
但是单选框和复选框等输入控件可能会将value特性用于不同目的。
model可以避免这样的冲突
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
`
})
lovingVue值将会传入这个名为checked的prop
v-on.native修饰符
有时候这种情况不是很好的,比如.native监听器将静默失败
解决这个问题,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: `
`
})
双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。
通过.sync修饰符:
带有.sync修饰符的v-bind不能和表达式一起使用
当用一个对象同时设置多个prop的时候,也可以将.sync修饰符和v-bind配合使用。
doc对象中的每个属性都作为一个独立的prop传进去,然后各自添加用于更新的v-on监听器。
将v-bind.sync用在一个字面量的对象上,如:v-bind.sync=”{ title: doc.title }”是无法工作的,因为要考虑很多边缘因素。
Vue实现了一套内容分发的API,这套API设计将元素作为承载分发内容的出口。
合成组件
Your Profile
模板中可能写成:
当组件渲染的时候, 就会替换为“Your Profile”,插槽内可以包含任何模板代码
如果没有包含一个元素,则该组件起始标签和结束标签之间的任何内容都会抛弃。
父级模板里的所有内容都是在父级作用域中编译的;
子模板里的所有内容都是在子作用域中编译的。
Clicking here will send you to: {{ url }}
为一个插槽设置具体的后备(默认的)内容是很有用的,它只会在没有提供内容的时候被渲染。
v-slot只能添加在上
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
绑定在元素上的特性被称为插槽prop。
将包含所有插槽prop的对象命名为slotProps。
在父级作用域中,我们可以使用带值的v-slot来定义我们提供的插槽prop的名字。
{{ slotProps.user.firstName }}
当被提供的内容只有默认插槽时,组件的标签才可以被当做插槽的模板来使用。
未指明的内容对应默认插槽,不带参数的v-slot被假定对应默认插槽。
{{ slotProps.user.firstName }}
默认插槽的缩写语法不能和具名插槽混用,会导致作用域不明确
只要出现多个插槽,始终为所有的插槽使用完整的基于语法
{{ slotProps.user.firstName }}
...
作用域插槽的内部工作原理是将你的插槽内容包含在一个传入单个参数的函数里
可以使用ES5解构来传入具体的插槽prop
{{ user.firstName }}
{{ person.firstName }}
动态指令参数也可以来定义动态的插槽名
...
v-slot缩写,把参数之前的所有内容替换为#
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
插槽prop允许我们将插槽转换为可复用的模板,这些模板可以基于输入的prop渲染出不同的内容。
在多标签的界面中使用is特性来切换不同的组件。
要求被切换到的组件都有自己的名字,不论是通过组件的name选项还是局部/全局注册。
Vue允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。
Vue只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: 'I am async!'
})
}, 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引用。
通过this.$refs.usernameInput 访问实例
使用一个类似的ref提供对内部这个指定元素的访问
通过父级组件定义方法
methods: {
// 用来从父级组件聚焦输入框
focus: function () {
this.$refs.input.focus()
}
}
这样就允许父级组件通过下面的代码聚焦 里的输入框:
this.$refs.usernameInput.focus()
ref和v-for一起使用的时候,得到的引用将会是一个包含了对应数据源的这些子组件的数组。
r e f s 只 会 在 组 件 渲 染 完 成 之 后 生 效 , 并 且 他 们 不 是 响 应 式 的 。 仅 用 于 一 个 直 接 操 作 子 组 件 的 “ 逃 生 舱 ” , 尽 量 避 免 在 模 板 或 计 算 属 性 中 访 问 refs只会在组件渲染完成之后生效,并且他们不是响应式的。仅用于一个直接操作子组件的“逃生舱”, 尽量避免在模板或计算属性中访问 refs只会在组件渲染完成之后生效,并且他们不是响应式的。仅用于一个直接操作子组件的“逃生舱”,尽量避免在模板或计算属性中访问refs.
provide选项允许我们指定我们想要提供给后代组件的数据/方法。
provide: function () {
return {
getMap: this.getMap
}
}
任何后代组件中,可以使用inject选项来接收指定的我们想要添加在这个实例上的属性。
inject: ['getMap']
依赖注入使我们不用担心我们可能会改变/移除一些子组件依赖的东西,
同时这些组件之间的接口是始终明确定义的,就和props一样。
负面影响:它将你应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。
同时所提供的属性是非响应式的。
组件是可以在它们自己的模板中调用自身的,通过name选项。
确保递归调用是条件性的
name: 'unique-name-of-my-component'
如果需要构建一个文件目录树,像访达或资源管理器那样,可以有一个组件
{{ folder.name }}
模板
-
{{ child.name }}
当 inline-template 这个特殊的特性出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容。这使得模板的撰写工作更加灵活。
内联模板需要定义在Vue所属的DOM元素内。
These are compiled as the component's own template.
Not parent's transclusion content.
x-template模板需要定义在Vue所属的DOM元素外。
Vue.component('hello-world', {
template: '#hello-world-template'
})
$forceUpdate
在根元素中添加v-once特性确保这些内容只计算一次然后缓存起来。
Vue.component('terms-of-service', {
template: `
Terms of Service
... a lot of static content ...
`
})
Vue提供了transition的封装组件,下列情形,可以给任何元素和组件添加进入/离开过渡:
hello
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- 是这些类名的默认前缀。如果你使用了 ,那么 v-enter 会替换为 my-transition-enter。
hello
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过渡的区别是,在动画中v-enter类名在节点插入DOM后不会立即删除,而是在animationend事件触发时删除。
通过以下特性来自定义过渡的类名:
优先级高于普通的类名
使用type特性并设置animation或transition来明确声明你需要Vue监听的类型。
默认情况下,Vue会等待其在过渡效果的根元素的第一个transitioned或animationed事件。
组件上的duration属性定制一个显性的过渡持续时间(以毫秒计)
...
定制进入和移出时间
...
// ...
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特性设置节点在初始渲染的过渡
自定义css类名
自定义JavaScript钩子
多个组件的过渡,对于原生标签可以使用v-if/v-else。
Sorry, no items found.
当有相同标签名的元素切换时,需要通过key特性设置唯一的值来标记以让Vue区分它们,
否则Vue为了效率只会替换相同标签内部的内容。
// ...
computed: {
buttonMessage: function () {
switch (this.docState) {
case 'saved': return 'Edit'
case 'edited': return 'Save'
case 'editing': return 'Cancel'
}
}
}
不需要使用key,而是使用动态组件。
new Vue({
el: '#transition-components-demo',
data: {
view: 'v-a'
},
components: {
'v-a': {
template: 'Component A'
},
'v-b': {
template: 'Component B'
}
}
})
.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;
}
渲染整个列表,使用组件,特点:
组件不仅可以可以进入和离开动画,还可以改变定位。
v-move特性会在元素的改变定位的过程中应用,对于设置过渡的切换时机和过渡曲线非常有用。
Vue使用一个叫FLP简单的动画队列,使用transforms将元素从之前的位置平滑过渡新的位置。
FLP过渡的元素不能设置为display:inline,可以设置为display:inline-block或放flex中。
通过data属性与JavaScript通信,就可以实现列表的交错过渡。
过渡可以通过Vue的组件系统实现复用。
要创建一个可复用过渡组件,需要将或作为根组件,然后将子组件放置在其中就可以 。
使用template
Vue.component('my-special-transition', {
template: '\
\
\
\
',
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特性来绑定动态值。
创建动态过渡的最终方案是通过接受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()
}
}
}
使用
一个指令定义对象可以提供如下几个钩子函数:
【除了el,其它参数都是只读的,不可修改】
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '
' +
'value: ' + s(binding.value) + '
' +
'expression: ' + s(binding.expression) + '
' +
'argument: ' + s(binding.arg) + '
' +
'modifiers: ' + s(binding.modifiers) + '
' +
'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
})
如果需要传入多个值
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
render函数渲染
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // 标签名称
this.$slots.default // 子节点数组
)
},
props: {
level: {
type: Number,
required: true
}
}
})
每个元素都是一个节点,每段文字也是一个节点,注释也是节点
一个节点就是页面的一部分,每个节点都可以有孩子节点。
你只需要告诉Vue你希望页面上的HTML是什么,这可以是在一个模板里:
{{ blogTitle }}
或render函数里:
render: function (createElement) {
return createElement('h1', this.blogTitle)
}
Vue通过建立一个虚拟的DOM来追踪自己要如何改变真实DOM。
return createElement('h1', this.blogTitle)
返回的并不是一个实际的DOM,可能是createNodeDescription,因为它所包含的信息
会告诉Vue页面上需要渲染什么样的节点,包括及其子节点的描述信息,
这样的节点就是“虚拟节点”,VNode。
“虚拟DOM”是我们对由Vue组件树建立起来的整个VNode树的称呼。
// @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 }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其它特殊顶层属性
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。
refInFor: true
}
VNode必须唯一:
组件树中的所有VNode必须是唯一的。
如果需要重复很多次的元素/组件,可以使用工厂函数来实现。
- {{ item.name }}
No items found.
等于:
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的直接对应–需要自己实现响应的逻辑
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) {
// ` `
return createElement('div', this.$slots.default)
}
也可以通过this.$scopedSlots访问作用域插槽,每个作用域插槽都是一个返回若干VNode的函数。
props: ['message'],
render: function (createElement) {
// ` `
return createElement('div', [
this.$scopedSlots.default({
text: this.message
})
])
}
用渲染函数向子组件中传递作用域插槽,可以利用VNode数据对象中的scopedSlots字段。
render: function (createElement) {
return createElement('div', [
createElement('child', {
// 在数据对象中传递 `scopedSlots`
// 格式为 { name: props => VNode | Array }
scopedSlots: {
default: function (props) {
return createElement('span', props.text)
}
}
})
])
}
Babel插件,用于在Vue中使用JSX语法,它可以让我们回到更接近于模板的语法上。
import AnchoredHeading from './AnchoredHeading.vue'
new Vue({
el: '#demo',
render: function (h) {
return (
Hello world!
)
}
})
将 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参数传递的,包括以下字段:
因为函数式组件只是函数,所以渲染开销也低很多。
在作为包装组件时,也非常有用,如:
在普通组件中,没有被定义为 prop 的 attribute 会自动添加到组件的根元素上,将已有的同名 attribute 进行替换或与其进行智能合并。
然而函数式组件要求你显式定义该行为:
Vue.component('my-functional-button', {
functional: true,
render: function (createElement, context) {
// 完全透传任何 attribute、事件监听器、子节点等。
return createElement('button', context.data, context.children)
}
})
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
通过全局方法Vue.use()使用插件,在调用new Vue之前完成
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
new Vue({
// ...组件选项
})
Vue.use会自动阻止多次注册相同插件,即使多次调用也只会注册一次该插件。
Vue的插件应该暴露一个install方法,这个方法的第一个参数是Vue构造器,第二个参数是一个可选的选项对象。
Vue可以自定义过滤器,用于文本格式化。
过滤器用在两个地方:
双花括号插值和v-bind表达式
过滤器应该添加在JavaScript表达式的尾部,由“管道”符号指示
{{ message | capitalize }}
或在一个组件的选项中定义本地的过滤器:
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 的值作为第三个参数
// 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组件的选项
}
import Vue from 'vue'
import Component from 'vue-class-component'
// @Component 修饰符注明了此类为一个 Vue 组件
@Component({
// 所有的组件选项都可以放在这里
template: ''
})
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 {
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。
webpack+vue-loader
Vue.config.errorHandler
vue-router
const NotFound = { template: 'Page not found
' }
const Home = { template: 'home page
' }
const About = { template: 'about page
' }
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) }
})
store模式
所有store中state的改变,都放置在store自身的action中去管理。
这种集中式状态管理能够被更容易理解哪种类型的mutation将会发生,以及他们是如何被触发。
每个实例/组件仍然可以拥有和管理自己的私有状态。
不应该在action中替换原始的状态对象,组件和store需要引用同一个共享对象,mutation才能够被观察。
不论使用模板还是渲染函数,内部都会被自动转义,避免了脚本注入。
动态attribute绑定也会自动被转义
Vue会自动转义Html内容,以避免向应用意外注入可执行的Html。然某些情况下,你清楚这些html是安全的,可以显示渲染Html内容:
1. 使用模板
2. 使用渲染函数
h('div', {
domProps: {
innerHTML: this.userProvidedHtml
}
})
3. 使用基于JSX的渲染函数
类似:
click me
如果未对该URL进行过滤以防止通过JavaScript:来执行JavaScript,会有潜在安全问题。
类似:
click me
sanitizedUrl假如已被过滤,是一个真实的url且没有JavaScript,但通过userProvidedStyles,恶意用户扔可以提供CSS来进行“点击诈骗”。
推荐只允许在一个iframe沙盒内进行CSS的完全控制,或让用户通过一个样式绑定来控制,推荐使用对象语法且只允许用户提供特定的可以安全控制的property的值。
click me
我们强烈不鼓励使用 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: '{{ message }}'
})
// 之后设置 `message`
vm.message = 'Hello!'
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个 watcher 被多次触发,只会被推入到队列中一次。