Vue的特点
1、使用组件化模式,提高代码复用率、且让代码更好维护。
2、声明式编码,让编码人员无需直接操作DOM,提高开发效率。
3、使用虚拟DOM+优秀的Diff算法,尽量复用DOM结点。
- 原生js:数据=>real-DOM
- Vue:数据=>virtual-DOM=>real-DOM
Diff算法
Vue2.0
【MVVM模型】
V-VM-M
M
- Model模型
- data中的数据
V
- View视图
- dom(模板)
VM
- ViewModel视图模型
- Vue实例对象
【新建实例】
<body>
<div id="root">
<h1>hello, {{ name }}</h1>
</div>
<script type="text/javascript">
new Vue({
el: '#root',
//el用于指定当前Vue实例为哪个容器服务(Vue实例和容器一一对应)。
data: {
//data用于存储供el所指定的容器使用的数据。对象式
name: 'Vue'
},
methods:{
showInfo(){
//this指向Vue实例对象;如果改成箭头函数,指向Window对象
}
}
});
</script>
</body>
//el的第二种写法:异步挂载
const v = new Vue({
data:{}
})
v.$mount('#root')
//data的第二种写法:函数式
//组件时必须使用函数式
new Vue({
el: '#root',
data: function(){
console.log(this)//匿名函数,this指的是Vue实例对象
return {
}
}
})
new Vue({
el: '#root',
data: ()=>{
console.log(this)//箭头函数,没有this,this指的是全局的Window对象
return {
}
}
})
【语法】
//插值语法:作用于标签体内
//{{}}
//可以写data里的属性、vm上的属性、Vue原型上的属性、js表达式
<h1>hello, {{ name }}</h1>
//指令语法:作用于标签属性
//v-bind或简写:
//单向绑定(数据从data流向页面)
<a v-bind:href="url"></a>
<a :href="url"></a>
//v-model
//v-model:value简写成v-model
//双向绑定
<input v-model:value="url"/>
<input v-model="url"/>
//v-model修饰符
//lazy:失去焦点再收集数据
<textarea v-model="userInfo.other"></textarea>
//number:输入字符串转为有效数字
<input type="number" v-model.number="userInfo.age"/>
//trim:输入首尾空格过滤
<input type="text" v-model.trim="userInfo.account"/>
//表单
//v-model收集的是value值,用户输入的是value值。
<input type="text" v-model="userInfo.account"/>
//v-model收集的是value值,且要给标签配置value值。
<input type="radio" name="sex" v-model="userInfo.sex" value="male"/>
<input type="radio" name="sex" v-model="userInfo.sex" value="female"/>
//没有配置input的value属性,那么收集的就是checked值。
//配置了input的value属性:v-model的初始值是非数组,那么收集的是checked值。v-model的初始值是数组,那么收集的是value组成的数组。
<input type="checkbox" v-model="userInfo.hobby" value="study"/>
<input type="checkbox" v-model="userInfo.hobby" value="game"/>
<input type="checkbox" v-model="userInfo.hobby" value="eat"/>
//v-on
//v-on:click简写成@click
//事件监听
<button v-on:click="showInfo1"></button>//默认都会传event
<button @click="showInfo($event, 11)"></button>
//事件修饰符
//prevent: 阻止默认事件
<a :href="url" @click.prevent="showInfo"></a>//阻止链接跳转
//stop: 阻止事件冒泡
//once: 事件只触发一次
//capture: 使用事件的捕捉模式
//self: 只有event.target是当前操作的元素时才触发事件
//passive: 事件的默认行为立即执行,无需等待事件回调执行完毕
//-----
//事件修饰符可以连续写。
<a :href="url" @click.prevent.stop="showInfo"></a>//阻止链接跳转
//条件渲染
//v-show
//通过display: none来实现;不展示的DOM元素未被移出,仅仅是使用样式隐藏掉。
//适用于切换频率较高的场景。
<p v-show="a===b">Vue</p>
//v-if
//不展示的DOM元素直接移除。
//适用于切换频率较低的场景。
//v-if和v-else-if、v-else之间不允许被任何标签打断。
<p v-if="n===1">Vue</p>
<p v-else-if="n===3">React</p>
<p v-else>Angular</p>
//template
//template不影响结构(渲染后网页代码不显示template标签,但是里面的代码体是一块的;如果用div会破坏结构且渲染后会显示div标签)。
<template v-show="n===1">
<p>Vue</p>
<p>React</p>
<p>Angular</p>
</template>
//v-text
//会替换掉节点中的内容。
//不能识别html结构标签。
<div v-text="name">aa</div>
//v-html
//会替换掉节点中的内容。
//能识别html结构标签。
//具有安全性问题:容易造成xss攻击。
<div v-html="name"></div>
//v-cloak
//没有值,Vue实例创建完毕并接管容器后会删除v-cloak属性。
//使用css配合v-cloak可以解决网速慢时页面展示出{{}}的问题。
<style>
[v-cloak]{
display: none;
}
</style>
<h2 v-cloak>{{name}}</h2>
<script></script>
//v-once
//在初次动态渲染后,就视为静态内容。以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
<h2 v-once>初始化的n值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
//v-pre
//跳过其所在节点的编译过程。可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
【列表渲染】
key的内部原理
(1)虚拟DOM中key的作用
- key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM和旧虚拟DOM的差异比较。
(2)对比规则
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
若虚拟DOM中内容没变,直接使用之前的真实DOM。
若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。- 旧虚拟DOM中未找到与新虚拟DOM相同的key:
创建新的真实DOM,随后渲染到页面。
用index作为key可能会引发的问题:
(1)若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新(界面效果没问题,但效率低)。
(2)若结构中包含了输入类的DOM,会产生错误DOM更新(界面效果有问题)。
开发中如何选择key:
(1)最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
(2)如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,则使用index作为key是没有问题的。
//v-for
//遍历数组(数组元素,数组下标)
<ul>
<li v-for="(p, index) of persons" :key="index">
{{p.name}}-{{p.age}}
</li>
</ul>
//遍历对象(键值,键名)
<ul>
<li v-for="(value, k) of car" :key="k">
{{k}}-{{value}}
</li>
</ul>
//遍历字符串(字符,下标)
<ul>
<li v-for="(char, index) of str" :key="index">
{{char}}-{{index}}
</li>
</ul>
//从1开始遍历指定次数
<ul>
<li v-for="(number, index) of 5" :key="index">
{{number}}-{{index}}
</li>
</ul>
列表过滤
new Vue({
el: '#root',
data: {
keyWord: '',
persons:[...],
filPersons: []
},
//用watch
watch:{
keyWord:{
immediate: true,
handler(val){
this.filPersons = this.persons.filter((p)=>{
return p.name.indexOf(val) !== -1;
})
}
}
}
//用computed
filPersons(){
return this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1;
})
}
})
列表排序
new Vue({
el: '#root',
data: {
keyWord: '',
sortType: 0,//0原顺序 1降序 2升序
persons:[...],
filPersons: []
},
filPersons(){
const arr = this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1;
});
if(this.sortType){
arr.sort((p1, p2)=>{
return this.sortType === 1 ? p2.age-p1.age : p1.age-p2.age;
});
}
return arr;
}
})
【计算属性computed】
(1)原理
- 底层借用了Object.defineproperty方法提供的getter和setter。
(2)优势
- 与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
(3)计算属性最终会出现在vm上,直接读取使用即可。
(4)如果计算属性要被修改,则必须写set函数去相应修改,且set中要引起计算时依赖的数据发生改变。
new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三'
},
computed: {
//完整写法
fullname: {
//get在初次读取fullName或所依赖的数据发生变化时调用。
get(){
return this.firstName + '-' + this.lastName;
},
set(value){
const arr = value.split('-');
this.firstName = arr[0];
this.lastName = arr[1];
}
}
//简写,只get不set时
fullName:function(){}
fullName(){}
}
})
【监视属性watch】
(1)当被监视的属性发生变化时,回调函数自动调用。
(2)监视的属性必须存在,才能进行监视。
const vm = new Vue({
el: '#root',
data: {
isHot: true
},
watch: {
isHot: {
immediate: true,//初始化时调用handler
handler(newValue, oldValue){
...
}
}
}
})
//或者
vm.$watch('isHot', {
immediate: true,
handler(newValue, oldValue){
...
}
});
vm.$watch('isHot', function(newValue, oldValue){
...
});
深度监视
(1)Vue中的watch默认不监测对象内部值的改变。通过配置deep:true可以监测对象内部值的改变。
(2)Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以。
new Vue({
el: '#root',
data: {
numbers: {
a: 1,
b: 2
}
},
watch: {
//当numbers的任意属性变化时,该方法无法监视到。因为numbers里面存的是引用地址,当属性变化时,引用地址未发生变化。
numbers: {
handler(newValue, oldValue){
...
}
}
//只监视a属性。
'numbers.a': {
handler(newValue, oldValue){
...
}
}
//监视numbers的任意属性发生变化。
numbers: {
deep: true,
handler(newValue, oldValue){
...
}
}
//简写,只需要handler时才可以简写。
numbers(newValue, oldValue){
...
}
}
})
computed和watch的区别
(1)computed能完成的功能,watch都可以完成。
(2)watch能完成的功能,computed不一定能完成,如:watch可以进行异步操作。
(1)所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象。
(2)所有不被Vue管理的函数(定时器的回调函数、ajax的回调函数等),最好写成箭头函数,这样this的指向才是vm或组件实例对象。
???
【过滤器filters】
<div id="root">
//{{ 参数 | 过滤器名 }}或者v-bind:属性="参数 | 过滤器名"
//默认将第一个参数作为过滤器的第一个参数。
//多个过滤器可以串联,前一个过滤器的结果是后一个过滤器的参数。
<h3>{{time | timeFormatter('YYYY_MM_DD') | mySlice}}</h3>
</div>
new Vue({
el: '#root',
data:{
time: 假装我是一个时间戳
},
//局部过滤器
filters:{
timeFormatter(value, str="YYYY年MM月DD日 HH:mm:ss"){
//这里的value是time。
return dayjs(value).format(str);
},
mySlice(value){
//这里的value是timeFormatter的返回值。
return value.slice(0,4);
}
}
})
//全局过滤器
Vue.filter('mySlice', function(value){
return value.slice(0, 4);
});
【自定义指令directives】
指令名如果是多个单词,要使用kebab-case命名方式(短横线命名法),不要用camelCase命名(小驼峰命名法)。
new Vue({
el: '#root',
data: {},
directives: {
//该方法在bind、update时调用。
big(element, binding){
...
},
fbind:{
//指令与元素成功绑定时调用。
bind(element, binding){
...
},
//指令所在元素被插入页面时调用。
inserted(element, binding){
...
},
//指令所在模板结构被重新解析时调用。
update(element, binding){
...
}
}
}
})
//全局指令
Vue.directive('fbind', {
//指令与元素成功绑定时调用。
bind(element, binding){
...
},
//指令所在元素被插入页面时调用。
inserted(element, binding){
...
},
//指令所在模板结构被重新解析时调用。
update(element, binding){
...
}
})
【绑定class样式】
//字符串写法: 适用于样式的类名不确定,需要动态指定。
<div id="root">
<div class="basic" :class="mood" @click="changeMood">{{name}}</div>
</div>
//数组写法:适用于要绑定的样式个数不确定、名字也不确定。
<div id="root">
<div class="basic" :class="classArr" @click="changeMood">{{name}}</div>
</div>
//对象写法:适用于要绑定的样式个数确定、名字也确定,但要动态决定用不用。
<div id="root">
<div class="basic" :class="classObj" @click="changeMood">{{name}}</div>
</div>
new Vue({
el: '#root',
data: {
name: 'aaa',
mood: 'normal',
classArr: ['c1', 'c2', 'c3'],
clssObj:{
c1: false,
c2: true,
c3: false
}
},
methods: {
changeMood(){
this.mood = 'happy';
}
}
})
【绑定style样式】
<div id="root">
<div class="basic" :style="{fontSize: fSize + 'px'}">{{name}}</div>
</div>
//对象写法
<div id="root">
<div class="basic" :style="styleObj">{{name}}</div>
</div>
//数组写法
<div id="root">
<div class="basic" :style="styleArr">{{name}}</div>
</div>
new Vue({
el: '#root',
data: {
name: 'aaa',
fSize: 40,
styleObj:{
fontSize: '40px'
//注意将短横线命名转化为小驼峰命名
},
styleArr:[
{
...
},{
...
}
]
}
})
【scoped样式】
- 作用
让样式在局部生效,防止冲突。- 写法
<style scoped>
【过渡与动画】
???
https://cn.vuejs.org/v2/guide/transitions.html
- 准备好样式:
- 元素进入的样式:
v-enter:进入的起点
v-enter-active:进入过程中
v-enter-to:进入的终点- 元素离开的样式:
v-leave:离开的起点
v-leave-active:离开过程中
v-leave-to:离开的终点
- 使用
<transition>
包裹要过度的元素,并配置name属性:
<transition name=“hello”>
<h1 v-show=“isShow”>你好啊!</h1>
</transition>- 备注:若有多个元素需要过度,则需要使用:
<transition-group>
,且每个元素都要指定key
值。
//搭配animate.css使用
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__headShake"
leave-active-class="animate__headShake"
>
<h1 v-show="!isShow" key="1">a</h1>
<h1 v-show="isShow" key="2">b</h1>
</transition-group>
【键盘事件】
(1)Vue中常见的按键别名
<input @keyup.enter=“showInfo” />
- enter:回车。
- delete:删除。(捕获“delete”键和“Backspace”键)
- esc:退出。
- space:空格。
- tab:换行。(必须配合keydown使用,因为tab一般用于切换焦点,当按下tab键时会切换到别的元素,使得keyup事件无法触发。)
- up:上。
- down:下。
- left:左。
- right:右。
(2)系统修饰键:ctrl、alt、shift、meta(win或command)
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才会触发。
- 配合keydown使用:正常触发事件。
(3)Vue未提供别名的按键可以使用按键原始的key值去绑定,但注意要将key值转换为短横线命名法(如@keyup.Caps-Lock="")。
(4)也可以使用keyCode去指定具体的按键(如@keyup.13=""),但是不推荐使用。
(5)也可以自定义按键别名(Vue.config.keyCodes.自定义键名=键码)。
(6)按键事件也可以连续写。如@keydown.ctrl.y=""。
【数据代理】
Object.defineProperty
P11-P13
【监测数据】
P33-P37妙蛙种子
Vue会监视data中所有层次的数据。
(1)如何监测对象中的数据
- 通过setter实现监视,且要在new Vue时就传入要监测的数据。
- 对象中后追加的属性,Vue默认不做响应式处理。
- 如需给后追加的属性做响应式,则使用Vue.set(target, propertyName/index, value)或vm.$set(target, propertyName/index, value)。
(2)如何监测数组中的数据
- 对于纯数组中的元素,Vue没有设置getter和setter。(对于对象,Vue有设置getter和setter;对于对象数组中的对象,Vue有设置getter和setter)
- 通过包裹数组更新元素的方法实现,本质就是做了两件事:
①调用原生的方法对数组进行更新。
②重新解析模板,更新页面。(3)在Vue中修改数组中的某个元素一定要使用如下方法:
①push()、pop()、shift()、unshift()、splice()、sort()、reverse()
②Vue.set()或vm.$set()
注意Vue.set()或vm.$set()中的target不能是vm或者是vm的根数据对象(vm._data)。
【生命周期】
生命周期(生命周期回调函数、生命周期函数、生命周期钩子)
- 是Vue在关键时刻帮我们调用的一些特殊名称的函数。
- 生命周期函数中的this指向是vm或组件实例对象。
new Vue()
初始化生命周期、事件,但数据代理还未开始。
【beforeCreate】
- 无法通过vm访问到data中的数据、methods中的方法。
- 初始化数据检测、数据代理。
【created】
可以通过vm访问到data中的数据、methods中的方法。解析模板,生成虚拟DOM(内存中),页面还不能显示解析好的内容。
【beforeMount】
- 此时页面呈现的是未经Vue编译的DOM结构;
- 此时所有对DOM的操作,最终都不奏效。
将内存中的虚拟DOM转为真实DOM插入页面。
【mounted】
- 此时页面呈现的是经过Vue编译的DOM;
- 此时所有对DOM的操作均有效(尽可能避免);
- 至此初始化过程结束,一般在此进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作。
【beforeUpdate】
- 此时数据是新的,但页面是旧的(页面尚未和数据保持同步)。
根据新数据,生成新的虚拟DOM,随后与旧的虚拟DOM进行比较,最终完成页面更新(即Model->View的更新)。
【updated】
- 此时数据是新的,页面也是新的(页面和数据保持同步)。
调用vm.$destroy()。
【beforeDestroy】
- 此时vm中的所有:data、methods、指令等都是处于可用状态,马上要执行销毁过程。此时update不会触发效果。
- 一般在此进行:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作。
移除监视、子组件、事件监听器。
【destroyed】
- 销毁后自定义事件会失效,但原生DOM事件依然有效。
【模块】
概念
- 向外提供特定功能的JS程序(一般就是一个JS文件)。
作用
- 复用JS,简化JS编写,提高JS运行效率。
模块化
- 当应用中的JS都以模块来编写的,那么这个应用就是一个模块化的应用。
【组件】
概念
- 实现应用中局部功能代码和资源的集合(html/css/js/image…)。
作用
- 复用编码,简化项目编码,提高运行效率。
组件化
- 当应用中的功能都是多组件的方式来编写的,那么这个应用就是一个组件化的应用。
VueComponent
- 组件本质是一个名为VueComponent的构造函数,是Vue.extend生成的。
- 写
<school></school>
,Vue在解析时会创建school组件的实例对象即new VueComponent(options)。- this的指向
(1)组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数,this都指向VueComponent实例对象。
(2)new Vue()配置中:data函数、methods中的函数、watch中的函数、computed中的函数,this都指向Vue实例对象。- VueComponent的实例对象,简称vc(组件实例对象)。
Vue的实例对象,简称vm。- VueComponent.prototype._proto_ === Vue.prototype
让组件实例对象可以访问到Vue原型上的属性、方法。
P59
![[Snipaste_2022-02-04_17-46-29.png]]
组件名
- 一个单词组成:首字母小写or首字母大写。
- 多个单词组成:短横线命名 or 大驼峰命名(需要Vue脚手架支持)。
- 尽量回避HTML已有标签名。
- 可以使用name配置项指定组件在开发者工具中呈现的名字。
组件标签
<school></school>
<school/>
该方法必须在脚手架下才能使用。不使用脚手架时,会导致后续组件不能渲染。
非单文件组件
- 一个文件包含多个组件。
- 组件嵌套
子组件必须在父组件之前声明。
<div id="root">
<xuexiao></xuexiao>
<xuesheng></xuesheng>
</div>
const school = Vue.extend({
//el不要写,因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
template: `<h1></h1>`,
//使用函数式,多个组件之间的数据不会相互影响。避免组件被复用时,数据存在引用关系。
data(){
return{
...
}
}
});
//可以简写,但是代码可读性差。
const school = {
template: `<h1></h1>`,
data(){
return{
...
}
}
};
const student = Vue.extend({
template: `<h1></h1>`,
data(){
return{
...
}
}
});
new Vue({
el: '#root',
//注册组件(局部注册)
components:{
xuexiao: school,
xuesheng: student
}
//简写
components:{
school,
student
}
});
//注册组件(全局注册)
Vue.component('school', school);
单文件组件
- 有多个文件,一个文件只包含一个组件。
//index.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="root"></div>
<!-- 引入容器后再引入js,不然容器未注册。 -->
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="../main.js"></script>
</body>
</html>
//main.js
import App from '';
new Vue({
el:'#root',
template: `<App></App>`,
components:{
App
}
})
//App.vue
<template>
<div>
<School></School>
</div>
</template>
<script>
//引入组件
import School from '';
export default{
name: 'App',
components:{
School
}
}
</script>
//子组件
<template>
</template>
<script>
export default{
name: 'School',
data(){
return{
}
}
};
</script>
<style>
</style>
【Vue-CLI】
CLI(Command Line Interface)
├── node_modules
├── *public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── *main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
//index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对IE浏览器的特殊配置,让IE浏览器以最高的渲染级别渲染页面 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- <%= BASE_URL %> = public/(./) -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 配置网页标题 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 当浏览器不支持js时,noscript中的元素就会被渲染。 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
vue.js和vue.runtime.xxx.js的区别
- vue.js是完整版的vue,包含了核心功能和模板解析器。
- vue.runtime.xxx.js是运行版的vue,只包含了核心功能,没有模板解析器。没有模板解析器,所以不能使用template配置项,需要使用render函数接受到的createElement函数去指定具体内容。
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
//等同于
new Vue({
render(createElement){
return createElement('h1', '你好');
}
//等同于
render: createElement => {
return createElement('h1', '你好');
}
//等同于
render: h => h('h1', '你好');
}).$mount('#app');
配置文件
- vue脚手架的默认配置被vue隐藏了。在命令行输入
vue inspect > output.js
即可查看。- 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh。
- 修改后需要重新npm run serve才可以。
//vue.config.js
//webpack commonjs???
module.exports = {
pages:{
index:{
//入口
entry: 'src/main.js'
}
},
//关闭语法检查
lintOnSave: false,
//配置代理1
//xxxx为转发给目的服务器的端口号。(注意:然后要将axios请求地址端口号改成本地端口号)
//优点:配置简单,请求资源时直接发给前端(8080)即可。
//缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
//工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
devServer:{
proxy:"http://localhost:xxxx"
},
//配置代理2
//优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
//缺点:配置略微繁琐,请求资源时必须加前缀。
/*
changeOrigin
用于控制请求头中的host值。默认值为true。
设置为true时,服务器收到的请求头中的host为服务器的端口号。(说谎)
设置为false时,服务器收到的请求头中的host为前端浏览器的真实端口号。(讲实话)
*/
devServer: {
proxy: {
'/api1': {// 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000',// 代理目标的基础路径
changeOrigin: true,
ws: true, // websocket
pathRewrite: {'^/api1': ''}
},
'/api2': {// 匹配所有以 '/api2'开头的请求路径
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
}
【ref属性】
- 被用来给元素或子组件注册引用信息(id的替代者)。
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象。
<template>
<div>
<h1 ref="title"></h1>
<School ref="sch"/>
</div>
</template>
showDOM(){
console.log(this.$refs)
console.log(this.$refs.title)
console.log(this.$refs.sch)
}
【props配置】
- 功能
让组件接收外部传过来的数据。
//父组件
<template>
<div>
//加:使得右边解析成表达式,则传过去的值为数值18。
<Student name="张三" sex="男" :age="18"/>
</div>
<template>
//子组件
export default{
name: 'Student',
data(){
return{
}
},
//只接收
props:['name', 'age', 'sex']
//接收并限制类型
props:{
name: String,
age: Number,
sex: String
}
//接收并限制类型、限制必要性、指定默认值
props: {
name:{
type: String,
required: true
},
age:{
type: Number,
default: 99
},
sex:{
type: String,
required: true
}
}
}
- 注意
props是只读的,Vue底层会监测你对props的修改,如果进行了修改就会发出警告。如果确实需要修改,请复制props的内容到data中,再去修改data中的数据。
export default{
name: 'Student',
data(){
return {
myAge: this.age;
}
},
props:['age']
}
【mixin混入】
- 功能
可以把多个组件共用的配置提取成一个混入对象。- 注意
data中原有的以原有的为主,没有则以mixin为主。
生命周期钩子都要。
//局部混入
//mixin.js
export const hunhe = {
methods:{
...
},
mounted(){
...
}
}
//组件
import {hunhe} from '../mixin'
export default{
name:'School',
data(){
return{
}
},
mixins:[hunhe]
}
//全局混入
//main.js
import {hunhe, hunhe2} from './mixin'
Vue.mixin(hunhe)
Vue.mixin(hunhe2)
【插件】
- 功能
用于增强Vue。- 本质
包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
//main.js
import plugins from './plugins'
Vue.use(plugins)
//plugins.js
export default{
install(Vue, options){
// 1. 添加全局过滤器
Vue.filter(....)
// 2. 添加全局指令
Vue.directive(....)
// 3. 配置全局混入(合)
Vue.mixin(....)
// 4. 添加实例方法
Vue.prototype.$myMethod = function () {...}
Vue.prototype.$myProperty = xxxx
}
}
【组件通信】
(1)子组件向父组件传值
通过props,将父组件中的函数传给子组件,子组件通过调用父组件的函数将参数传递给父组件。
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
//App.vue
<template>
<div id="root">
<School :getSchoolName="getSchoolName"/>
</div>
</template>
<script>
export default{
name: 'App',
component: {
School
},
data(){
...
},
methods:{
getSchoolName(name){
console.log(name);
}
}
}
</script>
<button @click="sendSchoolName"></button>
<script>
export default{
name: 'School',
props: ['getSchoolName'],
data(){
return{
name: 'name'
}
},
methods: {
sendSchoolName(){
this.getSchoolName(this.name);
}
}
}
</script>
通过自定义事件,在父组件中的子组件绑定一个自定义事件,通过触发子组件实例上的自定义事件将参数传递给父组件。
组件上也可以绑定原生DOM事件,需要使用
native
修饰符。如:@click.native
。
//App.js
//使用@或v-on
<template>
<div id="root">
<Student v-on:atguigu="getStudentName"/>
//or
<Student @atguigu="getStudentName"/>
//<Student @atguigu.once="getStudentName"/>
</div>
</template>
<script>
export default{
name: 'App',
component: {
Student
},
data(){
...
},
methods:{
getStudentName(name){
console.log(name);
}
}
}
</script>
//使用ref
//比较灵活,可以使用定时器。
<template>
<div id="root">
<Student ref="student"/>
</div>
</template>
<script>
export default{
name: 'App',
component: {
Student
},
data(){
...
},
methods:{
getStudentName(name){
console.log(name);
}
},
mounted(){
//通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
this.$ref.student.$on('atguigu', this.getStudentName);
//this.$ref.student.$once('atguigu', this.getStudentName);
}
}
</script>
<button @click="sendStudentName"></button>
<button @click="unbind"></button>
<script>
export default{
name: 'Student',
data(){
return{
name: 'name'
}
},
methods: {
sendStudentName(){
this.$emit('atguigu', this.name);
},
unbind(){
//解绑一个自定义事件
this.$off('atguigu');
//解绑多个自定义事件
this.$off(['atguigu', 'demo']);
}
}
}
</script>
全局事件总线。
(2)任意组件间通信
全局事件GlobalEventBus
![[Snipaste_2022-02-04_18-18-11.png]]
//main.js
...
new Vue({
...
beforeCreated(){
Vue.prototype.$bus = this;
}
})
//提供数据
methods:{
sendData(){
this.$bus.$emit('事件名', this.data);
}
},
beforeDestroyed(){
this.$off('事件名');
}
//接收数据
mounted(){
this.$bus.$on('事件名', (data)=>{
console.log(data);
...
})
}
消息订阅与发布pubsub
//安装库
npm i pubsub-js
//在订阅和发布消息的组件里引入
import pubsub from 'pubsub-js'
//提供数据
methods:{
sendData(){
pubsub.publish('事件名', this.data);
}
}
//接收数据
mounted(){
this.pid = pubsub.subscribe('事件名', (msgName, data)=>{
console.log(msgName, data);
...
});
},
beforeDestroyed(){
pubsub.unsubscribe(this.pid);
}
vuex
(3)父组件向子组件传值
通过props。
插槽
//默认插槽
//样式写在父组件或者子组件中都可以。
//父组件中:
<Category>
<div>这是插入默认插槽的内容</div>
</Category>
//子组件中:
<template>
<div>
<slot></slot>
</div>
</template>
//具名插槽
//父组件中:
<Category>
//v-slot必须配合template(任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。)
//v-slot:缩写成#
<template v-slot:footer>
<div>这是插入footer的内容</div>
</template>
</Category>
//子组件中:
<template>
<div>
<slot name="center">没有在父组件插槽插入内容就会显示的默认内容</slot>
<slot name="footer">插槽默认内容...</slot>
</div>
</template>
//作用域插槽
//父组件中:
<Category>
<template v-slot:default="scopeData">
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Category>
//子组件中:
<template>
<div>
<slot :games="games"></slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
//数据在子组件自身
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
},
}
</script>
【nextTick】
新生命周期函数
- 语法
this.$nextTick(回调函数)- 作用
在下一次 DOM 更新结束后执行其指定的回调。- 什么时候用
当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
函数执行的逻辑:函数执行=>DOM更新。如果在函数里
this.$ref.inputTitle.focus()
,不用nextTick的话,则会先执行函数即focus,再更新DOM,更新完后无法实现focus。
【本地存储】
(1)存储内容大小一般支持5MB左右(不同浏览器可能还不一样)。
(2)浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
(3)localStorage和sessionStorage的区别
- SessionStorage存储的内容会随着浏览器窗口关闭而消失。
- LocalStorage存储的内容,需要手动清除才会消失
xxxxxStorage.setItem('key', 'value');
//该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。键值需转换成字符串,如果是对象则使用JSON.stringfy(obj)。
xxxxxStorage.getItem('person');
//该方法接受一个键名作为参数,返回键名对应的值。
//如果xxx对应的value获取不到,那么getItem的返回值是null。
//JSON.parse(null)的结果依然是null。
xxxxxStorage.removeItem('key');
//该方法接受一个键名作为参数,并把该键名从存储中删除。
xxxxxStorage.clear()
//该方法会清空存储中的所有数据。
【vuex】
- 定义
在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。- 什么时候用
多个组件依赖于同一状态;
来自不同组件的行为需要变更同一状态。
//src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
//保存具体的数据
state: {
sum: 1
},
//修改state中的数据
mutations: {
JIA(state, value){
state.sum += value;
...
},
JIAN(state, value){
state.sum -= value;
}
},
//响应组件中用户的动作
actions: {
jia(context, value){
...
context.commit('JIA', value);
}
},
//用于将state中的数据进行加工
getters: {
bigSum(state){
return state.sum*10;
}
}
})
//main.js
import store from './store'
new Vue({
render: h => h(App),
store
}).$mount('#app')
<h1>{{ sum }}</h1>
<h1>{{ bigSum }}</h1>
<button @click="add(n)"></button>
<button @click="sub(n)"></button>
import {mapSate, mapGetters} from 'Vuex'
import {mapActions, mapMutations} from 'Vuex'
...
computed:{
//对象写法
...mapState({sum: 'sum'}),
//数组写法
...mapState(['sum']),
...mapGetters(['bigSum']),
},
methods:{
add(value){
this.$store.dispatch('jia', value);
},
//等同于(不传参会默认传事件对象)
//对象写法
...mapActions({add: 'jia'}),
//数组写法(html函数名要和action函数名一样)
...mapActions(['jia']),
sub(value){
this.$store.commit('JIAN', value);
},
//等同于
...mapGetters({add: 'JIA'}),
...mapGetters(['JIA']),
}
模块化和命名空间
//src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import countAbout from './count'
import personAbout from './person'
Vue.use(Vuex);
export default new Vuex.Store({
modules:{
countAbout,
personAbout
}
})
//src/store/count.js
export default{
namespaced: true,
state:{
...
},
mutations:{
...
},
actions:{
...
},
getters: {
...
}
};
//src/store/person.js
export default {
...
};
//state
this.$store.state.personAbout.sum
...mapState('countAbout', ['sum'])
//getters
this.$store.getters['personAbout/firstPersonName']
...mapGetters('countAbout', ['bigSum'])
//actions
this.$store.dispatch('personAbout/addPersonWang', person)
...mapActions('countAbout', ['jia'])
//mutations
this.$store.commit('personAbout/ADD_PERSON', person)
...mapMutations('countAbout', ['JIA'])
【SPA】
单页面应用
Single Page web Application
- 整个应用只有一个完整的页面。点击页面中的导航链接不会刷新页面,只做页面的局部刷新。
- 数据需要通过ajax请求获取。
【vue-router】
- 路由组件通常存放在
pages
或views
文件夹,一般组件通常存放在components
文件夹。- 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的$route属性,里面存储着自己的路由信息。
整个应用只有一个router,可以通过组件的$router属性获取到。
//src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
//引入Layout组件
import Aoubt from '../component/About'
import Home from '../components/Home'
Vue.use(VueRouter)
export default new VueRouter({
routes:[
{
path: '/about',
component: About
},
{
path: '/home',
component: Home
}
]
})
//main.js
import router from './router'
new Vue({
render: h => h(App),
router
}).$mount('#app')
//点击跳转
<router-link active-class="active" to="/about"></router-link>
//指定展示位置
<router-view></router-view>
嵌套路由
//src/router/index.js
export default new VueRouter({
routes:[
{
path: '/about',
component: About
},
{
path: '/home',
component: Home,
//通过children配置子级路由
children:[{
path: 'news', //此处一定不要写:/news
component: News
},
{
path:'message',//此处一定不要写:/message
component: Message,
children:[{
//params必须配置name
name: 'xiangqing',
path: 'detail',
//params参数
path: 'detail/:id/:title',
component: Detail
}]
}]
}
]
})
//子路由
//路径要写完整
<router-link to="/home/news">News</router-link>
//query参数
//不用配置路由
//字符串写法
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">跳转</router-link>
//对象写法
<router-link
:to="{
path:'/home/message/detail',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
//接受参数
this.$route.query.id
this.$route.query.title
//命名路由
<router-link :to="{name: 'xiangqing'}">News</router-link>
<router-link
:to="{
name:'xiangqing',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
//params参数
//要配置路由
//字符串写法
<router-link :to="`/home/message/detail/${m.id}/${m.title}`">跳转</router-link>
//对象写法
<router-link
:to="{
//必须使用name不能使用path
name:'xiangqing',
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
//接受参数
this.$route.params.id
this.$route.params.title
路由的props
- 通过路由配置传递参数给组件。
//src/router/index.js
export default new VueRouter({
routes:[
{
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,
//对象
//该对象中所有key-value组合最终都会通过props传给Detail组件。
props: {
a: 1,
b: 'hello'
},
//布尔值
//true则把路由收到的所有params参数通过props传给Detail组件,但是就收不到query参数了。
props: true,
//函数
//该函数返回的对象中每一组key-value都会通过props传给Detail组件。
props(route){
return {
id: route.query.id,
title: route.query.title
}
}
}]
}]
}
]
})
//Detail组件
<h1>{{a}}-{{b}}</h1>
<h1>{{id}}-{{title}}</h1>
export default({
name: 'Detail',
//对象
props: ['a', 'b']
//布尔值、函数
props: ['id', 'title']
})
replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式。
- 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push。
<router-link replace>News</router-link>
编程式路由导航
- 作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活。
this.$router.push({
name: 'xiangqing',
params: {
id: xxx,
title: xxx
}
})
this.$router.replace({
name: 'xiangqing',
params: {
id: xxx,
title: xxx
}
})
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go(-1) //可前进也可后退
缓存路由组件
- 作用:让不展示的路由组件保持挂载,不被销毁。
//写的是组件名
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
<keep-alive :include="['News', 'Msg']">
<router-view></router-view>
</keep-alive>
activated()、deactived()
- 新生命周期钩子
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
activated(){}路由组件被激活时触发。
deactivated(){}路由组件失活时触发。
路由守卫
//src/router/index.js
...
const router = new VueRouter({
routes: [
{
name: 'zhuye',
path: '/home',
component: Home,
meta: {
title: '主页'
},
children: [
{
name: 'xiwen',
path: 'news',
component: News,
meta: {
isAuth: true,
title: '新闻'
},
//独享路由守卫
//只有前置
beforeEnter: (to, from, next)=>{
...//类似全局前置路由守卫
}
},
{
name: 'xiaoxi',
path: 'message',
component: Message,
meta: {
isAuth: true,
title: '消息'
}
}
]
}
]
});
//全局前置路由守卫
//初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
//判断是否需要鉴权
if(to.meta.isAuth){
if(localStorage.getItem('school')=== 'atguigu'){
//放行
next();
}else{
alert('学校名不对,无权限查看!');
}
}else{
next();
}
});
//全局后置路由守卫
//初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
document.title = to.meta.title || 'vue_test';
});
export default router;
//组件.vue
export default{
...
//组件内守卫
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter(to, from, next){
...
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave(to, from,next){
...
}
}
hash值
- /#及其后面的内容就是hash值。
- hash值不会包含在 HTTP 请求中,即hash值不会带给服务器。
路由的两种工作模式
- hash模式
地址中永远带着#号,不美观 。
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。- history模式:
地址干净,美观 。
兼容性和hash模式相比略差。
存在刷新页面服务端404问题,若要应用部署上线需要后端人员支持(nodejs:connect-history-api-fallback;nginx)。
【打包】
npm init
npm i express
npm i connect-history-api-fallback
//server.js
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(5000, (err)=>{
if(!err)console.log('服务器启动成功')
})
//把vue项目npm run build后的dist里的文件放到当前目录下的static里。再node server就可以localhost:5000访问到。
node server