组件

什么是组件

组件是Vue的最强大的特性之一,他们帮你扩展基于Html元素来封装可复用的代码,在较高的水平上
组件是自定义元素,是Vue编译器的附加行为。在一些场景中,它们可能在一些原生的Html元素以is
属性的扩展形式出现。

使用组件

注册

通过以下方式创建vue实例:

1
2
3
4
new Vue({
el: '#some-element',
// options
})

可以使用Vue.component(tagName, options)注册一个全局组件,例如:

1
2
3
Vue.component('my-component', {
// options
})

注意:Vue不强制用W3c的规则命名(都小写,使用-连字符),但是遵守这个规则是好的做法。
只要一注册,组件可以在一个vue实例中作为一个自定义元素使用,<my-component></my-component>.
在初始化vue实例时请确保组件已经注册了。比如:

1
2
3
4
5
6
7
8
9
10
11
12
<div id="example">
<my-component></my-component>
</div>

// register
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// create a root instance
new Vue({
el: '#example'
})

将会渲染为:

1
2
3
<div id="example">
<div>A custom component!</div>
</div>

局部注册

没有必要将每一个组件都注册为全局的,你可以在一个实例或组件内部通过components属性局部的
使用组件:

1
2
3
4
5
6
7
8
9
10
var Child = {
template: '<div>A custom component!</div>'
}
new Vue({
// ...
components: {
// <my-component> will only be available in parent's template
'my-component': Child
}
})

DOM模板解析警告

当使用DOM作为你的模板时(比如:使用el选项来装载具有内容的元素),你会受到HTML固有的工作
限制,因为只有浏览器解析并初始化完成后Vue才会得到模板的内容,最值得注意的是,一些元素比如
<ul>,<ol>,<table>,<select>对他们里面的元素有严格地限制,比如<option>只能出现
在特定元素的里面。当在这些具有严格限制的HTNL元素中使用模板时,该如何做呢:

1
2
3
<table>
<my-row>...</my-row>
</table>

自定义的组件<my-row>将会被提升作为合法的内容,但是最终的输出结果会引起错误,使用is
这个属性作为变通方案:

1
2
3
<table>
<tr is="my-row"></tr>
</table>

如果你使用以下的来源是使用字符串模板,将不会受到以上限制:

  • <script type="text/x-template">
  • js行内字符串模板
  • .vue组件
    因此,尽可能使用字符串模板。

data必须是一个函数

模板的data必须是一个函数:

1
2
3
4
5
6
Vue.component('my-component', {
template: '<span>{{ message }}</span>',
data: {
message: 'hello'
}
})

Vue将会暂停并在控制台打印错误,告诉你data必须是一个函数或者是组件实例,这个规则的存在
很容易理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>

var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// data is technically a function, so Vue won't
// complain, but we return the same object
// reference for each component instance
data: function () {
return data
}
})
new Vue({
el: '#example-2'
})

由于这三个组件实例共享同一个data,当增加其中一个值时所有的值都会增加,通过返回一个全新
的对象来修复这个问题:

1
2
3
4
5
data: function () {
return {
counter: 0
}
}

现在每一个组件都有自己的内部状态。

组件的组合

组件可以在一起使用,最常见的就是父子关系:组件A可能在自己的模板中使用组件B。他们必然会
互相通信:父组件会给子组件传递props,子组件会给父组件报告在子组件内发生了什么。然而通
过一个明确定义的接口来保持父子组件的解耦也是非常重要的。这可以确保每个组件在相对隔离的情
况下也可书写与推理,因此书写和维护变得更简单了。在Vue.js中,父子组件的通信可以概括为props
向下传递,事件向上传递。父组件通过props给子组件传递data,子组件通过事件给父组件传递
消息。

Props

通过props传递打他

每一个组件实例都有自己的作用域。这意味着你不能在子组件的模板中直接使用父组件的data,父
组件的data可以通过props传递到子组件。props是一个自定义的属性用来传递父组件的信息,
子组件通过props选项来明确的声明它期望获得的数据:

1
2
3
4
5
6
7
8
9
10
Vue.component('child', {
// declare the props
props: ['message'],
// just like data, the prop can be used inside templates
// and is also made available in the vm as this.message
template: '<span>{{ message }}</span>'
})

//我们可以这样传递一个简单的字符串给child组件
<child message="hello!"></child>

驼峰式vs-分界符

HTML的属性是不区分大小写的,所以当使用非字符串模板时,驼峰式属性吗需要用他们的’-‘分界符
命名规则来替换:

1
2
3
4
5
6
7
8
Vue.component('child', {
// camelCase in JavaScript
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})

<!-- kebab-case in HTML -->
<child my-message="hello!"></child>

如果使用的是字符串模板,没有这个限制。

动态的Props

和属性绑定普通表达式类似,在父组件中可以使用v-bind动态的绑定data和props,不论父组件的
data是否改变,它始终会传递给子组件:

1
2
3
4
5
6
7
8
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>

//使用v-bind的简写形式:
<child :my-message="parentMsg"></child>

Literal(文本) VS Dynamic

一个常犯的错误就是使用文本语法传递一个数字:

1
2
<!-- this passes down a plain string "1" -->
<comp some-prop="1"></comp>

然而,由于这是一个文本属性,传递的数字是字符串类型的,如果要传递一个js的number类型,我们
需要使用v-bind它会将值预估为js的表达式:

1
2
<!-- this passes down an actual number -->
<comp v-bind:some-prop="1"></comp>

单项数据流

在父子组件中所有的数据都是单向流动的:当父组件的属性改变时,它会流向子组件,但是子组件的
变化不会流向父组件。这样可以阻止子组件意外变化而引起父组件状态的突变,这会让你的应用程序
很难找到原因。
另外,任何时候只要父组件更新了,子组件的所有props都会刷新到最近的值,这意味着你不要尝试
改变子组件的prop,如果你做了,Vue会在控制台警告你。
通常有两种情况你要改变prop:

  1. prop只是用来传递一个初始值,子组件只是想用它做一个局部的对象属性。
  2. prop作为一个原始值传递
    适当的使用场景是:
  3. 定义一个局部data属性,使用prop’s的初始值作为它的值:
1
2
3
4
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
  1. 定义一个属性的计算,通过prop’s的值计算:
1
2
3
4
5
6
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}

