前言

写这编文章也是为了回顾之前学的Vue,查漏补缺。此篇文章涵盖Vue的基本知识以及开发技巧等。这篇文章不讲Vue的相关原理,只会讲Vue 2.0在开发中常见的知识点。你准备好了吗?

安装

CDN引入

1
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

NPM

1
npm install vue

命令行工具(CLI)

安装Vue-cli npm install -g @vue/cli
利用脚手架创建一个Vue项目 vue create 项目名称 这是vue-cli3创建项目的命令;vue-cli2创建一个Vue项目 vue init webpack 项目名称

Vue实例

实例&&数据&&方法

当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var data = {a:1}
var vm = new Vue({
el:'#app',
data:data,
methods:{},
props:[],
components:{},
created:function(){}
....
})
vm.a == data.a //true
vm.a = 2; data.a == 2 //true
//反之亦然
data.a = 3; vm.a == 3 //true
vm.$data === data // => true
vm.$el === document.getElementById('app') // => true

当数据被Object.freeze()时,这会阻止修改现有的 property,也意味着响应系统无法再追踪变化。

生命周期

beforeCreate:只是初始化一些生命周期函数和默认事件,其他的均未创建。
created:初始化data和methods了。
beforeMount:数据已经准备好了,还没渲染到页面。
mounted:将编译好的HTML挂载到页面,完成了渲染。
beforeUpdate:数据被修改,当是还没被重新渲染到页面。
updated:重新渲染页面,页面已经是修改后的了。
beforeDestroy:Vue实例进入销毁状态,data、methods,以及过滤器和指令都是可以用的。
destroyed:Vue实例已经被销毁。
看懂下面这张图就行

模板语法

插值

文本
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值。

1
<span>Message: {{ msg }}</span>

无论何时,绑定的数据对象上 msg property 发生了改变,插值处的内容都会更新。
通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上的其它数据绑定:

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

原始HTML

1
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

v-html可能会引发XSS攻击,因此绝不要对用户提供的内容使用插值。
Attribute && 表达式

1
2
<div v-bind:id="dynamicId"></div>
{{ ok ? 'YES' : 'NO' }}

指令

v-if:判断true或者flase,是否插入元素。
v-else:经常和v-if一起用,还有v-else-if
v-for:遍历循环,注意要绑定:key
v-on:处理事件,v-on:事件名="表达式||函数名",简写@事件名="函数名"
v-bind:绑定属性,v-bind:属性名="属性值"简写:属性名="属性值"
v-show:原始的显示与隐藏,与v-if不同,v-if是插入元素或者删除元素,开销大。
v-html:插人原始HTML,插入的元素要用 >>> css选择器渲染。
v-text:插入文本。
v-cloak:解决vue解析时出现页面闪烁问题。
v-model:双向数据绑定。

修饰符

@submit.prevent="fName":.prevent阻止事件默认行为。
@click.stop="fName":.stop阻止事件冒泡,.capture可以发生事件捕获,.self只触发本身。
@keyup.enter="fName":.enter键盘回车事件,当然还有很多按键,请看我的另一篇文章Vue监听回车事件@keyup
@click.once="fName":.once事件只触发一次。
<el-input @keyup.enter.native="search"></el-input>:.native在自定义组件中,要加native才能监听原生的事件。
<input v-model.lazy="msg" >:.lazy惰性更新,也就是说不会更新那么快,等输入完成后才更新。
<input v-model.trim="msg">:.trim去除前后空格。
<input v-model.number="msg">:.number将输入的字符串变成数字。
.sync:对prop进行双向绑定,用法如下

