Vue是一套用于 构建用户界面 的 渐进式 JavaScript框架。
渐进式:vue可以自底向上逐层的应用(简单应用:只需一个轻量小巧的核心库;复杂应用:可以引入各种Vue插件库)
一、Vue基础
1.1. v-bind与v-model
v-bind:单项数据绑定,model => view view !=> medel
v-model:双向数据绑定,model <=> view (仅用于表单元素的value)
简写:
1.2.回顾Object.defineProperty
Object.defineProperty:对对象的属性进行操作(vue2.0中的数据劫持、数据代理...底层都有用到)
1.3. vue中的数据代理
数据代理:通过一个对象(vm)对另一个对象(data)的属性进行操作(Object.defineProperty)
1.4. Vue中的事件
a.事件传参问题
@click="fn" 与@click="fn($event,x)" 都可调用事件,后者可直接传递参数
b.事件修饰符(6种)
c.键盘事件
注:tab及系统修饰键(ctrl、alt、shift、meta)建议搭配@keydown.键名 使用
1.5. 计算属性computed 与 监视/侦听属性watch
计算属性:通过已有属性计算得来的新属性(getter/setter——defineProperty)
优点:计算属性computed有缓存,当创建时/所依赖的属性发生变化时才会刷新
监视属性:当监视的属性发生变换时被调用(handler(newVal,oldVal){})
计算属性与监视属性的区别:
当计算属性和监视属性都能实现效果时优先选择计算属性,但涉及到一些异步(比如:定时器setTimeout...)操作的就选监视属性。因为,computed依靠函数的返回值,不能开启异步任务,而watch依靠自身计算不依靠返回结果,可以开启异步任务。
1.6. 样式绑定
a.绑定class样式
字符串写法:适用于样式的类名不确定,需要动态绑定
:class="variable"
数组写法:适用于要绑定的样式个数不确定,名字也不确定
:class="classArr"
对象写法:适用于要绑定的样式个数确定,名字也确定,但要动态决定是否使用
:class="classObject"
b.绑定style样式
对象写法:
:style="styleObj"
注意:styleObj里要使用小驼峰命名
对象写法(不常用):
:style="[styleObj1,styleObj2]" :style="styleArr"
1.7. 条件渲染(v-if v-else v-show)
v-show="true/false" ======底层实现就是控制display
v-if="true/false" =======为false时直接对元素进行删除操作
注:切换频率高,使用v-show,频率低,使用v-if
v-if、v-else-if、v-else配合使用,当某条语句的判断条件成立时,紧接在该语句后的其他判断语句就不执行。
这几个条件配合使用时,这几条语句中间不可以加入其他语句,否则该判断结构会被打断,会失效。
注:template标签不会破坏DOM结构,但template只能配合v-if使用
1.8. 列表渲染(v-for)
//person=[
{id:'001',name:'张三'},
{id:'002',name:'李四'},
{id:'003',name:'王五'}
]
<li v-for="(p,index) in person" :key="p.id">{{p.name}}</li>
v-for中key的原理与作用:
真实dom没有key,虚拟dom上有,用户操作是在真实dom上发生,用户操作真实dom后,虚拟dom也随之发生改变,在进行页面渲染之前,新旧虚拟dom会进行对比(diff算法),根据key进行对比,key相同的元素,数据也相同的话就直接复用,否则就重新生成。使用index作为key的值,当打乱遍历的数据顺序时(如:unshift添加数据),数据会发生错乱,所以,开发中最好使用数据的唯一标识作为key的值。
key是每个节点的唯一标识,在vue中不写不会报错,但在react中会报错。
扩展:for-in与for-of的区别:
for-in主要用于遍历对象(val,key,inedx),也可以遍历数组(val,index)
for-of(es6新特性)主要用于遍历数组,也可以遍历字符串、伪数组,一般不用于遍历对象(需内建Object.keys())
使用总结:对象遍历使用for-in,数组遍历使用for-of
列表过滤:
方法一:在watch中使用filter(需要新建一个data属性作为遍历的对象,原数据对象不能直接修改)
方法二:在computed中使用filter
注意:data中的原数据尽量不要直接修改
列表排序:
在computed中利用filter+sort实现:
1.9. Vue监测数据改变的原理:
当data中的数据发生改变时,Observer方法(观察者)会收集数据,触发该数据对应的set方法(setter),setter会重新解析模板(界面),形成新的虚拟dom树,新旧虚拟dom树进行对比,相同的的元素直接复用,不一样的元素进行生成创建,达到监听数据改变形成响应式的功能。
Vue.set的使用:
①.在向data中的 对象 追加数据时,不可以直接添加(无法实现响应式),需要使用Vue.set()。
Vue.set(需要添加数据的对象,key,value)
或者 this.$set(需要添加数据的对象,key,value)
缺陷:不能直接给data追加数据,只能给data中的对象添加
②.在Vue中,没有为数组提供getter和setter,根据索引值直接赋值修改data中的数组数据,页面数据不会响应,Vue对数组的常用方法进行了封装,所以只有调用数组方法修改数组时,响应才会生效。
1.10. 收集表单数据(form)
表单type属性:
text:文本(v-model.trim收集数据时去除空格)
password:密码
number:数值(input默认输入的是字符串,所以,可以v-model.numder)
radio:单选(多选一时,固定name属性)===>配置value值,被选中时读取value
checkbox:多选 (多选v-model要绑定数组)===>配置value值,被选中时读取value
select>option:下拉选项
textarea:多行文本输入(有时不需要打一个字就收集,可以v-model.lazy,改标签失去焦点时才收集)
单个checkbox:勾选框(直接指定v-model,无需配置value值)
1.11. 过滤器
数据 | 过滤器的名称
过滤器的本质就是一个函数,将数据作为参数传入过滤器,然后将返回结果替换整个过滤器。多个过滤器可以串联使用。过滤器不改变原数据。
过滤器一般用于插值语法{{}}和v-bind,不能用于v-model
局部过滤器:和methods、methods一样,直接在Vm下filters:{}
//局部过滤器
filters:{
过滤器名(数据,参数='设置参数默认值'){
代码片段;
return 返回结果
}
}
全局过滤器:Vue.filter('过滤器名称',function(){})
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
总结:
1.12. 内置指令(Vue提供的但不常用的指令)
复习:
v-bind: 单向绑定解析表达式, 可简写为 :xxx
v-model : 双向数据绑定
v-for : 遍历数组/对象/字符串
v-on : 绑定事件监听, 可简写为@
v-if : 条件渲染(动态控制节点是否存存在)
v-else : 条件渲染(动态控制节点是否存存在)
v-show : 条件渲染 (动态控制节点是否展示)
v-text指令:
1.作用:向其所在的标签中插入文本内容。
2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
v-html指令:
1.作用:向指定节点中渲染包含html结构的内容。
2.与插值语法的区别:
(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
(2).v-html可以识别html结构。
3.严重注意:v-html有安全性问题!!!!
(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
(XSS攻击:冒充用户攻击,盗用cookie)
(2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
扩展:cookie的工作原理:
v-cloak指令(没有值):
1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
扩展:JS阻塞:JS具有阻塞特性,当浏览器在执行js代码时,不能同时做其它事情,即<script>每次出现都会让页面等待脚本的解析和执行(不论JS是内嵌的还是外链的),JS代码执行完成后,才继续渲染页面。在引入成功之前页面不会渲染或显示未处理的内容。
v-once指令(没有值):
1.v-once所在节点在初次动态渲染后,就视为静态内容了。
2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
v-pre指令:
1.跳过其所在节点的编译过程。(vue不解析带有该指令的标签)
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
1.13. 自定义指令
自定义指令:将原生操作dom的操作进行封装
在Vue的配置对象directives中进行封装(编写时不加" v- ",使用时采用" v-*** ")。
全写方式:
**
指令名:{
//element:指令所在的元素 bingding:绑定元素的信息
bind(element,binding){
//指令与元素成功绑定时调用
},
inserted(element,binding){
//指令所在元素被插入页面时调用
},
update(element,binding){
//指令所在模板被重新解析时调用
},
}
**
**简写方式:
指令名(指令所在的元素,绑定元素的信息){
代码片段(注意:没有返回值)
}
**
注意:directives中的函数的this不指向Vm,指向window
例子:
//界面使用::<span v-big="n"></span> <input type="text" v-fbind:value="n">
directives:{
//函数的调用:自定义指令与元素成功绑定时被调用;指令所在的模板被重新解析时
big(element,binding){//绑定元素的信息的 value*10
element.innerText = binding.value * 10
} ,
/*这样写是无法实现的:需要用到inserted函数,不能使用简写方式
fbind(element,binding){//获取焦点
element.value=binding.value;
element.focus();
},
*/
fbind:{
bind(element,binding){
//指令与元素成功绑定时调用
element.value=binding.value;
},
inserted(element,binding){
//指令所在元素被插入页面时调用
element.focus();
},
update(element,binding){
//指令所在模板被重新解析时调用
element.value=binding.value;
},
}
总结:
1.14. Vue生命周期(11个钩子函数)
通常情况(8个):
beforeCreate()==[数据监测、数据代理]==>created()====>beforeMount()====>mounted()====>beforeUpdate()====>updated()====>beforeDestroy(关闭定时器、取消订阅消息、解绑自定义事件)====>destroyed()
常用的两个生命周期钩子:
①.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
②.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】
总结:
二、Vue组件化
组件定义:实现应用中局部功能的代码(html、css、js)和资源(img/mp3/ttf/zip/gif...)的集合。
组件作用:复用代码,简化项目代码,提高运行效率。
2.1. 非单文件组件
Vue中使用组件的三大步骤:定义组件(创建组件)、注册组件、使用组件(写组件标签)
1、创建组件:
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
注意:组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
区别如下:
①.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
②.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构。
2、注册组件:
1.局部注册:靠new Vue的时候传入components选项
2.全局注册:靠Vue.component('组件名',组件)
3、使用组件:
<school></school>
组件注意点:
1.关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以使用name配置项指定组件在开发者工具中呈现的名字。
2.关于组件标签:
第一种写法:<school></school>
第二种写法:<school/>
备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。
3.一个简写方式:
const school = Vue.extend(options) 可简写为:const school = options
关于VueComponent:
const school = Vue.extend({... })//定义一个组件
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
2.我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!
4.关于this指向:
(1).组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
(2).new Vue(options)配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
Vue的实例对象,以后简称vm。
注意:Vm和vc的区别:vc没有el配置项;vc的data配置项只能使用函数的返回值。
1.一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype
2.为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
补充:原型链相关知识
实例的隐式原型属性(proto)永远指向自己缔造者的原型对象!
2.2. 单文件组件
组件:实现局部功能代码和资源的集合。
单文件组件(.vue)的结构组成:
<template>
<!--组件的结构-->
</template>
<script>
//组件交互相关的代码(数据/方法等)
</script>
<style>
/*组件的样式*/
</style>
文件暴露/导出的三种方式:
1、分别暴露:export const 组件名 = Vue.extend({})
2、统一暴露:export {组件名} ---------引入时:import {***} from ****
3、默认暴露:export default 组件名---------引入时:import *** from ****
统一暴露与默认暴露的区别
App.vue 汇总所有的组件
main.js 入口文件(new Vue({}))====引入App.vue
index.html 入口文件的容器====引入main.js
三、Vue脚手架 Vue CLI(command line interface 命令行接口工具)
Vue脚手架是Vue官方提供的标准化开发工具/开发平台。
3.1.Vue脚手架
使用步骤:
①全局安装@vue/cli(仅一次使用时,记得配置淘宝镜像)
npm config set registry https://registry.npm.taobao.org
②切换到要创建项目的目录,然后使用命令行创建项目 vue create ***
③启动项目 npm run serve
注:Vue脚手架隐藏了所有webpack相关的配置,若想查看相关配置:vue inspect > output.js
可参考文章使用Vue-CLI搭建项目
项目结构解析:
总结:执行npm run serve=>main.js=>App.js=>执行其下的组价=>汇总到App.js=>找到容器所在文件index.html,将执行的内容放入容器,浏览器进行渲染。
针对main.js中的render方法进行分析:
完整版的vue:vue核心+模板解析器,其中模板解析器占1/3左右,如果使用完整版的vue,使用webpack打包后模板解析器占了很大内存且无用。为了使打包完成的项目体积小一些,脚手架中引入的vue是残缺的,没有模板解析器(无法解析template标签),就需要使用render函数来进行渲染。
render其实是一个函数:
render(createElement){
return createElement('h1','你好啊!')
}
//vue调用,且该函数中没有用到this,可以简写:
render:createElement=>createElement('h1','你好啊!')
脚手架默认配置:public、favicon.icn、index.html、src、main.js
默认配置一般是不可更改的,若要自定义个性化配置,可在项目的package.json的同级目录下创建vue.config.js文件(若修改该文件,一定要有重启项目)。
关闭语法检查的方法:在vue.config.js文件下添加 lintOnSave:false(与pages平级)
Vue脚手架总结:
3.2. ref属性
普通标签:在原生javascript中,我们获取一个html元素通常使用id属性(getElementById),但在vue中,我们使用ref属性给元素做标识,然后this.$refs.获取相应元素。
组件标签:当ref使用到组件标签上时,this.$refs.获取到得就是该组件的实例对象VueComponent。(可用于组件间通信)
3.3. props配置
①<组件标签 key1="value1" key2="value2" ...>
组件:props:["key1","key2",...],
但这样接收到的是字符串,所以,可以使用v-bind解决,②即:<组件标签 :key1="value1" :key2="value2" ...>,为了避免上述情况,接收时也可进行类型限制,即:props:{key1:String,key2:Number,...}。③也可设置默认值指定和必要性限制:props:{key1:{type:String,required:true},key2:{type:Number,default:77},...}。
注意:接收到的props是不可以更改的,若想修改,可以赋值给其他变量,然后修改。扫描优先级:props>data
e.g.
props总结:
3.4. mixin混入(混合)
mixin:多个组件使用相同的配置时使用。
mixin总结:
3.5. 插件plugin
在src文件夹下创建文件plugins.js文件,创建一个对象(插件的本质是一个对象),在该对象的install方法(一定要有install方法)中进行插件编写,记得暴露/导出,然后在main.js中引入,然后使用(Vue.use(插件名,参数列表))。
3.6. scoped样式
在Vue中,各个组件的样式最终是会汇总到一起的,在各个组件中可能会出现类名相同的情况,造成类名冲突。为了解决上述问题,可以给各个组件的样式标签style加一个作用域限制(scoped)。
注意:App组件不适用scoped
<style scoped>... </style>
总结:让样式在局部生效,防止冲突
组件间通信:
原始方法:
父子间传参
兄弟间传参:借助父组件
1、自定义事件
2、全局事件总线
3、消息订阅与发布
4、Vuex
5、路由传参
3.7. 本地存储(控制台=>Application)
1、localStorage:借助浏览器的本地存储,将数据存在硬盘上。关闭浏览器,数据不会消失。
(主动删除或者删除浏览器缓存时就会被清除)
注:localStorage解决页面更新,数据丢失问题
localStorage.setItem("key":"value");//存储,key和value都要使用字符串
localStorage.getItem("key");//读取
localStorage.removeItem("key");//移除
localStorage.clear();//清空
JSON.stringify();//数组对象转JSON字符串
JSON.parse();//
2、sessionStorage:关闭浏览器,数据消失
3.8. 组件自定义事件
子组件给父组件传递数据的方式(3种):
1、通过父组件给子组件传递函数类型的props实现:子组件通过props接收,然后通过函数使用触发将数据传递给父组件。
<School :getSchoolName="getSchoolName"/>
2、通过父组件给子组件绑定一个自定义事件实现:给谁绑的自定义事件找谁触发,使用this.$emit("事件名",参数)触发(第一种写法,使用@或v-on
<Student @atguigu="getStudentName" @demo="m1"/>
3、通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref)
<Student ref="student" @click.native="show"/>
解绑自定义事件(给谁绑的自定义事件找谁解绑):
1、this.$off('自定义事件名'):解绑一个
2、this.$off(['自定义事件名','自定义事件名']):解绑多个
3、this.$off():解绑所有自定义事件
注意:销毁当前组件的VueComponent实例,销毁后该组件上的所有自定义事件都失效。
3.9. 全局事件总线:任意组件间通信
全局事件总线:在A组件中给x绑定自定义事件demo,回调函数留在A中,B组件触发x中的事件demo,并传递参数p,A组件就可接收到B组件传递的参数p。
x应满足的条件:所有组件都可访问;可调用到$on/$off/$emit...
条件①:所有组件都可访问
一个重要的内置关系:VueComponent.prototype.prop===Vue.prototype(让组件实例对象vc可以访问到Vue原型上的属性和方法),所以,可以把X放到Vue的原型对象Vue.prototype上(在main.js文件中)。
条件②:可调用到$on/$off/$emit...
在数据监测、数据代理之前(beforeCreate)直接安装(main.js)
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
//Vue.prototype.x= this
Vue.prototype.$bus = this //安装全局事件总线
}
})
使用:
this.$bus.$on('自定义事件名',(data)=>{data:接收到的数据})
this.$bus.$emit(('事件名',传递的数据)
this.$off('事件名');解绑
注:
1、收数据的组件:在mounted中绑定事件总线中自定义事件:this.$bus.on('绑定的自定义事件',绑定时触发的函数);
传数据的组件:触发this.$bus.$emit('绑定的自定义事件',参数);
2、在组件销毁前,可以关闭该组件使用到的自定义事件;
e.s. beforDestroy(){this.$bus.$off('hello')}
3.10. 消息订阅与发布:任意组件间通信
消息订阅与发布(mounted中):
(推荐库pubsub-js)安装:npm i pubsub-js
引入:import pubsub- from ' pubsub-js'
1.订阅消息:消息名(接收数据的组件:需要/买):
this.pubId=pubsub.subscribe('消息名',(消息名msgName,数据data)=>{})
//第二个参数使用箭头函数,this才能指向VC(组件实例对象)
2.发布消息:消息内容(传递数据的组件:提供/卖)pubsub.publish('消息名',function(){ return ...})
注:组件销毁前(beforeDeatory)需要把订阅消息取消pubsub.unsubscribe(this.pubId);
消息订阅与发布原生不可实现,需要借助第三方库pubsub-js(其他库也可以)
$nextTick
语法:this.$nextTick(function(){})
作用:在下一次DOM更新结束后执行其指定的回调函数。
什么时候用:当改变数据后,要基于更新后的DOM进行某些操作,要在nextTick所指定的回调函数中执行。
3.11. 过渡与动画
1、单个元素:将产生动画效果的这个元素使用标签<transition>包裹起来,然后编写进入/离开的动画,进入.v-enter-active 离开:v-leave-active
要使元素一开始就有进入动画,给transition标签加appear属性。
当有多个transition时,可以给transition标签加name属性(name="x1")进行区分,但在写选择器时,选择器名得与之对应,进入.x1-enter-active 离开:x1-leave-active
2、多个元素使用相同的动画效果:使用<transition-group>包裹元素,注意,其中的元素要使用key属性进行区分。
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template>
<script>
export default {
name:'Test',
data() {
return {
isShow:true
}
},
}
</script>
使用动画:
<style scoped>
h1{
background-color: orange;
}
.hello-enter-active{
animation: atguigu 0.5s linear;
}
.hello-leave-active{
animation: atguigu 0.5s linear reverse;
}
@keyframes atguigu {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
使用过渡:
<style scoped>
h1{
background-color: orange;
}
/* 进入的起点、离开的终点 */
.hello-enter,.hello-leave-to{
transform: translateX(-100%);
}
/* 进入的终点、离开的起点 */
.hello-enter-to,.hello-leave{
transform: translateX(0);
}
/* 进入的过程、离开的过程 */
.hello-enter-active,.hello-leave-active{
transition: 0.5s linear;
}
</style>
推荐:动画库animate
多个元素使用同一个动画效果(这里使用了动画库):
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__swing"
leave-active-class="animate__backOutUp"
>
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
3.12. 配置代理
axios:promis风格,支持请求拦截和响应拦截,体积小
fetch:会把返回的数据包装两层promise,兼容性差
1、axios的使用:(使用广泛,推荐)
下载:npm i axios
引入:import axios from 'axios'(App.vue)
使用:axios.get('url').then(response=>{},err=>{})
解决跨域(协议名+主机名+端口号):
1、cors:后端配置特殊的响应头
2、jsonp:借助script的src属性在引入外部资源时不受同源策略限制(只能解决get产生的跨域问题)
3、代理服务器(开发中常用)
使用脚手架中的代理服务器:
方式一:
在vue.config.js配置devServer:{ proxy:'http://localhonst:5000}
缺点:只能配置一台服务器代理;无法灵活控制请求是否走代理
方式二:
devServer:{
proxy:{
'/请求前缀':{
target:'url',//要发送到的服务器
pathRewirite:{'^/请求前缀',''},//重写路径(一定要写!!)
ws:true,//用于支持websocket
changeOrigin:false//用于控制请求头中的host值
}
}
}
axios.get('http://host:端口号/前缀/路径')
注意:
1、所谓“前缀”在请求时是放在端口号之后
2、引入第三方文件时,①src-assets-...-在App.vue中引入 import '文件' ②在public中添加,然后在index.html文件中使用link引入
3、Vue的插件库vue-resource(维护频率低)
安装:npm i vue-resource
引入(main.js):import vueResource from 'vue-resource'
使用:Vue.use(vueResource)
e.g. this.\$http.get('url').then()
3.13. 插槽slot
插槽:让父组件可以向子组件指定的位置插入html结构,也是一种组件间通信的方式,适用于父子组件。[子组件挖坑等父组件来填(父组件不填时使用默认值)]
插槽/匿名插槽/具名插槽/作用域插槽/v-slot指令
四、Vuex
4.1. Vuex简介
Vuex:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
Vuex
使用场景:[共享数据]①多个组件依赖同一状态/数据 ②来自不同组件的行为需要变更同一状态
4.2. Vuex工作原理
Vuex=Actions+Mutations+State
Actions:动作、行为 Mutations:修改、加工、维护 State:状态、数据
所有的组件实例对象VueCompontent都要能使用store(import+use)
注意:vue2只能使用vuex的3版本,vue3只能使用vuex的4版本
4.3. 搭建Vuex环境
1、安装:npm i vuex@指定版本号
2、引入(main.js):import Vuex from 'vuex'
Vue.use(Vuex)
3、store
4、VueComponent都能使用store
创建文件:src=>store=>index.js
//该文件用于创建Vuex中最为核心的stroe
//引入Vuex
import Vuex from 'vuex'
//准备actions——用于响应组件中的动作(服务员)
const actions = {}
//准备mutations——用于操作数据(state)
const mutations = {}
//准备state——用于存储数据
const state = {}
//创建store
//const store = new Vuex.Store({
export default new Vuex.Store({
acrions,
mutations,
state
})
//导出/暴露文件
//export default store
然后在main.js中引入 import store from './store/index.js',在vm中使用
注意:因为store是基于Vuex的,所以store必须得Vuex生效后才能使用,但import的执行顺序总是大于Vue.use(),所以,在./store/index.js中引入Vuex,然后将Vue.use(Vuex)放到store的创建之前,记得删除main.js中Vuex的引入和使用。
总结:
1获取state中的数据:this.$store.state.数据名
2修改state中的共享数据: 1
①将共享数据放到state中;
②在组件中调用this.$store.dispatch("数据名",变量);
③在actions中编写对应的数据 数据名:function(context,value){context.commit('数据名',value)},(actions可以进行业务逻辑的操作,尤其是对各组件共同使用到的操作,需要的数据去content中找);
④在mutations中** 数据名:function(state,value){}**,mutations尽量不要进行业务逻辑的操作,也不要发送请求。
无业务逻辑处理时,可忽略③步骤,①步骤中直接调用④步骤,即this.$store.commit("数据名",变量)
4.4. getters配置项
getters配置项:用于将state中的数据进行加工
4.5. 4种map方法的使用(读取Vuex中的数据的方法)
1、this.$store.state.数据名
2、借助mapState生成计算属性,从state中读取数据(对象写法)
...mapState({计算属性名:需获取的state中的变量,...})
3、借助mapState生成计算属性,从state中读取数据(数组写法):计算属性名与需获取的state中的变量同名时
...mapState(['需获取的state中的变量',...])
同理:还有mapGetters方法、mapActions方法、mapMutations方法(加上mapState方法,总的4个,mapActions和mapMutations在调用时,记得把变化量作为参数传入的)
多组件共享数据:
4.5. Vuex模块化namespace
Vuex模块化:按功能分类集中处理
配置项处理:
注意:每一个模块一定要开启命名空间'namespace:true'
组件获取数据时:
自己写:this.$store.state.模块名.数据名 this.$store.commit('模块名/mutation名',变化量名)
this.$store.state.getters[模块名/数据名] this.$store.state.dispatch('模块名/mutation名',变化量名)
借助map**方法:...mapState('模块名',{'方法名':数据,...})/...mapState('模块名',['数据列表']),同理,mapGetters方法、mapActions方法、mapMutations方法也一样
总结:
五、路由
路由:一个路由(route)就是一组映射关系【key-value(路径-组件)】,将路径与组件配对使用,多个路由需要路由器(router)进行管理。
5.1. 路由的基本使用
1、安装vue-router:npm i vue-router
(2022.7月之后,默认版本为4,且4版本只能在vue3中使用,若是vue2项目,使用路由安装时需要指定版本3:npm i vue-router@3
)
2、在mian.js中引入使用:import VueRouter from 'vue-router'
Vue.use(VueRouter)
3、创建路由器:在src下创建router文件夹>index.js
// 创建整个项目的路由器
// 引入vue-router
import VueRouter from 'vue-router'
// 引入组件
import Login from '../components/Login'
import Index from '../components/Index'
// 创建路由器并导出
export default new VueRouter({
routes:[
{
path:'/login',
component:Login
},
{
path:'/index',
component:Index
}
]
})
4、在main.js中引入路由器:
import router from './router/index'
new Vue({
el:'#app',
render:h=>{app},
router:router
})
5、跳转:<router-link to='/**'>跳转</router-link>
出口/视图:<router-view></router-view>
注:
①标签属性active-calss:该路由被激活时的样式
②一般组件与路由组件的区别:开发中为了一般组件与路由组件,会将它们发到不同的文件夹之下进行管理,pages:路由组件
③不用的路由组件会被销毁,待使用时再挂载
components:一般组件
动态路由
5.2. 嵌套路由
注:子级路由在配置对应组件时,不需要以'/'开头;子级路由在进行路由跳转时,需要把父级带上。(<router-link to='/父级路由/子级路由'></router-link>)
5.3. 路由传参
1、跳转路由并携带query参数:
①to的字符串写法:
在进行路由跳转时:<router-link to='/...?key1=value1&key2=value2'></router-link>
接收参数的组件使用$route(路由信息):this.$route.query.key1
<router-link :to='`/...?**key1=${value1}&key2=${value2}**`'></router-link>
②to的对象写法:
<router-link :to='{
path:"/...",
query:{
key1:value1,
key2:value2
}
}
'>
</router-link>
2、跳转路由并携带params参数:
在进行路由跳转时:
<router-link to='/.../value1/value2**'></router-link>
在路由配置时,在path属性中设置占位符(指明对应数据对应的对象):
{path:"/路由/:key1/:key2",component:组件}
接收参数的组件使用$route(路由信息):
this.\$route.params.key1
5.4. 命名路由
命名路由:在配置路由时增加name属性。
在路由跳转时就不用使用path属性编写多级路由,直接使用name属性即可。
注意:路由使用to的对象写法时,路由命名和params参数不可共存。
<router-link :to='{
<!--path:"/...", 这样备注是错的,但理解这里的用意就好-->
name:"...",
query:{
key1:value1,
key2:value2
}
}
'>
</router-link>
5.5. 路由的props配置
路由的props配置:在配置路由时添加props属性
第一种写法:props值为对象。该对象中的所有key-value都会以props的形式传给使用该路由的组件,然后在对应组件中接收props。(用的非常少,死数据)
第二种写法:值为布尔值。若为true,就会把改路由组件收到的所有params参数,以props的形式传给使用该路由的组件,然后在对应组件中接收props。
第三种写法:值为函数。依靠函数的返回值,返回值(对象)以props的形式传给使用该路由的组件。
5.6. router-link的replace属性
作用:控制路由跳转时操作浏览器历史记录模式。
浏览器的历史记录(栈)有两种写入方式:push和replace,push是追加历史记录,replace是替换当前记录,默认是push.
replace模式:取代栈顶的第一条数据(路由跳转时,当前路由会替换掉上一条路由,最终结果就是页面不能千金不能后退)
使用:<router-link to='/...' replace></router-link>
5.7. 编程式路由导航
有些时候我们无法使用<router-link>(标签)来进行路由或者需要实现跳转延迟,这时就需要使用编程式路由导航(不借助<router-link>的路由导航)
//e.g.
<button @click='goRouter'>编程式路由</button>
methods:{
goRouter(){
//this.$router.push({name:'rputeName',query:{}});
//this.$router.push({name:'rputeName',query:{}});
//this.$router.go(number);
this.back();
}
}
5.8. 缓存路由keep-alive
作用:让不展示的路由组件保持挂载,不被销毁。
//<keep-alive include='componmentName'>
<keep-alive :include='[cn1,cn2...]'>
<router-view></router-view>
</keep-alive>
5.9. 两(3)个新的生命钩子函数(activated,deactivated)
由路由缓存<keep-alive>引出的,路由组件独有的两个生命钩子函数。
作用:路由组件独有,用于捕获路由组件的激活/失活状态。
activated:路由组件被激活时触发
deactivated:路由组件失活时被触发
nextTick:在下一次DOM更新结束后执行其指定的回调函数。当改变数据后,要基于更新后的DOM进行某些操作,要在nextTick所指定的回调函数中执行。
5.10. 路由守卫(很重要!!!)
分类:全局守卫、独享守卫、组件内守卫(可搭配使用)
路由守卫:对路由进行权限控制,保护路由的安全。进入某个路由时,进行需求判断,条件为真才可进入该路由,否则跳回指定的其他路由。(防止在不登录的情况下在地址栏输入路径查看页面详情的情况发生)
1、全局前置路由守卫
使用:在路由器中给每个路由添加name属性,先使用变量接收路由器,再默认导出(不可简写),在导出之前进行路由守卫的编写beforeEach函数
beforeEach函数:初始化路由时被调用+每次路由切换之前被调用
to:目标路由(去哪里)
from:来自哪里
next:放行(接着往下走)
router.beforeEach((to,from,next)=>{
//前提条件,当前去的目标路由是否是我们需要进行守卫的路由,是:进入判断,否:放行
if(to.path==='/route1' || to.name==='/route1'...){
//是需要守卫的路由,那么就进行条件判断,满足就往下走,否则就修改目标路由
//例如,判断token是否有效,无效就跳到登录页面,有效就继续
if(!token){
next('/login')
}else{
next()
}
}else{
next()
}
})
export default router
!!!!!!如果需要守卫的路由很多的话,这种判断很繁琐,可以在路由配置时加路由源信息meta来控制是否需要守卫:
// 引入vue-router
import VueRouter from 'vue-router'
const router = new VueRputer({
routes:[
{
name:'**',
path:'/**',
meta:{isAuth:true},//需要守卫的路由为true
compontent:()=>import('/**')//按需引入路由组件,有效防止首屏加载慢或白屏问题
}
]
})
router.beforeEach((to,from,next)=>{
//前提条件:使用配置项进行判断是否路由守卫
if(to.meta.isAuth){
//是需要守卫的路由,那么就进行条件判断,满足就往下走,否则就修改目标路由
//例如,判断token是否有效,无效就跳到登录页面,有效就继续
if(!token){
next('/login')
}else{
next()
}
}else{
next()
}
})
export default router
2、全局后置路由守卫
后置路由守卫没有next参数,一般用于路由跳转后修改页面的标题title。
用法:在路由源信息meta中增加title属性,在路由跳转是进行修改:
// 引入vue-router
import VueRouter from 'vue-router'
const router = new VueRputer({
routes:[
{
name:'**',
path:'/**',
meta:{isAuth:true,title:'****'},
compontent:()=>import('/**')
}
]
})
router.beforeEach((to,fromt)=>{
if(to.meta.isAuth){
if(!token){
next('/login')
}else{
next()
}
}else{
next()
}
})
//使用后置路由守卫修改页面标题
router.afterEach((to,from)=>{
ducument.title = to.meta.title || '默认值'
})
export default router
3、独享路由守卫
独享路由守卫:只给某一个路由设置的守卫。只有前置没有后置!
{
name:'**',
path:'/**',
meta:{
isAuth:true,
title:'****',
beforeEnter:(to,from,next)=>{
if(to.meta.isAuth){
if(!token){
next('/login')
}else{
next()
}
}else{
next()
}
}
},
compontent:()=>import('/**')
}
4、组件内路由守卫
组件内路由守卫:直接在组件内编写beforeRouterEnter和afterRouterLeave(和钩子函数同级)
//beforeRouterEnter:通过路由规则,进入该组件时被调用
beforeRouterEnter(to,from,next){}
//afterRouterLeave:通过路由规则,离开该组件时被调用
afterRouterLeave(to,from,next){}
5.11. history模式和hash模式(仅从前端角度分析)
路由器的两种工作模式:history模式和hash模式
hash模式下,路径中的值称为哈希值,它不会随着http请求发给服务器。
hash模式(默认模式):地址栏带#号。兼容性好。
history模式:无#号。兼容性略差。
项目打包(npm run bind)前可将工作模式改为hash模式(哈希模式在部署发送网络请求时不会因地址栏参数产生404错误):更改工作模式:在路由配置项中 mode:'history或者hash'
若使用history模式,404报错的解决方法(使用node的中间件):
npm i connect-history-api-fallback
=>在server.js中引入:const history = require('connect-history-api-fallback')
=>使用(在使用静态资源之前):app.use(history)
扩展:使用node express搭建一个微型服务器
新建文件夹demo=>vscod打开=>安装express: 【
npm init
=>取名:test-server,一路回车=>npm i express
】=>创建server.js文件:=>服务器下一般有static(public)文件夹用来存放前端的文件(服务器要识别这些文件就需要借助中间件app.use来指定静态资源(express.static(__dirname+'static')))=>停掉重启即可在浏览器查看前端文件将前端文件放入服务器静态资源文件夹下的过程就称之为部署。
//server.js
//引入express
const express = require('express')
//引入connect-history-api-fallback解决跨域
const history = require('connect-history-api-fallback')
app.use(history)
//创建服务对象
const app = express()
//借助中间件app.use来指定静态资源
express.static(__dirname+'static'))
//配置路由
app.get('/person',(request,response)=>{
//该路由给前端返回的信息
request.send({name:'jaja',age:18})
})
//监听端口
app.listen(50005,(err)=>{
if(!err) {
//服务器启动成功
}
})
5.12. Vue UI组件库
移动端常用:
vant:https://youzan.github.io/vant
Cube UI:https://didi.github.io/cube-ui
Mint UI:https://mint-ui.github.io
PC端常用:
Element UI:https://element.eleme.cn
IView UI:https://www.iviewui.com
使用(以Element UI为例):
打开官网=>组件(按着官网来就好了)
npm i element-ui=>在main.js中引入使用(import和use)=>引入样式(最好按需引入)=>使用:<el-**>
按需引入:借助库babel-plugin-component:
安装:npm i babel-plugin-component -D(-D开发依赖)=>在babel.config.js下追加配置=>在main.js下引入需要的组件
六、Vue3
嗷嗷嗷,终于到vue3了!!!!!
Vue3快速上手
注:绝大部分截图来自尚硅谷张天禹老师教学视频