注意对象和数组是引用传递,所以当传递数组和对象给子组件时,在子组件修改它会影响父组件的
状态。

Prop验证

定义一个明确的获得的prop类型是很有用的,如果要求不满足,Vue会报警告,当多人开发时是很
有用的:

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
26
27
28
29
30
31
32
Vue.component('example', {
props: {
// basic type check (`null` means accept any type)
propA: Number,
// multiple possible types
propB: [String, Number],
// a required string
propC: {
type: String,
required: true
},
// a number with default value
propD: {
type: Number,
default: 100
},
// object/array defaults should be returned from a
// factory function
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// custom validator function
propF: {
validator: function (value) {
return value > 10
}
}
}
})

type有以下原始类型:

  • String
  • Number
  • Bollean
  • Function
  • Object
  • Array
    另外,type可以是自定义构造函数的类型,通过instanceof校验。

自定义事件

子组件如何和父组件通信呢?使用Vue的自定义事件系统。

使用v-on来定义事件

每一个Vue实例都实现了事件接口,

  • 使用$on(eventName)监听事件
  • 使用$emit(eventName)触发事件
    注意Vue的事件系统和浏览器的事件API是分开的。
    尽管他们工作类似,但是$on$emit不是addEventListenerdispatchEvent的别名。
    另外,父组件可以监听子组件使用$on发出的事件:
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
26
27
28
29
30
31
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>

Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})

绑定原生事件到组件
v-on指令添加后缀.native:

1
<my-component v-on:click.native="doTheThing"></my-component>

form表单组件使用自定义事件

使用v-modle来创建自定义表单组件:

1
<input v-model="something">

仅仅是语法糖:

1
<input v-bind:value="something" v-on:input="something = $event.target.value">

当在组件中使用时:

1
<custom-input v-bind:value="something" v-on:input="something = arguments[0]"></custom-input>

目前组件与v-modle的工作,它必须:

  • 接收一个value属性
  • 放射出一个input事件
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
26
27
28
29
30
31
32
33
34
35
<currency-input v-model="price"></currency-input>


Vue.component('currency-input', {
template: '\
<span>\
$\
<input\
ref="input"\
v-bind:value="value"\
v-on:input="updateValue($event.target.value)"\
>\
</span>\
',
props: ['value'],
methods: {
// Instead of updating the value directly, this
// method is used to format and place constraints
// on the input's value
updateValue: function (value) {
var formattedValue = value
// Remove whitespace on either side
.trim()
// Shorten to 2 decimal places
.slice(0, value.indexOf('.') + 3)
// If the value was not already normalized,
// manually override it to conform
if (formattedValue !== value) {
this.$refs.input.value = formattedValue
}
// Emit the number value through the input event
this.$emit('input', Number(formattedValue))
}
}
})

上面的这个例子很天真,可以参考这个

非父子组件通信

当两个不是父子关系的组件该如何通信呢?一个简单的场景,你可以创建一个vue空实例作为中央事
件汽车:

1
2
3
4
5
6
7
8
9
10
var bus = new Vue()

// in component A's method
bus.$emit('id-selected', 1)


// in component B's created hook
bus.$on('id-selected', function (id) {
// ...
})

在更复杂的场景中,可以采用专门的事件管理模式

内容分发槽

当使用组件时,经常想要组合他们:

1
2
3
4
<app>
<app-header></app-header>
<app-footer></app-footer>
</app>

需要注意两件事:

  1. <app>组件不知道在其内部要呈现的内容是什么.
  2. <app>组件看起来像是有自己的模板。
    使用特殊的<slot>元素。

编辑范围

想象一个这样的模板:

1
2
3
<child-component>
{{ message }}
</child-component>

message会绑定到父组件的data还是子组件的data么?它会绑定到父组件的data。组件范围的一个
规则是:
在父模板中的一切都是在父模板范围内编译,在模板就是在子模板范围内编译。
一个常见的错误就是尝试在子模板中绑定一个子模板的property/method:

1
2
<!-- does NOT work -->
<child-component v-show="someChildProperty"></child-component>

假设someChildProperty是子组件的prop,上面的例子不能运行,因为父模板不会意识到子组件的
状态。如果你需要绑定子范围的指令到一个组件的根节点上,你需要在子组件范围的模板上绑定:

1
2
3
4
5
6
7
8
9
Vue.component('child-component', {
// this does work, because we are in the right scope
template: '<div v-show="someChildProperty">Child</div>',
data: function () {
return {
someChildProperty: true
}
}
})

单个插槽

父内容将会被抛弃除非子组件模板上至少包含一个<slot>插槽。当只有一个没任何属性的插槽时,
全部内容都会被替换。<slot>的原始内容会被作为回退内容,如果持有组件是空的或者没有内容
金额插入式回退内容才会显示。有一个<my-component>的组件:

1
2
3
4
5
6
7
<div>
<h2>I'm the child title</h2>
<slot>
This will only be displayed if there is no content
to be distributed.
</slot>
</div>

当一个父组件使用它时:

1
2
3
4
5
6
7
<div>
<h1>I'm the parent title</h1>
<my-component>
<p>This is some original content</p>
<p>This is some more original content</p>
</my-component>
</div>

渲染结果是:

1
2
3
4
5
6
7
8
<div>
<h1>I'm the parent title</h1>
<div>
<h2>I'm the child title</h2>
<p>This is some original content</p>
<p>This is some more original content</p>
</div>
</div>

命名的插槽

<slot>有一个name属性,用来进一步定义内容如何被分配,你可以有多个不同名字的<slot>,
命名的<slot>将会匹配任何有相应的slot属性的元素。如果有没有名字的<slot>,它会匹配
任何没有被匹配上的内容,如果没有默认的<slot>,没有匹配上的内容将会被放弃。

1
2
3
4
5
6
7
8
9
10
11
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>

父标记:

1
2
3
4
5
6
<app-layout>
<h1 slot="header">Here might be a page title</h1>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<p slot="footer">Here's some contact info</p>
</app-layout>

渲染的结果是:

1
2
3
4
5
6
7
8
9
10
11
12
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>

当设计组件组合时内容分发是一个有用的机制。

限定作用域的Slot

2.1.0新出现的,限定作用域的slot是一种特殊的类型,作为一种可重用的模板用来代替已经渲染了
的元素。在子组件中,传递data给一个slot:

1
2
3
<div class="child">
<slot text="hello from child"></slot>
</div>