1
2
3
4
//父组件
<fa-comp :fatest.sync="test"></fa-comp>
//子组件
this.$emit('update:fatest,sontest);

.passive:提升移动端的性能,大概解释就是每次滚动都会有一个默认事件触发,加了这个就是告诉浏览器,不需要查询,不需要触发这个默认事件。
利用vue中的修饰符能让你的开发事倍功半。

计算属性和侦听器

基础实例

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护,所以,对于任何复杂逻辑,你都应当使用计算属性。

1
<p>Reversed message: "{{ reversedMessage() }}"</p>
1
2
3
4
5
6
7
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}

计算属性缓存 vs 方法

1
2
3
4
5
6
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。

计算属性 vs 侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//computed实现属性计算
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
},
//watch实现属性计算
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}

计算属性的 setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 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]
}
}
}

监听器

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
除了 watch 选项之外,您还可以使用命令式的 vm.$watch。

Class 与 Style 绑定

绑定 HTML Class

对象语法
我们可以传给 v-bind:class 一个对象,以动态地切换 class:

1
2
3
4
5
6
7
8
<div :class="{ active: isActive }" :class="classObject"></div> //两种形式
//...
data: {
classObject: {
active: true,
'text-danger': false
}
}

上面的语法表示 active 这个 class 存在与否将取决于数据 property isActive 的布尔值。
数组语法

1
2
3
4
5
6
7
<div :class="[activeClass, errorClass]" :class="[isActive ? activeClass : '', errorClass]"></div> //也可以使用三元表达式
//...
data: {
isActive:false,
activeClass: 'active',
errorClass: 'text-danger'
}

绑定内联样式

对象语法

1
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

数组语法

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

和绑定HTML Class 大同小异,这里就不做多描述了。
注意:当 v-bind:style 使用需要添加浏览器引擎前缀的 CSS property 时,如 transform,Vue.js 会自动侦测并添加相应的前缀。
前缀多重值
你可以为 style 绑定中的 property 提供一个包含多个值的数组,常用于提供多个带前缀的值,如下

1
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

这样写只会渲染数组中最后一个被浏览器支持的值。在本例中,如果浏览器支持不带浏览器前缀的 flexbox,那么就只会渲染 display: flex。

条件渲染

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-if,v-else,v-else-if,v-for,v-show在前面的指令那里都有解释,这里就不做多描述了。

不推荐同时使用 v-ifv-for;当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。
注意
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外。也就是说v-if切换是,复用了已有元素。要用key 管理可复用的元素

列表渲染

在 v-for 里使用对象

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

在遍历对象时,会按 Object.keys() 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。

维护状态

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute:

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

不要使用对象或数组之类的非基本类型值作为 v-for 的 key。请用字符串或数值类型的值。

数组更新检测

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括(可以改变原数组的方法):push,pop,shift,unshift,splice,sort,reverse;
当然也可以使用数组替换,用新数组替换原来的旧数组,达到数据变化以驱动视图更新。例如 filter()concat()slice(),虽然不能改变原数组,但是可以赋值替换的方式来改变原数组。

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。

在 v-for 里使用值范围

v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数。

1
2
3
4
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
//结果:1,2,3...10

事件处理

可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。

1
<button v-on:click="greet">Greet</button>

除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:

1
<button v-on:click="say('hi')">Say hi</button>

事件修饰符,在前面有说了,这里就不做多描述。

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。
.exact修饰符允许你控制由精确的系统修饰符组合触发的事件。

1
2
3
4
5
6
7
8
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>

鼠标按钮修饰符.left,.right,.middle;

表单输入绑定

基础用法

