vue学习笔记

By Erik

安装

Vue.js 不支持 IE8 及其以下版本。

1
2
# 最新稳定版
$ npm install vue
1
2
3
4
5
6
7
8
# 全局安装 vue-cli
$ npm install --global vue-cli
# 创建一个基于 webpack 模板的新项目
$ vue init webpack my-project
# 安装依赖,走你
$ cd my-project
$ npm install
$ npm run dev
1
2
3
4
git clone https://github.com/vuejs/vue.git node_modules/vue
cd node_modules/vue
npm install
npm run build

介绍

一套构建用户界面的渐进式框架。与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计。Vue 的核心库只关注视图层。

申明式渲染:

1
2
3
4
5
6
7
8
9
10
<div id="app">
{{ message }}
</div>

var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})

条件与循环

1
2
3
4
5
6
7
8
9
10
<div id="app-3">
<p v-if="seen">现在你看到我了</p>
</div>

var app3 = new Vue({
el: '#app-3',
data: {
seen: true
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app-4">
<ol>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ol>
</div>

var app4 = new Vue({
el: '#app-4',
data: {
todos: [
{ text: '学习 JavaScript' },
{ text: '学习 Vue' },
{ text: '整个牛项目' }
]
}
})

处理用户输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app-5">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">逆转消息</button>
</div>

var app5 = new Vue({
el: '#app-5',
data: {
message: 'Hello Vue.js!'
},
methods: {
reverseMessage: function () {
this.message = this.message.split('').reverse().join('')
}
}
})
1
2
3
4
5
6
7
8
9
10
11
<div id="app-6">
<p>{{ message }}</p>
<input v-model="message">
</div>

var app6 = new Vue({
el: '#app-6',
data: {
message: 'Hello Vue!'
}
})

组件化应用构建

1
2
3
4
5
6
7
8
9
// 定义名为 todo-item 的新组件
Vue.component('todo-item', {
template: '<li>这是个待办项</li>'
})

<ol>
<!-- 创建一个 todo-item 组件的实例 -->
<todo-item></todo-item>
</ol>

子单元通过 props 接口实现了与父单元很好的解耦。

Vue实例

1
2
3
var vm = new Vue({
// 选项
})

属性和方法

1
2
3
4
5
6
7
8
9
10
11
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})
vm.$data === data // -> true
vm.$el === document.getElementById('example') // -> true
// $watch 是一个实例方法
vm.$watch('a', function (newVal, oldVal) {
// 这个回调将在 `vm.a` 改变后调用
})

被代理的属性是响应的

生命周期

1
2
3
4
5
6
7
8
9
10
var vm = new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
})
// -> "a is: 1"

mounted、updated、destroyed。钩子的 this 指向调用它的 Vue 实例。

周期图

模板语法

插值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<span>Message: {{ msg }}</span>

<span v-once>这个将不会改变: {{ msg }}</span>

<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

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

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

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div v-bind:id="'list-' + id"></div>

指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<p v-if="seen">现在你看到我了</p>

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

<a v-on:click="doSomething">...</a>

<form v-on:submit.prevent="onSubmit">...</form>

<!-- 完整语法 -->
<a v-bind:href="url">...</a>

<!-- 缩写 -->
<a :href="url">...</a>

<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

<!-- 缩写 -->
<a @click="doSomething">...</a>

计算属性和侦听器

计算属性

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

var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})

计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//计算属性的 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]
}
}
}
// ...

侦听器

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
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>

<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/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: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.getAnswer()
}
},
methods: {
// `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
// 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
// AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
// `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
// 请参考:https://lodash.com/docs#debounce
getAnswer: _.debounce(
function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
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
})
},
// 这是我们为判定用户停止输入等待的毫秒数
500
)
}
})
</script>

Class 与 Style 绑定

绑定 HTML Class

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
<div v-bind:class="{ active: isActive }"></div>

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

<div v-bind:class="classObject"></div>

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

也可以在这里绑定一个返回对象的计算属性。

<div v-bind:class="[activeClass, errorClass]"></div>

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

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

绑定内联样式

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

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

//多个样式对象应用到同一个元素上
<div v-bind:style="[baseStyles, overridingStyles]"></div>

条件渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>

<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 是简单地切换元素的 CSS 属性 display 。不支持

v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件不太可能改变,则使用 v-if 较好

列表渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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' }
]
}
})
//对象迭代
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }} : {{ value }}
</div>
1
2
3
4
5
6
7
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>
//组件有自己独立的作用域。为了传递迭代数据到组件里,我们要用 props

v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。

Vue 包含一组观察数组的变异方法,触发视图更新:

1
2
3
4
5
6
7
push()
pop()
shift()
unshift()
splice()
sort()
reverse()

显示一个数组的过滤或排序副本

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
})
}
}

事件处理器

v-on 指令监听 DOM 事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="example-1">
<button v-on:click="counter += 1">增加 1</button>
<p>这个按钮被点击了 {{ counter }} 次。</p>
</div>

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

<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>

// ...
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) event.preventDefault()
alert(message)
}
}

事件修饰符

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 阻止单击事件冒泡 -->
<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>
<!-- 只当事件在该元素本身(比如不是子元素)触发时触发回调 -->
<div v-on:click.self="doThat">...</div>

键值修饰符