在父组件中,<template>元素会持有一个特殊的scope属性来表示它是一个限定作用域的slot
模板,作用域的值是一个临时变量来掌控从子组件传递过来的data:

1
2
3
4
5
6
7
8
<div class="parent">
<child>
<template scope="props">
<span>hello from parent</span>
<span>{{ props.text }}</span>
</template>
</child>
</div>

它的渲染结果是:

1
2
3
4
5
6
<div class="parent">
<div class="child">
<span>hello from parent</span>
<span>hello from child</span>
</div>
</div>

列表中如何使用slot呢?

1
2
3
4
5
6
<my-awesome-list :items="items">
<!-- scoped slot can be named too -->
<template slot="item" scope="props">
<li class="my-fancy-item">{{ props.text }}</li>
</template>
</my-awesome-list>

list组件模板:

1
2
3
4
5
6
7
<ul>
<slot name="item"
v-for="item in items"
:text="item.text">
<!-- fallback content here -->
</slot>
</ul>

动态的组件

通过is属性动态的切换预定了的组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var vm = new Vue({
el: '#example',
data: {
currentView: 'home'
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})


<component v-bind:is="currentView">
<!-- component changes when vm.currentView changes! -->
</component>

或者直接绑定组件对象:

1
2
3
4
5
6
7
8
9
var Home = {
template: '<p>Welcome home!</p>'
}
var vm = new Vue({
el: '#example',
data: {
currentView: Home
}
})

保持活跃keep-alive

如果你想在内存中保存切换出去的组件以避免重新渲染,可以使用<keep-alive>元素包装它:

1
2
3
4
5
<keep-alive>
<component :is="currentView">
<!-- inactive components will be cached! -->
</component>
</keep-alive>

API引用查阅详细信息

编写可重用组件

当编写组件时应该思考后期会不会重用该组件,编写一次性的组件会紧耦合,编写可重用组件需要
思考写什么东西呢?有以下三点

  • Props 允许外部环境传递props给组件
  • Events 允许在外部环境触发事件
  • Slots 允许外部环境组合具有额外内容的组件
    在模板中可以清晰的传达出你的意图:
1
2
3
4
5
6
7
8
9
<my-component
:foo="baz"
:bar="qux"
@event-a="doThis"
@event-b="doThat"
>
<img slot="icon" src="...">
<p slot="main-text">Hello!</p>
</my-component>

子组件引用

不管存在的props和events,有时候你需要在js中直接访问子组件,为了完成这个目标你需要给子组
件分配一个ref ID,然后通过ref来引用:

1
2
3
4
5
6
7
<div id="parent">
<user-profile ref="profile"></user-profile>
</div>

var parent = new Vue({ el: '#parent' })
// access child component instance
var child = parent.$refs.profile

refv-for一起使用时,你得到的ref将会是一个对象或数组,包含了子组件元数据的镜像。
注意:$refs在组件渲染之后才存在,并且它不是反应式的。你应该避免在模板以及属性计算中
使用$refs

异步组件

在大型app中,我们会把组件拆成一个块,只有当需要的时候才会加载它,在Vue中很简单:

1
2
3
4
5
6
7
8
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// Pass the component definition to the resolve callback
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})

当从服务器得到组件的定义时调用resolve函数,调用reject表示获取组件定义失败。建议的
方式是使用Webpack的代码拆分特性:

1
2
3
4
5
6
Vue.component('async-webpack-example', function (resolve) {
// This special require syntax will instruct Webpack to
// automatically split your built code into bundles which
// are loaded over Ajax requests.
require(['./my-async-component'], resolve)
})

使用Es6语法:

1
2
3
4
Vue.component(
'async-webpack-example',
() => System.import('./my-async-component')
)

组件命名惯例

你可以使用驼峰式,-分界符等,Vue不关注这些:

1
2
3
4
5
6
7
8
9
// in a component definition
components: {
// register using kebab-case
'kebab-cased-component': { /* ... */ },
// register using camelCase
'camelCasedComponent': { /* ... */ },
// register using TitleCase
'TitleCasedComponent': { /* ... */ }
}

在html的模板,需要使用-分界符命名规则:

1
2
3
4
<!-- alway use kebab-case in HTML templates -->
<kebab-cased-component></kebab-cased-component>
<camel-cased-component></camel-cased-component>
<title-cased-component></title-cased-component>

在字符串模板中也没有什么限制:

1
2
3
4
<!-- use whatever you want in string templates! -->
<my-component></my-component>
<myComponent></myComponent>
<MyComponent></MyComponent>

如果你的模板不通过插槽传递内容,可以自闭合:

1
<my-component/>

注意:这个只能在字符串模板中 使用。

递归组件

在组建内部可以通过他们的模板来循环调用自己,这个是通过name选项工作的:

1
name: 'unique-name-of-my-component'

当你使用Vue.component注册了一个全局组件,这个全局Id自动设置为组建的名字:

1
2
3
Vue.component('unique-name-of-my-component', {
// ...
})

如果不小心,很有可能会引起无限循环:

1
2
name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'

这样会引起栈溢出的,所以确保循环是有条件的。

组件之间的相互引用

如果你在构建一个文件目录树,你有一个tree-foler组件有这样的一个模板:

1
2
3
4
<p>
<span>{{ folder.name }}</span>
<tree-folder-contents :children="folder.children"/>
</p>

然后tree-folder-contents持有这样的一个模板:

1
2
3
4
5
6
<ul>
<li v-for="child in children">
<tree-folder v-if="child.children" :folder="child"/>
<span v-else>{{ child.name }}</span>
</li>
</ul>

当你仔细看,会发现循环依赖了,也就是A need B, B need A等等,利用beforeCreate来解决
这个问题:

1
2
3
beforeCreate: function () {
this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue')
}

行内模板

建议在组建中通过template使用模板。

X-Templates

text/x-template:

1
2
3
4
5
6
7
<script type="text/x-template" id="hello-world-template">
<p>Hello hello hello</p>
</script>

Vue.component('hello-world', {
template: '#hello-world-template'
})

这个也不建议使用,因为它分离了组件的其它定义。

Cheap Static Components with v-once

在Vue中渲染简单的Html元素是很快的,但是有时候你需要渲染一个包含大量静态内容的组件。在这
种情况下可以使用v-once指令到根元素来解析一次并缓存:

1
2
3
4
5
6
7
8
Vue.component('terms-of-service', {
template: '\
<div v-once>\
<h1>Terms of Service</h1>\
... a lot of static content ...\
</div>\
'
})

form input绑定

基本用法

可以在form表单中的input和textarea中使用v-model指令创建双向绑定。它会自动的挑选正确的
方式来更新基于input类型的元素。v-model本质上是基于用户输入事件来更改data的语法糖,以及
对一些边缘情况的特殊照顾。v-model不会关心input或textarea的初始值,它只会处理vue实例
的data。

Text

1
2
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

input输入框的值是’edit me’

多行文本

1
2
3
4
<span>Multiline message is:</span>
<p style="white-space: pre">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

在textarea插入文本值<textarea></textarea>不会工作,使用v-model代替。

Checkbox

单个复选框,布尔值:

1
2
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>

多个复选框,绑定同一个数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>

new Vue({
el: '...',
data: {
checkedNames: []
}
})

Radio

1
2
3
4
5
6
7
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>

Select

1
2
3
4
5
6
<select v-model="selected">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>

多个选择,绑定数组:

1
2
3
4
5
6
7
<select v-model="selected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<br>
<span>Selected: {{ selected }}</span>

通过v-for动态渲染的选项:

1
2
3
4
5
6
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>

Value Bindings

对于radio,checkbox,select选项,v-modle的绑定值通常是静态的字符串(checkbox是布尔值)

1
2
3
4
5
6
7
8
<!-- `picked` is a string "a" when checked -->
<input type="radio" v-model="picked" value="a">
<!-- `toggle` is either true or false -->
<input type="checkbox" v-model="toggle">
<!-- `selected` is a string "abc" when selected -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>

但是有时候,我们需要绑定vue实例的动态的属性的值,我们可以通过v-bind来达到这个目的,
使用v-bind允许我们绑定input值为非字符串。

Checkbox

1
2
3
4
5
6
7
8
9
10
11
<input
type="checkbox"
v-model="toggle"
v-bind:true-value="a"
v-bind:false-value="b"
>

// when checked:
vm.toggle === vm.a
// when unchecked:
vm.toggle === vm.b

Radio

1
2
3
4
<input type="radio" v-model="pick" v-bind:value="a">

// when checked:
vm.pick === vm.a

Select Options

1
2
3
4
5
6
7
8
<select v-model="selected">
<!-- inline object literal -->
<option v-bind:value="{ number: 123 }">123</option>
</select>

// when selected:
typeof vm.selected // -> 'object'
vm.selected.number // -> 123

修饰符

.lazy

默认情况下,v-model在输入事件后同步输入值,你可以添加.lazy修饰符开代替change事件

1
2
<!-- synced after "change" instead of "input" -->
<input v-model.lazy="msg" >

.number

如果你想要用户输入自动转换为数字,以可以添加在v-model后添加number修饰符来控制输入

1
<input v-model.number="age" type="number">

这个很有用,因为type="number"返回的是一个字符串。

.trim

如果你想自动的去掉用户输入的空格,可以在v-model后添加’.trim’来控制用户输入

1
<input v-model.trim="msg">

v-model with Components

Html的内置输入类型总是不能满足你的需求,幸运的是,Vue组件允许你构建具有完全自定义行为的
input,这些input实际上工作方式还是v-model,可以在自定义input中了解。

事件处理

事件的监听

可以使用v-on指令来监听DOM事件并执行js函数,例如:

1
2
3
4
5
6
7
8
9
10
11
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>

var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})

事件处理方法

如果时间处理函数的逻辑很复杂,那么在v-on属性上写js是不可行的,所以v-on指令接受一个
你要执行的方法名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="example-2">
<!-- `greet` is the name of a method defined below -->
<button v-on:click="greet">Greet</button>
</div>

var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// define methods under the `methods` object
methods: {
greet: function (event) {
// `this` inside methods points to the Vue instance
alert('Hello ' + this.name + '!')
// `event` is the native DOM event
alert(event.target.tagName)
}
}
})
// you can invoke methods in JavaScript too
example2.greet() // -> 'Hello Vue.js!'

行内处理方法

我们也可以使用行内的js语法而不是直接绑定函数名:

1
2
3
4
5
6
7
8
9
10
11
12
13
<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)
}
}
})

有时候我们需要在行内访问原始DOM的事件,你可以使用$event传递给函数:

1
2
3
4
5
6
7
8
9
10
<button v-on:click="warn('Form cannot be submitted yet.', $event)">Submit</button>

//...
methods: {
warn: function (message, event) {
// now we have access to the native event
if (event) event.preventDefault()
alert(message)
}
}

事件修饰符

在事件处理中调用event.preventDefault或者event.stopPropagation()是很常见的需要。
虽然我们可以在方法中处理,但是保持方法的纯净是很有必要的,我们只需要处理数据相关的逻辑
不用关心DOM的逻辑,为了解决这个问题,Vue为v-on提供了事件修饰符,它由一个后缀.表示:

  • .stop
  • .prevent
  • .capture
  • .self
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- the click event's propagation will be stopped -->
<a v-on:click.stop="doThis"></a>
<!-- the submit event will no longer reload the page -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- modifiers can be chained -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- just the modifier -->
<form v-on:submit.prevent></form>
<!-- use capture mode when adding the event listener -->
<div v-on:click.capture="doThis">...</div>
<!-- only trigger handler if event.target is the element itself -->
<!-- i.e. not from a child element -->
<div v-on:click.self="doThat">...</div>

Key修饰符

当监听键盘事件时,我们需要查看key code,Vue也为v-on提供了键盘事件的修饰符:

1
2
<!-- only call vm.submit() when the keyCode is 13 -->
<input v-on:keyup.13="submit">

技术所有的key code是很难的,为此Vue提供了常用key code的别名:

1
2
3
4
<!-- same as above -->
<input v-on:keyup.enter="submit">
<!-- also works for shorthand -->
<input @keyup.enter="submit">

这里是键盘事件的别名列表

  • .enter
  • .tab
  • .delete
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
    你也可以通过全局config.keyCodes对象自定义键盘事件修饰符:
1
2
// enable v-on:keyup.f1
Vue.config.keyCodes.f1 = 112

鼠标事件修饰符

2.1.0新出来的,当相关的建按下时,你可以使用一下的别名修饰符监听鼠标事件:

  • .ctrl
  • .alt
  • .shift
  • .meta