v-model 会忽略所有表单元素的 value、checked、selected attribute 的初始值而总是将 Vue 实例的数据作为数据来源,v-model是双向数据绑定的;
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。
    绑定文本:<input v-model="message" >
    多行文本:<textarea v-model="message" ></textarea>
    复选框:<input type="checkbox" v-model="checked">
    单选框:
    1
    2
    <input type="radio" id="one" value="One" v-model="picked">
    <input type="radio" id="two" value="Two" v-model="picked">
    选择框:
    1
    2
    3
    4
    5
    6
    <select v-model="selected" multiple> //如果可以多选,加上multiple
    <option disabled value="">请选择</option>
    <option>A</option>
    <option>B</option>
    <option>C</option>
    </select>

    值绑定

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- 当选中时,`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>

    修饰符

    .trim,.lazy,number;在模板语法里面有讲解了,这里就不做多描述。

    组件

    组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
  • (单个根元素)*示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 定义一个名为 button-counter 的新组件
    Vue.component('button-counter', {
    data: function () {
    return {
    count: 0
    }
    },
    template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
    })

    组件复用

    你可以将组件进行任意次数的复用:
    1
    2
    3
    4
    5
    <div id="components-demo">
    <button-counter></button-counter>
    <button-counter></button-counter>
    <button-counter></button-counter>
    </div>
    注意当点击按钮时,每个组件都会各自独立维护它的 count。因为你每用一次组件,就会有一个它的新实例被创建。组件之间是独立的,复用的时候也会产生独立的作用域。
  • data 必须是一个函数*
    一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
    1
    2
    3
    4
    5
    data: function () {
    return {
    count: 0
    }
    }
    这也就是为什么,组件复用的时候,复用组件之间的数据是不互通的,作用域相互独立。
    为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册局部注册。至此,我们的组件都只是通过 Vue.component 全局注册的:

    组件通信

    父向子传props:
    1
    2
    3
    4
    5
    6
    7
    //...父组件
    <son :title="hello" ></son>
    //...
    //...子组件
    {{title}}
    props:[title]
    //...
    子传父$emit:
    子组件通过$emit触发父组件的事件,而达到数据传递的效果。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //...父组件
    <son @getSonData="sonData" ></son>
    methods:{
    sonData(data){
    console.log(data) //data为子组件传递过来的数据
    }
    }
    //...
    //...子组件
    <button @click="sendData">向父组件传数据</button>
    methods:{
    sendData(){
    this.$emit('getSonData','我是子组件传递过来的数据')
    }
    }
    //...
    EventBus 事件总线传递数据

    在组件上使用v-model

    1
    <input v-model="searchText">
    等价于
    1
    2
    3
    4
    <input
    v-bind:value="searchText"
    v-on:input="searchText = $event.target.value"
    >
    当用在组件上时,v-model 则会这样:
    1
    2
    3
    4
    <custom-input
    v-bind:value="searchText"
    v-on:input="searchText = $event"
    ></custom-input>
    为了让它正常工作,这个组件内的 必须:
  • 将其 value attribute 绑定到一个名叫 value 的 prop 上
  • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出。
    写成代码之后是这样的:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Vue.component('custom-input', {
    props: ['value'],
    template: `
    <input
    v-bind:value="value"
    v-on:input="$emit('input', $event.target.value)"
    >
    `
    })
    现在 v-model 就应该可以在这个组件上完美地工作起来了:
    1
    <custom-input v-model="searchText"></custom-input>

通过插槽分发内容:

和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:

1
2
3
<alert-box>
Something bad happened.
</alert-box>

幸好,Vue 自定义的 <slot>元素让这变得非常简单:

1
2
3
4
5
6
7
8
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
`
})

动态组件

有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:
上述内容可以通过 Vue 的 元素加一个特殊的 is attribute 来实现:

1
2
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>

在上述示例中,currentTabComponent 可以包括:已注册组件的名字,或一个组件的选项对象

解析 DOM 模板时的注意事项

有些 HTML 元素,诸如 <ul><ol><table><select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li><tr><option>,只能出现在其它某些特定的元素内部。
这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:

1
2
3
<table>
<blog-post-row></blog-post-row>
</table>

这个自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is attribute 给了我们一个变通的办法:

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

需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:

  1. 字符串 (例如:template: '...')
  2. 单文件组件 (.vue)
  3. <script type="text/x-template">

写在最后

Vue的基础篇就到这里结束,下篇深入了解组件