[TOC]
这是一份我给公司后端同事培训Vue的培训第一节课技术文档,旨在带领他们入门Vue的开发世界,释放更多的前端资源。现开放出来以求业界各位的技术指点或补充,感谢~
以下文档配合代码一起演示和讲解效果更佳。代码地址:Vue-coe-test代码地址
创建第一个vue实例
使用以下命令行创建一个vue项目
$ npm install -g vue-cli
$ vue init webpack vue-coe-test
$ cd vue-coe-test
$ npm install
$ npm run dev
挂载点,模板,实例
### // App.vue
<div id="app"></div>
//main.js
new Vue({
el: '#app',
router,
components: { App },
template: '<h1>{{ msg }}</h1>',
data () {
return {
msg: 'Hello,my first Vue.js App'
}
}
})
- 挂载点就是Vue实例里面el属性对应的dom节点。
- 模板就是挂载点里面的内容,也可以写在实例的template中。
- 实例就是new的时候指定一个dom节点,一个模板内容,一个data属性,vue会自动生成一段动态的数据放在挂载点中。
基本的数据表达式:
- {{ data }}
- v-text
- v-html
<h1>{{ msg }}</h1>
<div v-text="text"></div>
<div v-html="text"></div>
data () {
return {
msg: 'Welcome to Your Vue.js App',
text: '<div style="color: red">I am red</div>'
}
},
Vue中的属性绑定和双向数据绑定
<div v-bind:title="title" v-bind:class="[{ active: isActive }, 'title']">阿弥陀佛,么么哒</div>
data () {
return {
title: '大冰的畅销书'
}
}
现在演示的都是单向绑定,那双向绑定是什么呢?
数据双向绑定是指数据可以决定页面的展示,页面的操作也可以改变数据。
<input type="text" v-model="title">
data () {
return {
title: '大冰的畅销书'
}
},
vue中常用指令
指令 (Directives) 是带有 v- 前缀的特殊特性。指令特性的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
v-bind
动态地绑定一个或多个特性,或一个组件 prop 到表达式。
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
在这里 href 是参数,告知 v-bind 指令将该元素的 href 特性与表达式 url 的值绑定。
<!-- class 绑定 -->
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
data: {
isActive: true,
errorClass: 'text-danger'
}
这里的class是响应的,是根据data里的值来显示对应class的,当然你也可以加上指定的class。
v-show, v-if, v-for
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。 v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。 相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
- 示例(更多用法参考官方文档)
HTML
<!-- vue -->
<div>
<h2>问:猩猩最讨厌什么线?</h2>
<div>
<button :class="[{active: item.active}, 'js-button btn-answer']" v-for="item, index in btns" @click="clickHandle(index)">{{item.text}}</button>
</div>
<div class="js-answer"></div>
<div v-show="show">
<div v-if="answer == 0">
bingo!平行线没有相交(香蕉)
</div>
<div v-else-if="answer == 1">
你怕是个假猩猩吧
</div>
<div v-else>
你怕是个假猩猩吧
</div>
</div>
</div>
<!-- twig -->
{% set items = ['A、平行线', 'B、线段', 'C、交叉线'] %}
{% for item in items %}
<button class="js-button btn-answer"
{{ item }}
</button>
{% endfor %}
<div class="js-answer"></div>
data
data () {
return {
show: false,
answer: 0,
btns: [
{
key: 0,
text: 'A、平行线',
active: false
},
{
key: 1,
text: 'B、线段',
active: false
},
{
key: 2,
text: 'C、交叉线',
active: false
}]
}
},
js
<!-- jquery -->
mounted() {
$('.js-button').click(function(e) {
let index = $(e.target).index();
let answerText = '';
index ? (answerText = '你怕是个假猩猩吧') : (answerText = 'bingo!平行线没有相交(香蕉)')
$('.js-answer').text(answerText);
$(this).addClass('active').siblings().removeClass('active');
})
}
<!-- vue -->
methods: {
clickHandle(index) {
this.show = true;
this.answer = index;
this.btns.forEach( item=> {
if (item.key == index) {
item.active = true;
} else {
item.active = false;
}
})
}
}
v-on
绑定事件监听器。事件类型由参数指定。 表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略。
<!-- 方法处理器 -->
<button v-on:click="doThis"></button>
<!-- 缩写 -->
<button @click="doThis"></button>
<!-- 内联语句 -->
<button v-on:click="doThat('hello', $event)"></button>
<!-- 在子组件上监听自定义事件 (当子组件触发“my-event”时将调用事件处理器) -->
<my-component @my-event="handleThis"></my-component>
vue实例中方法和事件
数据传输方法
$emit与$on来进行组件之间的数据传输
-
1.父组件到子组件通信
-
2.子组件到父组件的通信
-
3.兄弟组件之间的通信
vm.$on( event, callback )
监听当前实例上的自定义事件。事件可以由vm.$emit触发。回调函数会接收所有传入事件触发函数的额外参数。
vm.$emit( eventName, […args] )
触发当前实例上的事件。附加参数都会传给监听器回调。
vm.$emit('test', 'hi')
vm.$on('test', function (msg) {
console.log(msg) // hi
})
父子通信,子父通信,兄弟通信
代码演示
- 父子通信
第一种:父组件给子组件传子,使用props
<!-- 父组件 -->
<child :sonMsg="msg"></child>
// js
import child from './children.vue';
export default {
components: {
child
}
}
<!-- 子组件 -->
// html
<div class="children-text">{{sonMsg}}</div>
// js
export default {
props: {
sonMsg: {
type: String,
default: '父组件数据的默认值'
}
}
}
第二种:使用$refs传输数据
<!-- 父组件 -->
// html
<input type="button" @click="parentEnvet" value="父组件触发" />
<child ref="childcomp"></child>
// js
import child from './children.vue';
export default {
components: {
child
},
methods: {
parentEnvet() {
// 注意:这里的childFn是子组件里已存在的方法
this.$refs.childcomp.childFn();
// 或者这样写:this.$refs['childcomp'].childFn();
}
}
}
<!-- 子组件 -->
// js
methods: {
childFn() {
// do somethings
}
}
第三种:使用$emit 和 $on传输数据(包括子父通信)
<!-- 父组件 -->
// html
<div class="parent-box">
<div class="mb20" v-text="vals"></div>
</div>
<div class="children-box">
<child @showMsg="msgFromChild" :sonMsg="msg">
</child>
</div>
// js
<script>
import child from './children.vue';
export default {
components: {
item
},
data () {
return {
vals: '我会展示子组件传给我的数据',
msg: '我是父组件的数据,将传给子组件~'
}
},
methods: {
// 接受子组件的数据
msgFromChild(data) {
this.vals = data;
}
}
}
</script>
<!-- 子组件 -->
// html
<div>
<div class="children-text">{{parentMsg}}</div>
<button @click="send">子组件触发</button>
</div>
// js
<script>
export default {
props: {
parentMsg: {
type: String,
default: '父组件数据的默认值'
}
},
data () {
return {
msg: '我真的是太帅了'
}
},
methods: {
send() {
// 给父组件传递数据
this.$emit('showMsg', this.msg)
}
}
}
</script>
- 非父子组件进行通信
思考:了解了父子通信后,你认为非父子组件之间的通信应该是怎样的呢?
非父子组件之间传值,需要定义个公共的实例文件bus.js,作为中间仓库来传值,不然路由组件之间达不到传值的效果。
1.建立公共的实例文件,内容如下:
// src/util/bus.js
import Vue from 'vue'
export default new Vue();
2.分别在非父子组件内引入该公共实例文件(假如命名为Bus)
A组件在方法里使用Bus.$emit('fnName', data)传送数据
// 组件A
<template>
<div>
<h3>我现在要跟B组件打个招呼</h3>
<input type="text" @change="saiHi" v-model="text"/>
</div>
</template>
<script>
import Bus from '../util/bus.js';
export default {
data () {
return {
text: '你好,我是小猪佩奇A'
}
},
methods: {
saiHi() {
Bus.$emit('saiHi', this.text);
}
}
}
</script>
B组件(常在created,mounted生命周期里)使用Bus.$on('fnName', (data) => {xxx})接收数据
// 组件B
<template>
<div>
<div class="message-text" v-for="msg in msgs" v-text="msg.text"></div>
</div>
</template>
<script>
import Bus from '../util/bus.js';
export default {
data () {
return {
msgs: [{
text: 'hello'
}],
}
},
mounted() {
Bus.$on('saiHi', (data) => {
this.msgs.push({text: data});
})
},
}
</script>
Vue中计算属性computed和监听属性函数watch
定义
**计算属性:**基于它们的依赖进行缓存的。只在相关依赖发生改变时它们才会重新求值。 **侦听属性:**当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。通常更好的做法是使用计算属性而不是命令式的 watch 回调
computed和watch比较
<div id="demo">{{ fullName }}</div>
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
}
}
})
fullName属性依赖于firstName和lastName,这里有个缺点就是,无论firstName或lastName其中的任何一个发生改变时,都要调用不同的监听函数来更新fullName属性。但是当我们使用计算属性时,代码就会变得更加简洁。
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
这时,我们只要监听fullName属性就行,至于firstName或lastName属性的任何改变,我们都可以通过fullName的getter()方法得到最终的fullName值。
另外需要注意的是,计算属性默认只有 getter ,不能通过this.xxx = 'xxx'这种方式去赋值,不过在需要时你也可以提供一个 setter,允许同时设置getter()、setter()方法。如下:
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
-
相同点: 首先它们都是以Vue的依赖追踪机制为基础的,它们的共同点是:都是希望在依赖数据发生改变的时候,被依赖的数据根据预先定义好的函数,发生“自动”的变化。
-
不同点: watch和computed各自处理的数据关系场景不同 (1) watch擅长处理的场景:一个数据影响多个数据。适合监控场景,某【一个】变量改变时需要做什么操作;类似于onchange,适合耗时操作,如网络请求等。 (2) computed擅长处理的场景:一个数据受多个数据影响。某【一些】变量发生变化时,影响的【单个】结果对应地发生改变。
生命周期
-
创建前/后**(beforeCreated/created)**: 在beforeCreated阶段,vue实例的挂载元素
$el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,$el还没有。 -
载入前/后**(beforeMount/mounted)**:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
-
更新前/后**(beforeUpdate/updated)**:当data变化时,会触发beforeUpdate和updated方法。
-
销毁前/后**(beforeDestory/destroyed)**:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在