Why Listeners in HTML?

你可能关心整个事件监听方式违反了”关注点分离”原则,放心,由于所有的事件处理函数和表达式
严格的绑定在当前正在处理的视图上,不会引起维护困难,事实上使用v-on有以下优点:

  1. 通过略读Html模板可以很容易在js找到事件处理函数
  2. 由于你不用手动在js中添加事件监听,ViewModel代码是纯粹的逻辑,易于测试。
  3. 当ViewModel销毁时,所有的事件监听都会自动的被移除,你不需要自己去清理

Vue的列表渲染

v-for

使用v-for指令渲染基于数组的列表,v-for的语法格式为item in items,items是data
里的属性,item是每个数组中每个元素的别名。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>

var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})

v-for块里,有到父属性范围的访问权限,v-for也支持一个可选的第二个参数,用来表示当前
元素的索引:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<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作为定界符,这更接近js中迭代器的语法:

1
<div v-for="item of items"></div>

模板v-for

和模板v-if相似,可以在<template>中使用v-for用来渲染多个元素的块,比如:

1
2
3
4
5
6
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider"></li>
</template>
</ul>

对象v-for

也可以使用v-for来迭代一个对象的所有属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<ul id="repeat-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>

new Vue({
el: '#repeat-object',
data: {
object: {
FirstName: 'John',
LastName: 'Doe',
Age: 30
}
}
})

也可以对这个key提供第二个参数:

1
2
3
<div v-for="(value, key) in object">
{{ key }} : {{ value }}
</div>

以及相应的索引:

1
2
3
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }} : {{ value }}
</div>

范围性的v-for

v-for也接收一个整数,在这种场景中,它会重复渲染此模板多次:

1
2
3
<div>
<span v-for="n in 10">{{ n }}</span>
</div>

组件和v-for

可以在自定义组件中使用v-for:

1
<my-component v-for="item in items"></my-component>

然而这不会将任何数据自动的传递给组件,因为组件有独立的基于它自己的作用域,为了将一个可迭代
的数据传递个给组件,需要使用属性:

1
2
3
4
5
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index">
</my-component>

不自动注入item到组件的原因是这会使组件紧耦合于v-for是如何工作的。为了明确data的
来源并使组件在其他情况下也可复用,这里有个例子:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<div id="todo-list-example">
<input
v-model="newTodoText"
v-on:keyup.enter="addNewTodo"
placeholder="Add a todo"
>
<ul>
<li
is="todo-item"
v-for="(todo, index) in todos"
v-bind:title="todo"
v-on:remove="todos.splice(index, 1)"
></li>
</ul>
</div>

Vue.component('todo-item', {
template: '\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">X</button>\
</li>\
',
props: ['title']
})
new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
'Do the dishes',
'Take out the trash',
'Mow the lawn'
]
},
methods: {
addNewTodo: function () {
this.todos.push(this.newTodoText)
this.newTodoText = ''
}
}
})

key

当Vue更新一个由v-for渲染的列表时,默认情况下使用的是”原地修补”的策略,如果数据列表的
顺序改变了,移动DOM元素来匹配最新的顺序,Vue会原地简单的修正每一个元素确保特定索引初应该
渲染什么。这种默认方式是有效率的,但是只适用于你的列表渲染不依赖于子组件或临时DOM状态(例
如,input输入值)。你需要给每一个节点提供一个唯一的key属性,给Vue一个线索以便可以追踪
每个节点的身份,从而复用且重排序存在的节点.你需要使用v-bind来绑定动态的值,在这里使用
的是简写形式:

1
2
3
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>

不管是否需要建议给v-for提供一个key,除非迭代的DOM内容很简单,或者你故意依赖默认行为来
提高性能增益。由于它是Vue标识节点的通用机制,key还有其它用途不仅仅是很v-for相关联。

数组改变的探测

突变方法

Vue包装了观察数组突变的方法,所以它们也会触发视图的更新,包装的方法有:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
    可以在浏览器的控制台中调用这些突变方法,比如: example1.items.push({ message: 'Baz' })

替换数组

突变方法,顾名思义就是调用这些方法之后改变的是原始数组。和不是突变的方法相比较,比如:
filter(), concat(),他们不改变原始数组而是返回一个新数组,当使用非突变方法时,你需
要用新数组替换原始数组:

1
2
3
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})

Vue实现了一些智能的启发式技术来最大化的复用DOM元素,所以替换一个数组是一个非常有效的操作。

警告

由于js的限制,Vue不能发现以下数组的改变:

  1. 直接通过索引给数组赋值,比如:vm.items[indexOfItem] = newValue
  2. 改变数组的长度。 比如: vm.items.length = newLength
    为了克服警告1,下面的两种方式完成的效果和vm.items[indexOfItem] = newValue是一样的,
    但是会触发反应系统:
1
2
3
4
5
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)

// Array.prototype.splice`
example1.items.splice(indexOfItem, 1, newValue)

处理警告2的方式,也使用splice:

1
example1.items.splice(newLength)

排序或筛选结果的显示

有时候我们需要显示数字的筛选或排序结果没有必要去重置原始数组,在这种情况下,使用属性的计算
来返回筛选的或者排序好的数组:

1
2
3
4
5
6
7
8
9
10
11
12
<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
})
}
}

或者:

1
2
3
4
5
6
7
8
9
10
11
12
<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-if

在字符串模板中,我们需要按照下面的方式写一个条件块:

1
2
3
4
<!-- Handlebars template -->
{{#if ok}}
<h1>Yes</h1>
{{/if}}

在Vue中,我们使用v-if指令来得到相同的效果:

1
<h1 v-if="ok">Yes</h1>

当然可以使用v-else指令来添加”else”块:

1
2
<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>

因为v-if是一个指令,它需要附属在一个元素上,但是当我们需要切换不止一个元素时怎么办呢?
在这种情况下,我们需要使用在<template>中使用v-if,作为一个看不见的包装,最终的渲染
结果是不包含<template>元素:

1
2
3
4
5
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>

v-else

可以使用v-else指令来指出一个v-if的”else 块”:

1
2
3
4
5
6
<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>

v-else指令必须跟在v-ifv-else-if元素后面,否则它不会起作用。

v-else-if

在2.1.0中新添加的特性,可以多次链式的使用:

1
2
3
4
5
6
7
8
9
10
11
12
<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>

同样必须跟在v-ifv-else-if元素后面。

通过key控制元素的复用

Vue尝试尽可能高效的渲染元素,经常重新使用它们而不是重新从零开始渲染他们,为了使Vue更快
这儿有一些有用的优点,比如,允许用户切换等了类型:

1
2
3
4
5
6
7
8
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>

然后切换loginType,将不会擦除用户已经输入的内容,因为两个模板都使用了同一个<input>
改变的仅仅是placeholder.这样不是很好,Vue提供了一种方式来完善这个缺点,只需要添加
一个唯一的key属性:

1
2
3
4
5
6
7
8
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>

注意<label>还是会重用的,因为它们有key属性。

v-show

v-show是一个有条件显示一个元素的指令,它的用法:

1
<h1 v-show="ok">Hello!</h1>

区别是一个带有v-show的元素会在DOM中渲染并保留在DOM中,v-show仅仅是改变元素的display
属性,注意:,v-show不支持<template>,与不与v-else一起工作。

v-if vs v-show

v-if条件为真时才渲染因为他要保证在切换过程中事件监听以及子组件正确的销毁并重建。
v-if是”懒惰的”,如果在初始渲染时,条件为false,它不会干任何事,只有当条件为true时,
才会渲染。
v-show的元素会不管初始条件渲染,它仅仅是基本的CSS切换。
通常来讲,v-if切换时有较高的消耗,而v-show在初始化的时候有较高的消耗,如果你需要经常
切换选择v-show,如果在运行时不太可能改变则选择v-if

样式绑定

Class和Style的绑定

对数据绑定的共同需求是操纵元素的列表样式及行内样式,由于他们都是属性,可以通过v-bind
来处理他们。当v-bind绑定的是class以及style时,除了字符串,表达式也可以被评估为
对象或者数组。

绑定HTML的类

对象语法

我们可以传递一个对象给v-bind:class来动态的切换class:

1
<div v-bind:class="{ active: isActive }"></div>

上面的语法意味着class的存在由isActive是否为真决定。可以通过对象中的多个字段来切换多个
类,另外v-bind:class指令可以与class属性共同存在,下面的例子:

1
2
3
<div class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>

以及相应的数据对象:

1
2
3
4
data: {
isActive: true,
hasError: false
}

它的渲染结果是:

1
<div class="static active"></div>

isActivehasError改变时,类列表页会相应的变化,比如,当hasErrortrue时,
相应的类列表会变为"static active text-danger".
绑定的对象不一定非得内联:

1
2
3
4
5
6
7
8
<div v-bind:class="classObject"></div>

data: {
classObject: {
active: true,
'text-danger': false
}
}

渲染结果是相同的,我们也可以绑定一个返回对象的计算属性,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div v-bind:class="classObject"></div>
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal',
}
}
}

数组语法

我们可以传递一个数组给v-bind:class应用于类列表:

1
2
3
4
5
6
<div v-bind:class="[activeClass, errorClass]">

data: {
activeClass: 'active',
errorClass: 'text-danger'
}

它的渲染结果是:

1
<div class="active text-danger"></div>

如果你想在列表中有条件的切换类,你可以使用三元运算符:

1
<div v-bind:class="[isActive ? activeClass : '', errorClass]">

errorClass会一直应用,但是只有当isActive为真时activeClass才会被应用。但是当你
有多个条件切换的类在列表中会有点罗嗦,可以在列表语法中使用对象语法:

1
<div v-bind:class="[{ active: isActive }, errorClass]">

With Components

当你在自定义组建中使用class属性时,类将会被添加到组件的根节点上,该元素已存在的类不会
被覆盖,比如,你这么声明一个组件:

1
2
3
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})

在使用它时又添加了一些类:

1
<my-component class="baz boo"></my-component>

最终的渲染结果是:

1
<p class="foo bar baz boo">Hi</p>

对于类绑定也是同样的效果:

1
<my-component v-bind:class="{ active: isActive }"></my-component>

isActive为true时,渲染结果为:

1
<p class="foo bar active"></p>

行内样式的绑定

类语法

v-bind:style的对象语法简单易懂,看起来很像CSS,你可以使用驼峰式或者”-“形式的属性命名:

1
2
3
4
5
6
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

data: {
activeColor: 'red',
fontSize: 30
}

直接绑定一个样式对象让模板看起来很干净是一个好主意:

1
2
3
4
5
6
7
8
<div v-bind:style="styleObject"></div>

data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}

对象语法经常结合返回对象的计算属性来使用。

数组语法

v-bind:style的数组语法允许你对一个元素应用多个样式对象:

1
<div v-bind:style="[baseStyles, overridingStyles]">

Auto-prefixing(自动添加前缀)

当你在v-bind:style中使用一个需要添加供应商前缀的属性时,Vue会自动的检测出并且添加一个
合适的前缀来应用该样式,比如transform

Vue属性的计算及监听器

Vue属性计算及监听器

属性计算

模板内使用表达式是很方便的,但只是对于简单的操作。如果在模板中放入大量的逻辑,只会让模板
变得庞大且难以管理:

1
2
3
<div id="example">
{{ message.split('').reverse().join('') }}
</div>

你需要看一会才知道这个模板显示的是倒序的字符串,如果你要在模板中多次使用的话,这会变得很
糟糕。对于复杂逻辑,应该使用属性的计算。

基本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>

<script>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// a computed getter
reversedMessage: function () {
// `this` points to the vm instance
return this.message.split('').reverse().join('')
}
}
})
</script>

在这里我们已经宣布了一个计算的属性reversedMessage,我们提供的这个函数和属性的getter函数
类似rm.reversedMessage:

1
2
3
console.log(vm.reversedMessage) // -> 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // -> 'eybdooG'

vm.reversedMessage的值依赖于vm.message的值。你可以将计算的属性像普通属性一样绑定到
模板上,Vue会意识到vm.reversedMessage依赖vm.message,所以当vm.message改变的时候
修改对应绑定的vm.reversedMessage的值,当然最好的部分是我们已经以声明的方式创建了依赖
关系:计算的getter函数是纯函数并且没有副作用,这样可以很轻松的测试。

计算的缓存VS方法

你可能已经注意到我们可以通过在表达式中调用方法得到相同的结果:

1
2
3
4
5
6
7
8
9
10
<p>Reversed message: "{{ reverseMessage() }}"</p>