1
2
3
4
5
6
7
8
9
.enter
.tab
.delete (捕获 “删除” 和 “退格” 键)
.esc
.space
.up
.down
.left
.right

修饰符

1
2
3
4
.ctrl
.alt
.shift
.meta

表单控件绑定

v-model 指令在表单控件元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。

1
2
3
4
5
6
7
8
9
10
11
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

<!-- 当选中时,`picked` 为字符串 "a" -->
<input type="radio" v-model="picked" value="a">
<!-- `toggle` 为 true 或 false -->
<input type="checkbox" v-model="toggle">
<!-- 当选中时,`selected` 为字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>
1
2
3
4
5
6
7
8
9
10
.lazy
<!-- 在 "change" 而不是 "input" 事件中更新 -->
<input v-model.lazy="msg" >

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

.trim
<input v-model.trim="msg">
v-model 与组件

组件

组件可以扩展 HTML 元素,封装可重用的代码。

使用组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
全局注册组件
Vue.component('my-component', {
// 选项
})
//确保在初始化根实例之前注册了组件
<div id="example">
<my-component></my-component>
</div>

局部注册
var Child = {
template: '<div>A custom component!</div>'
}
new Vue({
// ...
components: {
// <my-component> 将只在父模板可用
'my-component': Child
}
})
Vue 构造器传入的各种选项大多数都可以在组件里用。
data 是一个例外,它必须是函数。

Vue 中,父子组件的关系可以总结为 props down, events up。父组件通过 props 向下传递数据给子组件,子组件通过 events 给父组件发送消息。

1
2
3
4
5
6
7
8
9
Vue.component('child', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 可以用在模板内
// 同样也可以在 vm 实例中像“this.message”这样使用
template: '<span>{{ message }}</span>'
})

<child message="hello!"></child>

动态地绑定父组件的数据到子模板的 props,就是用 v-bind。

1
<child v-bind:my-message="parentMsg"></child>

每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。

props 指定验证规格:

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
Vue.component('example', {
props: {
// 基础类型检测 (`null` 意思是任何类型都可以)
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 value > 10
}
}
}
})

非prop属性

每个 Vue 实例都实现了事件接口 (Events interface),即:

使用 $on(eventName) 监听事件

使用 $emit(eventName) 触发事件

某个组件的根元素上监听一个原生事件。可以使用 .native 修饰 v-on。

有时候两个组件也需要通信 (非父子关系)。在简单的场景下,可以使用一个空的 Vue 实例作为中央事件总线:

1
2
3
4
5
6
7
var bus = new Vue()
// 触发组件 A 中的事件
bus.$emit('id-selected', 1)
// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {
// ...
})

slot分发内容

元素作为原始内容的插槽。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>

<app-layout>
<h1 slot="header">这里可能是一个页面标题</h1>
<p>主要内容的一个段落。</p>
<p>另一个主要段落。</p>
<p slot="footer">这里有一些联系信息</p>
</app-layout>

动态组件

动态地绑定到它的 is 特性

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

<component v-bind:is="currentView">
<!-- 组件在 vm.currentview 变化时改变! -->
</component>

Props 允许外部环境传递数据给组件

Events 允许从外部环境在组件内触发副作用

Slots 允许外部环境将额外的内容组合在组件中。

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

var parent = new Vue({ el: '#parent' })
// 访问子组件
var child = parent.$refs.profile

过当组件中包含大量静态内容时,可以考虑使用 v-once 将渲染结果缓存起来

深入响应式原理

过渡效果

在 CSS 过渡和动画中自动应用 class

可以配合使用第三方 CSS 动画库,如 Animate.css

在过渡钩子函数中使用 JavaScript 直接操作 DOM

可以配合使用第三方 JavaScript 动画库,如 Velocity.js

Vue 提供了 transition 的封装组件:

自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。

如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。

如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作(插入/删除)在下一帧中立即执行。

js钩子

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
// ...
methods: {
// --------
// 进入中
// --------
beforeEnter: function (el) {
// ...
},
// 此回调函数是可选项的设置
// 与 CSS 结合时使用
enter: function (el, done) {
// ...
done()
},
afterEnter: function (el) {
// ...
},
enterCancelled: function (el) {
// ...
},
// --------
// 离开时
// --------
beforeLeave: function (el) {
// ...
},
// 此回调函数是可选项的设置
// 与 CSS 结合时使用
leave: function (el, done) {
// ...
done()
},
afterLeave: function (el) {
// ...
},
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {
// ...
}
}

过渡状态

数字和运算

颜色的显示

SVG 节点的位置

元素的大小和其他的属性

Render 函数

createElement参数

js代替模板功能

jsx

函数组件化

自定义指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 注册一个全局自定义指令 v-focus
Vue.directive('focus', {
// 当绑定元素插入到 DOM 中。
inserted: function (el) {
// 聚焦元素
el.focus()
}
})

directives: {
focus: {
// 指令的定义---
}
}

混合

混合 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义一个混合对象
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!"
选项合并

插件

添加全局方法或者属性,如: vue-custom-element

添加全局资源:指令/过滤器/过渡等,如 vue-touch

通过全局 mixin 方法添加一些组件选项,如: vue-router

添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。

一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router

单文件组件

生产环境部署提示

路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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) }
})

状态管理

单元测试

服务端渲染

TypeScript 支持