Vue核心
Vue简介
动态构建用户界面渐进式的JavaScript
框架
特点:
- 遵循MVVM模式
- 编码简洁、体积小,运行效率高,适合移动 /
PC
开发 - 它本身只关注
UI
, 也可以引入其它第三方库开发项目
初识Vue
- 想让
Vue
工作,就必须创建一个Vue
实例,且要传入一个配置对象 - 容器内依旧符合
HTML
语法,只是加了一些特殊的Vue
语法 - 容器内的代码被称为:
Vue
模板
模板语法
插值语法
- 功能:用于解析标签体内容
- 语法:
{{xxx}}
,xxx会作为js表达式解析
<body>
full name: <span>{{fullname()}}</span>
</body>
<script>
new Vue({
el:'',
data: {
},
methods: {
fullname(){
return 'xx';
}
}
})
</script>
指令语法
- 功能:解析标签属性,解析标签体内容,绑定事件
v-bing:href=‘xxx'
,xxx会作为js表达式解析
//作为js表达式解析,意味着可以直接使用js能用的方法
<h1>{{name}}</h1>
<a :href="school.name.toUpperCase()">xx</a>
数据绑定
- 单向绑定(
v-bind
):数据只能从data流向页面 - 双向绑定(
v-model
):data和页面互通
- 双向绑定一般都应用在表单类元素(如input,select)
v-model:value
可以简写为v-model
,因为默认采集的就是value值
el和data的两种写法
el
//第一种
new Vue({
el: '#app',
data: {
},
})
//第二种
const vm = new Vue({
data: {
},
})
vm.$mount('#app');
data
//第一种,对象式
data: {
name: 'xx',
}
//第二种,函数式。组件一定要用这种写法
data(){
return{
name: 'xx',
}
}
MVVM
- M:model,模型。对应data中的数据
- V:view,视图。模板
- VM:viewmodel,vue实例对象
- data中所有的属性最终都出现在了vm身上
- vm身上的所有属性以及Vue原型上所有属性,在Vue模板中都可以直接使用
数据代理
通过一个对象代理对另一个对象中的属性的操作(读 / 写)
let number = 18;
let person = {
name: 'xx',
sex: 'a',
};
Object.defineProperty(person, 'age', {
value: 18;//值
enumrable: true;//可枚举的,就是能不能遍历得到
writeble: true;//可修改的
configurable: true;//可删除的
//当有人读取person的age属性时,该函数就会被调用,返回值就是age的值
get(){
return number;
}
//当有人修改age属性时,该函数就会被调用,且会收到修改的具体值
set(value){
number = value;
}
})
事件
基本使用
- 使用
v-on
或者@xx
绑定事件,xx是事件名 - 事件的回调需要配置在methods对象中
- methods中配置的函数**不要使用箭头函数!**否则this就会变成window
@click="demo"
和@click="demo($event)"
效果一致,但是后者可以传参。$event
是默认传递的,如果配置了其他形参,则需要在形参列表使用$event
占位- 绑定事件的时候可以写一些简单的语句(但是不推荐)
事件修饰符
-
prevent:阻止默认事件(常用)
<a href="https://www.baidu.com" @click.prevent="show">click me</a>
-
stop:阻止事件冒泡(常用)
<div @click="show"> <!--没加stop修饰之前,会调用两次show方法,就是事件冒泡--> <button @click.stop="show"> click me </button> </div>
-
once:事件只触发一次(常用)
-
capture:使用事件的捕获模式
-
self:只有
event.target
是当前操作的元素才触发事件 -
passive:事件的默认行为立刻触发,无需等待事件回调执行完毕
事件修饰符可以连这些,先后发挥作用
@click.stop.prevent="show"
键盘事件
-
常用的按键别名:
- 回车,enter
- 删除,delete
- 退出,esc
- 空格,space
- 换行,tab(特殊,必须配合keydown使用才有效果)
- 上,up
- 下,down
- 左,left
- 右,right
<!-- 松开 enter键 后调用方法--> <input type="text" @keyup.enter="show"/>
-
Vue未提供别名的按键,可以使用按键原始的key值去绑定,但要注意转为kebab-case(短横线命名)
<!-- 松开 capslock键 后调用方法 --> <!-- 所有按键的名,可以通过默认传递的event参数,通过event.key输出按键名,event.keyCode输出按键值--> <input type="text" @keyup.caps-lock="show"/>
-
系统修饰符(用法特殊):ctrl,alt,shift,meta
- 配合keyup事件使用:按下修饰符的同时,再按下其他键,随后释放其他键,事件才会触发
- 配合keydown事件使用:正常触发事件
<!-- 按下修饰符的同时,再按下其他键,随后释放其他键,事件才会触发 --> <input type="text" @keyup.ctrl="show"/> <!-- 也可以直接指定使用什么组合 如下就是使用ctrl+y才会触发 --> <input type="text" @keyup.ctrl.y="show"/>
-
也可以使用keycode去指定具体的按键(不推荐)
<input type="text" @keyup.13="show"/>
-
Vue.config.keyCodes.自定义键名 = 键码
,可以定制<body> <input type="text" @keyup.自定义键名="show"/> </body> <script> Vue.config.keyCodes.自定义键名 = 自定义键码 </script>
自定义事件
-
一种组件间通信的方式,适用于:子组件—》父组件
-
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件,事件的回调函数在A中
-
父组件绑定自定义事件:
-
在父组件中:
<Demo @myEvent="callback" />
或<Demo v-on:myEvent="callback" />
,callback函数定义在父组件中 -
在父组件中:
<template> <!-- 1.声明ref --> <Demo ref="demo"/> </template> <script> new Vue({ methods: { // 2.定义处理方法 test(){ ... } }, mounted(){ // 3.绑定,接收并使用test方法处理myEvent事件 this.$refs.demo.$on('myEvent', this.test); } }) </script>
-
-
子组件触发自定义事件:
this.$emit('myEvent', 数据)
<template> <button @click="xx"></button> </template> <scrpit> methods: { xx(){ this.$emit('myEvent', data); } } </scrpit>
-
解绑自定义事件:
this.$off('myEvent')
,解绑myEvent事件;this.$off([]'myEvent','demo'])
,解绑myEvent和demo事件;this.$off()
,解绑所有事件; -
组件上也可以绑定原生DOM事件,需要使用
native
修饰符,例如:<Demo @click.native="callback" />
。没加修饰符只能通过$emit
来触发
全局事件总线
-
一种组件间通信的方式,适用于任意组件间通信
-
安装全局事件总线:
new Vue({ ... beforeCreate(){ // 一般取名叫$bus,也是为了迎合Vue的规范。 // this就是当前的vm。因为vm身上有$on,$off,$emit,$once这些方法,所以弄出一个$bus这样的傀儡对象来使用 Vue.prototype.$bus = this; } ... })
-
使用:
-
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调函数在A中
methods:{ demo(data){} } mounted(){ // 接收并使用demo方法处理xxx事件 this.$bus.$on('xxx', this.demo) }
-
提供数据:
this.$bus.$emit('xxx', data)
-
-
最好在beforeDestroy中,用$off去解绑当前组件所用到的事件,因为bus只有一个,事件之间可能会重名
计算属性和监视
computed
- 定义:要用的属性不存在,要通过已有属性计算得来
- 原理:底层借助了
Object.defineproperty
方法提供的getter和setter - get函数什么时候执行?
- 初次读取的时候执行,并缓存可供复用
- 当依赖的数据发生变化
- 计算属性最终会出现在vm中,可以直接使用
<body>
<span>{{fullName1()}}</span>
<!--表面上fullName2是一个函数,实则是vue执行了fullName2声明的函数然后生成了一个fullName2属性,放在了vm里可供直接调用 -->
<span>{{fullName2}}</span>
</body>
<script>
computed: {
//完整写法
fullName1: {
get(){
...
}
set(){
....
}
}
//简写,考虑到计算属性一般不会涉及修改才能简写
fullName2(){
....
}
}
</script>
watch
- 监视属性必须存在才有意义,可以是data里的,也是可以
computed
计算出来的 - 当属性变化时,回调函数自动调用,在函数内部进行计算
两种写法
- new Vue传入
watch
配置 - 通过
vm.$watch
监视
<script>
//方式一
new Vue({
el:'',
data: {
name: 'ss'.
}
watch: {
name: {
//具体的配置信息以及回调函数
}
}
})
//方式二
let vm = new Vue({
...
})
vm.$watch('要监视的值', {
//具体的配置以及回调函数
})
</script>
监视多级结构中某个属性的变化
<script>
new Vue({
data: {
numbers: {
a: 1,
b: 2,
}
},
watch: {
//最原始的写法,只是平时我们省略了引号
'numbers.a':{
//hanlder函数可以获取新旧值
handler(newValue, oldValue){
...
}
},
'numbers.b':{
handler(){
...
}
},
},
watch: {
//第二种写法
numbers: {
deep: true,
handler(){
...
}
}
}
})
</script>
简写形式
只关注handler函数,其他配置不关心时才可以这么写
<script>
new Vue({
data: {
name: 'xx',
}
watch: {
name(){
//平时handler的处理逻辑照常写
}
}
})
</script>
区别
computed
能完成的,watch
也能完成;反之不能,watch
可以进行异步操作
绑定样式
绑定class
<style>
.normal{}
.happy{}
.sad{}
.class1{}
.class2{}
.class3{}
</style>
<body>
<!--字符串写法,适用于:样式类名不确定,需要动态指定 -->
<div :class="mood" @click="changeMood"></div>
<!--数组写法,适用于:要绑定的样式个数不确定,名字也不确定 -->
<div :class="classArr"></div>
<!--对象写法,适用于:要绑定的样式个数确定,名字也确定,但需要动态指定用不用 -->
<div :class="classObj"></div>
</body>
<script>
new Vue({
el:'#root',
data:{
name: 'xx',
mood: 'normal',
classArr: ['class1', 'class2', 'class3'],
classObj: {
class1: false,
class2: false,
}
},
methods:{
changeMood(){
this.mood = 'happy';
}
}
})
</script>
绑定style
<body>
<div :style="styleObj"></div>
<div :style="{fontSize: }"></div>
</body>
<script>
new Vue({
el:'#root',
data:{
name: 'xx',
styleObj: {
//这里需要写style原本就有样式名,原本的短横线写法该为驼峰写法,比如
fontSize: '40px',
backgroudColor: 'orange',
}
},
})
</script>
条件渲染
v-if
v-if="表达式"
v-else-if="表达式"
v-else
适用于:切换频率比较低的场景
特点:不展示的DOM元素直接移除
注意:上三个可以配合使用,但是中间不能被其他的打断
v-show
v-show="表达式"
适用于:切换频率比较高的场景
特点:不展示的DOM元素未被移除,只是隐藏
使用
v-if
的时候元素可能无法获取到,v-show
一定可以获取到本质就是因为
v-if
会移除DOM元素
列表渲染
基本使用
可以使用v-for
遍历数组,对象,字符串(用的少),指定次数(用的更少)
v-for="(item,index) in xxx" :key="xxx"
<!-- 括号里写的是遍历时的默认参数 -->
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<ul>
<li v-for="(p,index) in persons" :key="index">
{{p.name}}-{{p.age}}
</li>
</ul>
<!-- 遍历对象 -->
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,k) in car" :key="k">
{{k}}-{{value}}
</li>
</ul>
<!-- 遍历字符串 -->
<h2>测试遍历字符串(用得少)</h2>
<ul>
<li v-for="(char,index) in str" :key="index">
{{char}}-{{index}}
</li>
</ul>
<!-- 遍历指定次数 -->
<h2>测试遍历指定次数(用得少)</h2>
<ul>
<li v-for="(number,index) in 5" :key="index">
{{index}}-{{number}}
</li>
</ul>
key的原理
-
虚拟DOM中key的作用
key是虚拟DOM对象的标识,当数据发生改变时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行DOM差异比较,规则如下:
- 旧DOM中找到了与新DOM相同的key
- 如内容没变,则直接使用
- 如内容改变,生成新的DOM替换
- 未找到相同的key,创建新的DOM渲染到页面中
- 旧DOM中找到了与新DOM相同的key
-
用index作为key的问题
- 若对数据进行:逆序添加,逆序删除等破坏顺序的操作,会产生没有必要的真实DOM更新。界面没问题,但是效率低
- 如果结构中还包含输入类的DOM,页面就会出错,对应不上的问题,错位。
-
最好使用唯一标识来作为key
监视数据原理
-
Vue会监视data中所有层次的数据
-
如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据
-
对象中后追加的属性,Vue默认不做响应式处理
-
如需给后添加的属性做响应式处理,请使用:
Vue.set(target, propertyName/index, value) 或 this.$set(target, propertyName/index, value)
-
-
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质上就是做了两件事:
- 调用原生对应的方法对数组进行修改
- 重新解析模板,进而更新页面
-
在Vue修改数组中某个元素一定要用如下方法:
1. push(),pop(),shift(),unshift(),splice(),sort(),reverse() 2. Vue.set() this.$set()
Vue.set()
和this.$set()
不能给vm或vm的根数据对象添加属性
v-model
主要用于收集表单数据。
input.type=text
,则收集的是用户输入的valueinput.type=radio
,则收集的是用户输入的value,且需要配置value的值input.type=checkbox
- 没有配置value属性,则收集的是checked(勾选与否,布尔值)
- 配置了value属性:
v-model
的初始值是非数组,收集的是checked- 初始值是数组,收集的就是value组成的数组
v-model
有三个修饰符:
lazy
:控件失去焦点后收集数据number
:输入的字符串转为有效的数字trim
:去除数据的首尾空格
过滤器
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
语法:
- 注册过滤器:
Vue.filter(name, callbakc)
或者new Vue({ filters: {} })
- 使用过滤器:
{{ xxx | 过滤器名 }}
或v-bind:xx="xxx | 过滤器"
注意:
- 过滤器默认传递正在使用该过滤器的值作为参数,也可以接收额外的参数。
- 多个过滤器可以串联,xx传递给过滤器1,过滤器1的返回值传递给过滤器2做参数
- 并没有改变原本的数据,是产生了新的对应的数据
内置指令
常用内置指令
-
v-text
: 更新元素的 textContent -
v-html
: 更新元素的 innerHTML。会替换掉节点中所有的内容,{{xx}}不会;且可以识别HTML结构 -
v-if
: 如果为 true, 当前标签才会输出到页面 -
v-else
: 如果为 false, 当前标签才会输出到页面 -
v-show
: 通过控制 display 样式来控制显示/隐藏 -
v-for
: 遍历数组/对象 -
v-on
: 绑定事件监听, 一般简写为@ -
v-bind
: 绑定解析表达式, 可以省略 v-bind -
v-model
: 双向数据绑定以下只是一个属性,不需要接收一个表达式
-
v-cloak
: 防止闪现(数据太多有时候绑定没那么快,会在页面直接显示出插值语法的花括号),一般与css
的属性选择器配合使用:[v-cloak] { display: none }
-
v-once
:所在节点在初次动态渲染后,就视为静态内容了。以后数据改变不会影响所在结构的更新 -
v-pre
:跳过其所在节点的编译过程,可以利用它跳过没有指令语法,插值语法的节点,加快编译
自定义指令
注册全局指令
<script>
//el和binding分别接收两个参数:一个是原生DOM元素,就是当前指令和哪个DOM绑定;另一个是一个绑定关系,内部会有一些相关的数据
Vue.directive('my-directive', function(el, binding){
el.innerHTML = binding.value.toupperCase()
})
</script>
注册局部指令
<script>
new Vue({
//指令里出现的this都是只想window,因为指令本质就是DOM操作
directives : {
//完整写法,多级单词需要用 - 隔开,且用引号包裹
'my-directive': {
//bind是内置的函数,类似接口。现在是重写,还有inserted,update
bind (el, binding) {
el.innerHTML = binding.value.toupperCase()
},
inserted (el, binding) {
...
},
update (el, binding) {
...
}
},
//简写,相当于只写了bind函数和update函数
'my-directive'(el, binding){
el.innerHTML = binding.value.toupperCase();
},
},
})
</script>
插槽—slot
-
作用:让父组件可以向子组件指定位置插入html结构,也是一种父子通信方式,适用于父组件—》子组件
-
分类:默认插槽,具名插槽,作用于插槽
-
使用方式:
-
默认插槽:只有一个坑
父组件: <Category> <!-- 以下div会出现在子组件的slot中 --> <!-- 在这填坑 --> <div> html结构 </div> </Category> 子组件Category: <template> <div> <!-- 在这挖个坑,声明有个坑 --> <slot>插槽默认内容...</slot> </div> </template>
-
具名插槽:多个坑
父组件: <Category> <!-- 第一个坑,写法1 --> <template slot="center"> <div> html结构 </div> </template> <!-- 第二个坑,写法2 --> <template v-slot:footer> <div> html结构 </div> </template> </Category> 子组件Category: <template> <div> <!-- 定义插槽,挖了两个坑 --> <slot name="center">default things...</slot> <slot name="footer">default things...</slot> </div> </template>
-
作用域插槽:根据Category使用的作用域不同,会有不同的样式。例如第一个作用域想用无序列表,第二个作用域想用有序列表
父组件中: <Category> <template scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <!-- scopeData是一个对象,里面含有子组件传过来的值 --> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category> <Category> <template scope="scopeData"> <!-- 生成的是ol列表 --> <ol> <!-- scopeData是一个对象,里面含有子组件传过来的值 --> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ol> </template> </Category> 子组件Category中: <template> <div> <!-- 挖坑,给使用这个插槽的人传递对象数据,对象中包含games属性,games属性的值是gamesHere --> <slot :games="gamesHere"></slot> </div> </template> <script> export default { name: 'Category', data(){ return{ gamesHere: ['a','b','c'], } }, } </script>
-
生命周期
又叫:生命周期回调函数,生命周期函数,生命周期钩子,是Vue在关键时刻帮我们调用的一些特殊名称的函数。
生命周期函数的名字不可更改,但函数的具体内容由实际而定
生命周期函数中的this
指向的是vm或组件实例对象
Vue组件化编程
非单文件组件
基本使用
-
使用
Vue.extend(options)
创建,其中options和new Vue时传入的那个options几乎一样,区别如下:- el不要写——最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
- data必须写成函数——避免组件被复用时,数据存在引用关系
使用
template
标签可以配置组件结构,也就是HTML结构 -
注册组件。
- 局部注册:
new Vue
时传入components
选项 - 全局注册:
Vue.component('组件名', 组件)
- 局部注册:
-
在HTML中编写组件标签即可
注意点
-
关于组件名:
-
一个单词组成:
- 首字母大 / 小写
-
多个单词组成:
- kebab-case命名:my-school
- CameCase命名:MySchool(需要Vue脚手架支持)
- 组件名尽可能回避HTML中已有的元素名称
- 可以使用name配置项指定组件在开发者工具中呈现的名字
-
-
关于组件标签:
- ,不用脚手架会导致后续组件不能渲染
VueComponent
- 组件本质是一个名为
VueComponent
的构造函数,且不是程序员定义的,是Vue.extend
生成的 - 我们只需要写自定义的组件名,Vue解析时会帮我们创建组件的实例对象,即Vue帮我们执行的:
new VueComponent(options)
- 特别注意:每次调用
Vue.extend
,返回的都是一个全新的VueComponent
- 关于
this
的指向:- 组件配置中:data函数,methods中的函数,watch中的函数,computed中的函数,都是指向
VueComponent
实例对象 new Vue()
配置中:data函数,methods中的函数,watch中的函数,computed中的函数,都是指向Vue
实例对象- vm管理着一个个的vc
- 组件配置中:data函数,methods中的函数,watch中的函数,computed中的函数,都是指向
一个重要的内置关系
具体组件.prototype.__proto__ === Vue.prototype
意义:让组件实例对象vc可以访问到Vue原型上的属性、方法
单文件组件
创建组件
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name:'School',
data(){
return {
name:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.name)
}
},
}
</script>
<style>
.demo{
background-color: orange;
}
</style>
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
age:18
}
}
}
</script>
App组件汇总
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
//引入组件
import School from './School.vue'
import Student from './Student.vue'
export default {
name:'App',
components:{
School,
Student
}
}
</script>
程序的主入口——main.js
import App from './App.vue'
new Vue({
el:'#root',
template:`<App></App>`,
components:{App},
})
页面引用——index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>练习一下单文件组件的语法</title>
</head>
<body>
<!-- 准备一个容器 -->
<div id="root"></div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
流程
- 拆分静态组件:组件要按照功能点拆分,命名不要与HTML冲突
- 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
- 一个组件在用:放在组件自身就可以
- 多个组件在用:放在他们共同的父组件(状态提升)
- 实现交互
使用Vue脚手架
render函数
new Vue({
el: '#root',//挂载方式1
render: h => h(App组件,程序入口的那个组件),
}).$mount('#root')//挂载方式2
- vue.js与vue.runtime.xxx.js的区别:vue.js是完整版的vue,包含:核心功能+模板解析器;另一个没有模板解析器
- 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去制定具体内容
修改默认配置
- 使用vue inspect > output.js命令可以查看vue脚手架的默认配置
- 创建并使用vue.config.js可以对脚手架进行个性化定制,具体参数详情看官网
ref属性
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上获取的是真实的DOM元素,应用在组件标签上获取的是组件实例对象vc
- 使用方式:
<h1 ref="xxx"></h1> 或 <school ref="xxx"></school>
- 获取:
this.$ref.具体的ref名称
props配置
功能:让组件接收外部传来的数据,就类似HTML标签的各种属性。还可以用来父子组件通信(但是更多的还是使用自定义事件来通信)
- 传递数据:
<Demo name="xxx" :age="18">
- 接收数据:
- 只接收:
props: ['name', 'age']
- 限制类型:
props: {name: String, age: Number}
- 限制类型+限制必要性+指定默认值:
props: {name: {type: String, required: true, default: 'xx'}, }
- 只接收:
props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告
这里要理解好什么算修改:对于基本数据类型,修改值就是修改;对于对象数组而言,没有改变地址就不算修改,修改内部的数据不算
适用于:
-
父组件—》子组件通信
-
子组件—》父组件通信,要求父先给子传递一个函数
<!-- 父组件 --> <template> <!-- 2.给子组件传递 --> <MyFooter :checkAllTodo="checkAllTodo"></MyFooter> </template> <script> import MyFooter from './components/MyFooter' export default { name: 'App', components: {MyFooter}, methods: { // 1.声明一个需要给子组件使用的方法 checkAllTodo(){ ... } } } </script> <!-- 子组件 --> <template> <input type="checkbox" @change="checkAll"/> </template> <script> import MyFooter from './components/MyFooter' export default { name: 'App', // 3.声明接收 props: ['checkAllTodo'], methods: { checkAll(){ // 4.使用 this.checkAllTodo(); } } } </script>
mixin混入
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
-
定义混合,例如:
//xx.js export const mixin = { data(){}, methos:{} }
-
使用混合
-
全局混入:在main.js中import对应的混入文件,使用
Vue.mixin(xxx)
-
局部混入:
new Vue({ data(){}, methods: {}, mixins: [mixin],//只有一个也要写成数组形式 })
-
如果mixin配置的是钩子函数,比如mounted,则会先执行mixin中配置的钩子函数,再执行本身的钩子函数
如果配置的是其他信息,则就近原则。
插件
功能:用于增强Vue
本质:包含install方法的一个对象,install方法的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
定义插件:
//plugins.js
export default {
install(Vue, options){
...
}
}
使用插件:
//main.js
import 插件名(随意) from '插件路径'
Vue.user(插件名, 参数1, 参数2...);
scoped样式
作用:让样式在局部生效,防止同名冲突
写法:<style scoped>
浏览器存储
- 存储内容大小一般支持5MB左右,不同浏览器不一样
- 浏览器通过
Window.sessionStorage
和Window.localStorage
属性来实现本地存储机制 - 相关API:
- xxxStorage.setItem
- xxxStorage.getItem
- xxxStorage.removeItem
- xxxStorage.clear
- sessionStorage存储的内容会随着浏览器窗口关闭而消失
- LocalStorage存储的内容,需要手动消除才会消失
- getItem如果对应的value获取不到,则会返回null
- JSON.parse(null)的结果依然是null,JSON.stringfy()用于转化对象
消息订阅与发布(以pubsub举例)
- 一种组件间的通信的方式,适用于任意组件间通信
- 使用步骤:
- 安装pubsub:
npm i pubsub-js
- 引入pubhub:
import pubhub from 'pubhub-js'
- 接收数据:
this.pid = pubhub.subscribe('xxx', 回调函数)
,pid是用于解绑事件 - 提供数据:
pubhub.publish('xxx',data)
- 最好在beforeDestroy中使用
pubhub.unsubscribe('xxx')
取消订阅
- 安装pubsub:
$nextTick
- 语法:
this.$netxTick(回调函数)
- 作用:在下一次DOM更新结束后执行其指定的回调
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
过度与动画
-
作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。
-
图示:
-
写法:
-
准备好样式(css):
- 元素进入的样式:
- 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">xx</h1> </transition>
- 配置了name属性后,上述的
v-enter
等就需要改写成hello-enter
,v-enter
只是默认 - 若有多个元素需要过度,则需要使用
<transition-group>
,且每个元素都要制定key值
- 配置了name属性后,上述的
-
Vue中的Ajax
配置代理
代理服务器就像一个中间商,对前端来说,和前端保持同源;对后端来说也保持同源(伪同源),这样就可以接收数据并转发给前端
方式一
在Vue.config.js中添加如下配置:
devServer: {
proxy: "http://localhost:5000"
}
- 优点:配置简单,请求资源时直接发给前端即可。前端直接使用axios不会有跨域问题
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先匹配前端资源,public目录下的文件如果匹配上了就会直接返回,不走代理)
方式二
编写Vue.config.js配置,具体如下:
module.export = {
devServer: {
proxy: {
'/api1': {//匹配所有以‘/api1’开头的请求
target: 'http://localhost:5000',//代理目标的基础路径
changeOrigin: true,//设置为true时,服务器收到的请求头中的host为:localhost:5000;反之为8080。
//就类似是否说谎,false就表示老老实实承认自己是为8080代理。
pathRewrite: {"正则表达式" : "替换成什么字符"},
},
'/api2': {//匹配所有以‘/api2’开头的请求
target: 'http://localhost:5000',
changeOrigin: true,
pathRewrite: {"正则表达式" : "替换成什么字符"},
}
}
}
}
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
- 缺点:配置繁琐,请求资源时必须加前缀
Vuex
概念
专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间的通信方式,且适用于任意组件间的通信
三大部分的外围其实是由store所管理
何时使用
- 多个组件依赖同一状态
- 来自不同组件的行为需要变更同一状态
搭建
-
创建文件:
src/store/index.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex Vue.use(Vuex); //准备actions对象——响应组件中用户的动作 const actions = {}; //准备mutations对象——修改state中的数据 const mutations ={}; //准备state对象——保存具体的数据 const state = {}; //创建并暴露 export default new Vuex.Store({ actions, mutations, state })
-
在main.js中创建vm时传入store配置项
import store from './store/index.js' new Vue({ el: '#app', render: h => h(App), store, })
基本使用
-
初始化数据,配置actions,mutations,操作文件store.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex Vue.use(Vuex); //准备actions对象——响应组件中用户的动作 const actions = { //默认传递两个参数,一个是上下文对象,一个是传递的value //context对象里包含commit,dispatch方法,state数据 jia(context, value){ context.commit('JIA', value); }, }; //准备mutations对象——修改state中的数据 const mutations ={ JIA(state, value){ state.sum += value; }, }; //准备state对象——保存具体的数据 const state = { sum: 0, }; //创建并暴露 export default new Vuex.Store({ actions, mutations, state })
-
组件中读取vuex中的数据:
$store.state.sum
-
组件中修改vuex中的数据:
$store.dispatch(actions中的方法名, 数据)
或$store.commit(mutations中的方法名, 数据)
若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接写commit
getters的使用
概念:当state中的数据需要加工后才能使用时,可以使用getters加工
- 操作store/index.js
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex
Vue.use(Vuex);
//准备actions对象——响应组件中用户的动作
const actions = {
...
};
//准备mutations对象——修改state中的数据
const mutations ={
...
};
//准备state对象——保存具体的数据
const state = {
...
};
// 用于将state中的值进行加工
const getters = {
bigSum(state){
return state.xx * 10;
}
};
//创建并暴露
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
- 组件中使用:
$store.getters.bigSum
四个map方法的使用
mapState
:用于帮助我们映射state
中的数据为计算属性mapGetters
:用于帮助我们映射getters
中的数据为计算属性mapActions
:用于帮助我们生成actions
对话的方法,即:包含$store.dispatch(xxx)
的函数mapMutations
:用于帮助我们映射mutations
对话的方法,即:包含$store.commit(xxx)
的函数
<script>
import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
export default {
name: 'xx',
data() {
},
computed: {
// 1.mapState
// 借助mapState生成计算属性:sum,school,subject(对象写法)
// 一定要写成字符串形式,因为不写就会被当成变量,在state中就可能出现找不到该变量的情况,写成字符串才会找对应名称的值
...mapState({sum: 'sum', school: 'school', subject: 'subject'}),
// 数组写法
...mapState(['sum', 'school', 'subject']),
// 2.mapGetters
// 借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum: 'bigSum'}),
// 数组写法
...mapGetters(['bigSum']),
},
method: {
// 1.mapActions
// 靠mapActions生成:incrementOdd,incrementWait(对象写法)
...mapActions({incrementOdd:'jiaOdd', incrementWait:'jiaWait'}),
// 靠mapActions生成:incrementOdd,incrementWait(数组写法)
...mapActions(['jiaOdd', 'jiaWait']),
// 2.mapMutations
// 靠mapMutations生成:increment,decrement(对象写法)
...mapMutations({increment:'JIA', decrement:'JIAN'}),
// 靠mapMutations生成:increment,decrement(数组写法)
...mapMutations(['JIA', 'JIAN']),
}
}
</script>
mapActions和mapMutations使用时,若需要传递参数,需要在模板中绑定事件时传递好参数,否则参数是事件对象
例如:
<button @click="increnment(n)"></button>
因为使用mapActions生成时,它生成的是:increment(value){…对value的操作…},所以在绑定事件时,如果不指明参数,方法也会默认传递参数,这个参数是事件对象
模块化+命名空间
- 目的:让代码更好维护,让多种数据分类更加明确
- 修改store/index.js
const countAbout = {
// 开启命名空间
namespaced: true,
state: {x: 1},
mutations: {...},
actions: {...},
getters: {...}
}
const personAbout = {
// 开启命名空间
namespaced: true,
state: {x: 1},
mutations: {...},
actions: {...},
getters: {...}
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout,
}
})
-
开启命名空间后,组件中读取state数据
// 1.自己直接读取 this.$store.state.personAbout.list // 2.借助mapState,不开启命名空间,就会报错:countAbout找不到 ...mapState('countAbout', ['sum','school','subject']) ...mapState('countAbout',{sum: 'sum', school: 'school', subject: 'subject'}),
-
开启命名空间后,组件中读取getters数据
// 1.自己直接读取,getters里是不分的,它是用如下很长的键,来标注 this.$store.getters['personAbout/firstPersonName'] // 2.借助mapGetters,不开启命名空间,就会报错 ...mapGetters('countAbout', ['bigSum'])
-
组件中调用dispatch方法
// 1.自己直接读取,dispatch里是不分的,它是用如下很长的键,来标注 this.$store.dispatch('personAbout/firstPersonName', 参数) // 2.借助mapActions,不开启命名空间,就会报错 ...mapActions('countAbout', {bigSum:'bigSum'})
-
组件中调用commit方法
// 1.自己直接读取,commit里是不分的,它是用如下很长的键,来标注 this.$store.commit('personAbout/firstPersonName', 参数) // 2.借助mapMutations,不开启命名空间,就会报错 ...mapMutations('countAbout', ['bigSum'])
Vue-Router
相关理解
- vue-router:vue的一个插件库,专门用来实现SPA(single page web application)应用
SPA:
- 单页web应用
- 整个应用只有一个完整的页面
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新
- 数据需要通过ajax请求获取
-
路由
路由就是一组映射关系(key-value),key为路径,value可能是function或component
路由分类:
-
后端路由:value为function,用于处理客户端提交的请求。
工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
-
前端路由:value为component,用户展示页面内容
工作过程:当浏览器的路径改变时,对应的组件就会显示。
-
基本使用
-
安装vue-router,命令:
npm i vue-router
-
应用插件:
Vue.use(VueRouter)
-
编写router配置项:
// 引入VueRouter import VueRouter from 'vue-router' // 引入所需组件 import About from '../../' import Home from './../..' // 创建并暴露 export default new VueRouter({ routes: [ { path: '/about', component: About, }, { path: '/home', component: Home, } ] })
-
实现切换(active-class可配置激活时的样式)
<!-- to表示指向的路径 --> <router-link active-class="active" to="/about">About</router-link>
-
指定展示位置
<router-view></router-view>
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在component
文件夹- 通过切换,“隐藏”了的路由组件,默认是被销毁的,需要的时候再重新挂载
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息- 整个应用只有一个
router
,可以通过组件的$router
属性获取到
嵌套路由
-
配置路由规则,使用
children
配置项// 引入VueRouter import VueRouter from 'vue-router' // 引入所需组件 import About from '../../' import Home from './../..' // 创建并暴露 export default new VueRouter({ routes: [ { path: '/about', component: About, }, { path: '/home', component: Home, children: [ { // 此处不要再写 斜杠 path: 'news', component: News } ] } ] })
-
跳转(要写完整路径)
<router-link to="/home/news">News</router-link>
路由传参(query参数)
-
传递参数
<!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path: '/home/message/detail', query:{ id: 666, title: 你好 } }">跳转</router-link>
-
接收参数
$route.query.id $route.query.title
命名路由
-
作用:可以简化路由的跳转
query参数是用
&
拼接的,params参数是直接出现在路由层级里 -
使用
-
给路由命名
routes: [ { name: 'guanyu', path: '/about', component: About, }, { path: '/home', component: Home, children: [ { name: 'xin', // 此处不要再写 斜杠 path: 'news', component: News } ] } ]
-
简化跳转
<!-- 简化前,需要写完整路径 --> <router-link to="/home/xin">跳转</router-link> <!-- 简化后,直接使用name属性跳转 --> <router-link :to="{name: 'xin'}">跳转</router-link> <router-link :to="{ name: 'xin', query: { id: 666, title: '你好' } }">跳转</router-link>
-
param参数
-
配置路由,声明接收params参数
routes: [ { name: 'guanyu', path: '/about', component: About, }, { path: '/home', component: Home, children: [ { name: 'xin', // 使用占位符声明接收params参数 path: 'news/:id/:title', component: News } ] } ]
-
传递参数
<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name: 'xin', params: { id: 666, title: '你好', } }">跳转</router-link>
路由携带params参数时,若使用to的对象写法,必须使用name配置项,不能使用path配置项
-
接收参数
$route.params.id $route.params.title
props配置
作用:让路由组件更方便的收到参数
{
path: '/home',
component: Home,
// 第一种写法,props为对象,该对象中的所有key-value的组合最终都会通过props传给detail组件
props: {a:900,b:'aa'},
// 第二种写法,props为布尔值,为真时,路由收到的所有params参数通过props传给detail组件
props: true,
// 第三种写法,props为函数,该函数饭返回的对象中的所有key-value的组合最终都会通过props传给detail组件
// 这里的route就是回调函数时默认传递的参数,就是$route
props(route){
return {
id: route.query.id,
title: route.params.title
}
}
}
replace属性
- 作用:控制路由跳转时操作浏览器历史纪录的模式
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push是追加历史记录,replace是替换当前记录。路由跳转时默认为push - 开启replace模式:
<router-link replace....>xxx</router-link>
编程式路由导航
-
作用:不借助
<router-link>
来实现路由跳转,让路由跳转更加灵活 -
具体编码
// $router的API this.$router.push({ name: 'xin', params:{ id: 4, title: 'aa' } }) this.$router.replace({ name: 'xin', params:{ id: 4, title: 'aa' } }) // 浏览器的后退功能 this.$router.back() // 浏览器的前进功能 this.$router.forward() // 浏览器的跳转,想跳几步跳几步 this.$router.go(-3)
缓存路由组件
-
作用:让不展示的路由组件保持挂载,不被销毁
-
具体:
<keep-alive include="组件名"> <router-link></router-link> </keep-alive> <keep-alive :include="['News', 'XXX']"> <router-link></router-link> </keep-alive>
keep-alive需要写在需要缓存的页面的父页面中
两个新的生命周期钩子
- 作用:路由组件所独有的钩子,用于捕获路由组件的激活状态
- 具体名字:
activated
:路由组件被激活时触发deactivated
:路由组件失活时触发
路由守卫
- 作用:对路由进行权限控制
- 分类:全局守卫,独享守卫,组件内守卫
全局守卫
// 全局前置守卫:初始化时执行,每次路由切换前执行
router.beforeEach((to, from, next) => {
// meta.isAuth是在路由配置的时候就写好的,路由的元数据
if(to.meta.isAuth){
// 权限控制的具体规则
if(localStorage.getItem('school') === 'xxx'){
// 放行
next();
}else{
alert("school is wrong!");
}
}else{
next();
}
})
// 全局后置守卫:初始化时执行,每次路由切换后执行
router.afterEach((to, from, next) => {
if(to.meta.title){
// 修改网页的title
document.title = to.meta.title;
}else{
document.title = "xxxxx";
}
})
独享守卫
{
path: '/home',
component: Home,
beforeEnter(to, from, next){
......
}
}
独享守卫就是写在某个路由配置项里,且只有前置守卫
组件内守卫
// 进入守卫:必须是通过路由规则进入该组件时调用
beforeRouteEnter(to, from, next){}
// 离开守卫:必须是通过路由规则离开该组件时调用
afterRouteEnter(to, from, next){}
路由器的两种工作模式
- 对于一个url来说,什么是hash值?——#及其后面的内容就是hash值
- hash值不会包含在http请求中,即:hash值不会带给服务器
- hash模式:
- 地址中永远带着#号,不美观
- 若以后将地址通过第三方手机app分享,若app检验严格,则地址会被标记为不合法
- 兼容较好
- history模式:
- 地址干净,美观
- 兼容性和hash模式相比略差
- 应用部署上线时需要后端人员支持,解决刷新页面404的问题