<script>
// in component
methods: {
reverseMessage: function () {
return this.message.split('').reverse().join('')
}
}
</script>

我们可以定义一个方法来代替属性的计算。对于最终结果,这两种方式确实完全相同。但是,不同之
处是,计算的属性会缓存基于它的依赖,一个计算的属性只有当它的依赖项发生改变时才会重新计算
这意味着只要message没有改变,多次访问计算的属性reversedMessage会返回上次计算的结果
而不是重新运行该函数。下面的计算的属性永远不会发生变化,因为Date.now()不是一个反应式
的依赖项:

1
2
3
4
5
computed: {
now: function () {
return Date.now()
}
}

作为比较,不管是否重新渲染,一个方法的调用都需要重新运行该方法。我们为什么需要缓存呢?
想象一个非常耗时的计算属性A,它需要在一个很大的数组中循环并且进行大量的计算,然后可能有
其余的计算属性依赖于A的计算属性,如果没有还从的话,我们将毫无必要的执行多次A的计算,当在
不需要使用缓存的情况下,使用方法代替。

计算的属性VS监听的属性

Vue确实提供了一种通用的方法来观察Vue实例中data的变化做出反应:watch properties.当你
的一些数据需要基于其它一些数据做出改变时,可以尝试使用watch属性。但是使用属性的计算相对
来说更好一点,考虑下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="demo">{{ fullName }}</div>

<script>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
</script>

上面的代码有重复,和属性的计算相比较:

1
2
3
4
5
6
7
8
9
10
11
12
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})

这样更好一点。

计算属性的Setter

默认情况下计算的属性时只读的,但是当你需要时你可以提供一个setter函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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]
}
}
}

现在当你执行vm.fullName = 'John Doe',setter函数将会被执行,相应的vm.firstName
vm.lastName也会改变。

监听器

虽然在多数情况下,属性的计算是很合适的,但是有时候自定义监听器也是有必要的。这也是为什么
Vue提供了一个同用的方式,通过watch选项来对数据的变化做出反应。当你要响应不断变化的数据
执行异步操作或者昂贵操作的时候很有用。例如:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>

<!-- Since there is already a rich ecosystem of ajax libraries -->
<!-- and collections of general-purpose utility methods, Vue core -->
<!-- is able to remain small by not reinventing them. This also -->
<!-- gives you the freedom to just use what you're familiar with. -->
<script src="https://unpkg.com/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://unpkg.com/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// whenever question changes, this function will run
question: function (newQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.getAnswer()
}
},
methods: {
// _.debounce is a function provided by lodash to limit how
// often a particularly expensive operation can be run.
// In this case, we want to limit how often we access
// yesno.wtf/api, waiting until the user has completely
// finished typing before making the ajax request. To learn
// more about the _.debounce function (and its cousin
// _.throttle), visit: https://lodash.com/docs#debounce
getAnswer: _.debounce(
function () {
var vm = this
if (this.question.indexOf('?') === -1) {
vm.answer = 'Questions usually contain a question mark. ;-)'
return
}
vm.answer = 'Thinking...'
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
},
// This is the number of milliseconds we wait for the
// user to stop typing.
500
)
}
})
</script>

在这个场景中,使用watch选项允许我们执行异步操作,限制多久执行操作,以及只有获取到数据
才设置state。而这些在属性计算都是不可用的。除了这个选项你还可以使用必要的vm.$watch API

Vue模板语法

模板语法

Vue使用基于HTML的模板语法,允许你以声明式的方式绑定渲染的DOM到Vue实例的data。
所有的Vue模板是有效的html,它可以被特定兼容性的浏览器及html解析器解析。在引擎下,Vue编
译模板到虚拟DOM的render函数里,与反应系统相结合,聪明的计算出最小数量的组件重新渲染当
应用程序state发生变化时,最小数量的DOM操作。如果很熟悉虚拟DOM的概念,并更喜欢原生js的
特性,可以直接写render函数来代替模板。

文本插入

Text

最常用的文本绑定就是使用双括号语法这种格式,比如: <span>Message: </span>
将会替换为相应data对象的属性值,当data的msg属性值改变时,也会改变。
通过使用v-once指令让data属性改变时相应的不更新。但是记住,这回影响任何在该
节点绑定的数据。

1
<span v-once>This will never change: {{ msg }}</span>

原始html

双花括号的语法以文本格式解释data,而不是HTML,如果要输出HTML,使用v-html指令.

1
2
3
4
5
6
7
8
9
10
11
<div id="app">
<div v-html="raw"></div>
</div>
<script>
var vue = new Vue({
el: '#app',
data: {
raw: '<p style='color:red'>测试</p>'
}
});
</script>

Vue不是基于字符串模板引擎,组件是UI重用和组成首选的基本单位。注意:在网站动态的渲染任何
HTML是非常危险的,因为会引起XSS攻击,只有使用HTML插入在信赖的内容上并永远不会使用用户提
供的内容才是安全的。

属性

双花括号语法不能使用在HTML的属性中,替换方案是使用v-bind命令:

1
<div v-bind:id="dynamicId"></div>

对于boolean类型的属性同样有效,当值为false时,移除此属性:

1
<button v-bind:disabled="someDynamicCondition">Button</button>

使用JS表达式

Vue支持在数据绑定时使用js表达式,比如:

1
2
3
4
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>

这样绑定的限制是,在双花括号语法中只能使用一个表达式:

1
2
3
4
<!-- this is a statement, not an expression: -->
{{ var a = 1 }}
<!-- flow control won't work either, use ternary expressions -->
{{ if (ok) { return message } }}

模板表达式是沙箱式的,你可以访问类似MathDate的全局变量,但是不能在模板表达式中
访问用户自定义的全局变量。

指令

指令是以v为前缀的特殊的属性,指令属性值期望的是一个简单的js表达式,指令的作用就是当表
达式的值更改时,将响应作用应用到DOM上:

1
<p v-if="seen">Now you see me</p>

seen表达式的值为false时,v-if指令将移除p元素,反之则插入p元素。

参数

一些指令可以携带一个参数,它由指令:后的名字表示。

1
<a v-bind:href="url"></a>

href是一个参数,它告诉v-bind指令绑定元素的href属性到url表达式的值,还有就是
v-on指令,用来监听DOM事件:

1
<a v-on:click="doSomething">

时间名称就是要监听的DOM时间.

修饰符

修饰符是一个特殊的后缀又一个.表示,它表明一个指令由一种特殊的方式绑定,比如.prevent
v-on指令调用e.preventDefault()阻止事件的默认执行。

筛选器

Vue.js允许你定义筛选器,可以应用于常见的文本格式,筛选器常用在两个地方,双花括号以及v-bind
表达式,筛选器添加在js表达式的后面,由管道|符号表示:

1
2
3
4
<!-- in mustaches -->
{{ message | capitalize }}
<!-- in v-bind -->
<div v-bind:id="rawId | formatId"></div>

注意:Vue2.x的筛选器只能用在双花括号以及v-bind表达式中,因为筛选器的主要设计用来文本
转换的目的,对于更复杂的指令,使用计算的属性来代替。
筛选器函数总是接收表达式的值作为第一个参数:

1
2
3
4
5
6
7
8
9
10
new Vue({
// ...
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})

筛选器可以使链式的:

1
{{ message | filterA | filterB }}

筛选器是js函数,所以他们可以接收参数:

1
{{ message | filterA('arg1', arg2) }}

在这里,'arg1'将作为第二个参数传递给filter函数,而arg2表达式会评估它的值并作为第三
个参数传递。

速记

频繁的使用v-前缀的指令很繁琐,所以Vue提供了两种常用指令的简写方式v-bind,v-on:

v-bind的速记方式
1
2
3
4
<!-- full syntax -->
<a v-bind:href="url"></a>
<!-- shorthand -->
<a :href="url"></a>
v-on的速记方式
1
2
3
4
<!-- full syntax -->
<a v-on:click="doSomething"></a>
<!-- shorthand -->
<a @click="doSomething"></a>

Vue纵览

Vue实例的属性和方法

Vue实例通过var app = new Vue({})来创建,构造参数接收一个对象,该对象包含的属性有
data, template, el, methods, 生命周期回调函数等等,简单的实例:

1
2
3
4
5
6
7
8
9
10
11
var vm = new Vue({
el: '#app',
data: {
a: 1
},
methods: {
hello: function() {

}
}
});

Vue的生命周期钩子

每一个Vue实例在创建的时候都需要经过几个初始化步骤。比如,它需要设置data的监听器,模板的
编译,将实例镶嵌到DOM上,以及当data修改时更新DOM。沿着这个途径,它会调用一些生命周期的
钩子,以便给我们一些执行自定义逻辑的机会。比如:created钩子当实例创建完成后会调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var vm = new Vue({
data: {
a: 1
},
created: function() {
console.log(this.msg);
},
mounted: function() {

},
updated: function() {

},
destroyed: function() {

}
});

这些生命钩子调用时,他们的上下文(this)指向当前vue实例。

生命周期图

lifecycleImg

docker

Docker(容器引擎)

docker是一个开源的可以运行任何app的轻量级的容器。docker容器既’hardware-agnostic(硬件不可知?)’又
‘platform-agnostic(平台不可知?)’.这意味着它可以运行在任何地方,在你的笔记本也行,在云平台亦可以。
并且不要求使用特定的语言,框架,包系统。这使得应用程序的部署,web app的规模调整,后端服务能更好的构建
而不依赖于特定的堆栈或提供者。
docker是一个开放源码的部署引擎,它是由dotCloud驱动的。

比VMs好

分发应用程序及沙坑执行的常用方法是使用虚拟机。典型的VM格式有VMware的vmdk,Oracle的VirtualBox
亚马逊的ESC2,理论上这些格式允许每个开发者可以自动的打包应用程序到一个指定的机器以便于分发及部署。
实际上,几乎不会有这种情况,因为受到以下原因的制裁:

  • Size: VMs很大,存储及移动他们不是很明智。
  • 性能: 运行VMs消耗CPU和内存很大,所以在很多场景下使用它们很不明智,安装过虚拟机的我深有体会。
  • 可移植性
  • 以硬件为中心: VMs的设计是基于机器管理员的想法,而不是软件开发者 。因此,它提供了非常有限的
    的工具给开发者,building, testing, running,比如,VMs提供应用程序的版本控制,监控,配置,
    日志记录等等。
    相比之下,Docker依赖于一种不同于集装箱化的沙盒方法,不像传统的虚拟化,集装箱化发生在内核
    级别。Docker容器非常小,几乎0内存0cpu消耗,可完全移植,并且是以程序为中心设计而设计的。
    也许最重要的是,Docker可以在操作系统中运行,也可以在虚拟机中运行。

Play Well With Others

Docker不需要你购买特定的语言,特定的框架,特定的打包系统,特定的配置语言。
你的程序是Unix进程么?使用文件了么?tcp链接?环境变量?标准Unix流以及命令行参数作为输入
输出,在Docker都可以。

Escape dependecy hell(逃脱地狱依赖)

一个常见的问题就是开发人员很难以一个简单自动的方式管理一个程序的所有依赖,常见的原因有以下一些:

  • 跨平台依赖(cross-platform dependencies).现在的应用程序经常依赖系统库、二进制文件、特定语言包,
    特定的框架模块、作为另外一个项目开发的内部组件等等的组合。这些依赖处在不同的”世界”并且需要不同的工具
    而且相互之间不一定能正常的工作,所以需要尴尬的自定义集成。
  • 依赖冲突.不同的应用程序有可能需要依赖一个库的不同版本。
  • 自定义依赖.开发者可能需要准备应用程序依赖的一个自定义版本,一些打包工具可以处理依赖的自定义版本,
    但是有些工具则不行,并且每种的处理方式又不同。
    Docker让开发者在一个地方描述所有的程序依赖来轻松的解决了地狱依赖的这些常见的问题,同时精简了组装过程。
    Docker通过运行Unix命令序列来定义一个build,在同一个容器里一个接一个的。构造命令修改了容器里的内容(经常
    是安装新文件,新文件系统),下一个命令修改了更多的内容等等。由于每一个命令都是基于上一个命令的结果,命令
    执行的顺序标识依赖项。下面的是一个典型的Docker构造过程:
1
2
3
4
FROM ubuntu:12.04
RUN apt-get update && apt-get install -y python python-pip curl
RUN curl -sSL https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xzv
RUN cd helloflask-master && pip install -r requirements.txt

Docker不关心依赖的构建关系,只要他们可以在容器中可以通过Unix命令构建。

ToDo(入门)