Vue学习笔记(一)--基础概念、基本指令
基础概念
- vue特性
- 数据的变化会驱动视图自动更新
- 好处:程序员只管把数据维护好,那么页面结构会被vue自动渲染出来!
- 注意:数据驱动视图是。
- 在网页中,form表单负责采集数据,Ajax负责提交数据。
- 好处:开发者不再需要手动操作DOM元素,来获取表单元素最新的值!

- MVVM
- MVVM是vue实现数据驱动视图和双向数据绑定的核心原理。MVVM指的是Model、View和ViewModel
- Model表示当前页面渲染时所依赖的数据源。
- View表示当前页面所渲染的DOM结构。
- ViewModel表示vue的实例,它是MVVM的核心。
- vue的基本使用
- 导入vue.js 的script脚本文件
- 在页面中声明一个将要被vue所控制的DOM区域
- 创建vm 实例对象(vue 实例对象)
常用指令
- 内容渲染指令
v-text:会覆盖元素原有内容v-html:可以把带标签的字符串,渲染真正的html内容,会有XSS风险,会覆盖子组件: 插值表达式,内容占位符 (只能用在元素的内容节点,不能用于属性节点)- 在插值语法和属性绑定里,可以使用JS表达式js
{{number + 1}} {{ok ? "YES" :"NO"}} {{message.split("").reverse().join(' ')}}
- 属性绑定指令
v-bind:属性名为元素的属性动态绑定值(可以简写成:)js//在使用v-bind属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,例如; <div :title="'box' + index">这是一个div</div>
- 事件绑定指令
v-on:事件名称="事件处理函数(定义在methods中)",可以简写成@- 事件修饰符: 示例:
@click.prevent=''修饰符可以连续写js.prevent //阻止默认行为(例如:阻止a连接的跳转、阻止表单的提交等) .stop //阻止事件冒泡 .capture//以捕获模式触发当前的事件处理函数 .once //绑定的事件只触发1次 .self //只有在event.target是当前元素自身时触发事件处理函数 .passive://事件的默认行为立即执行,无需等待事件回调执行完毕: - 按键修饰符: 示例:
@keyup.enter=''jsdelete //(“删除”和“退格”键) tab//(特殊,必须配合keydown去使用) //系统修饰健(用法特殊):ctrl、alt、 shift、 meta @click.ctrl.exact //只有ctrl被按下的时候触发 @click.exact //没有任何系统按键按下的收触发 (1).配合keyup使用:按下修饰健的同时,再按其他健,随后释放,事件才触发 (2).配合keydown使用:正常触发事件。
- 双向绑定指令(表单元素使用才有意义)
v-model=''用来辅助开发者在不操作DOM的前提下,快速获取表单的数据。
<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。<input type="checkbox"/>- 没有配置input的value属性,那么收集的就是checked(是布尔值)
- 配置input的value属性:
- v-model的初始值是非数组,那么收集的就是checked(是布尔值)
- v-model的初始值是数组,那么收集的的就是value组成的数组
- v-model的三个修饰符:js
.lazy://失去焦点再收集数据 .number://输入字符串转为有效的数字 .trim://输入首尾空格过滤 - 自定义v-modeljs
//父组件中 <HelloWorld v-model="showFlag" v-if="showFlag"></HelloWorld> //子组件中 <button @click="$emit('change', false)">关闭组件</button> export default { name: 'HelloWorld', model: { prop: 'checked', //对应props:中的checked event: 'change' //对应触发事件 }, props: { checked: { type: Boolean } }, }
- 条件渲染指令(按需控制DOM的显示与隐藏)v-if / v-show 的区别
- 实现原理不同:
- v-if 指令会动态地创建或移除DOM元素
- v-show指令会动态为元素添加或移除
style=" display: none;"样式
- 性能消耗不同:
- v-if有更高的切换开销,而v-show有更高的初始渲染开销。因此:
- 如果需要非常频繁地切换,则使用v-show较好
- 如果在运行时条件很少改变,则使用v-if 较好
- v-if、v-else-if、v-else不需要加条件判断,三者可以配合使用
- 实现原理不同:
- 列表渲染指令 (基于一个数组来循环渲染一个列表结构)js
v-for="(item,index)in xxx" :key="item.id" //遍历数组 v-for="(value,key,index) in xxx" :key="key"//遍历对象- 加key的作用:
- 正确维护列表的状态
- 复用现有的DOM元素,提升渲染的性能
- key的值只能是字符串或数字类型
- key的值必须具有唯一性(即:key的值不能重复)
- 建议把数据项id属性的值作为key的值(因为id属性的值具有唯一性)
- 使用index的值当作key的值没有任何意义(因为index的值不具有唯一性)
- 建议使用v-for指令时要指定key的值(既提升性能、又防止列表状态紊乱)
- 虚拟DOM中key的作用:
- key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟Ddl,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
- 对比规则:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没变,直接使用之前的真实DOM!
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DON.
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 创建新的真实DOM,随后渲染到到页面。
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
- 会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。
- 如果结构中还包含输入类的DOM:
- 会产生错误DOM更新==〉界面有问题。
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
- 开发中如何选择key? :
- 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
- 虚拟DOM中key的作用:
- 加key的作用:
- 其他指令
v-cloak指令(没有值)- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
- 使用css配合v-cloak可以解决网速慢时页面展示出
的页面闪烁问题。
v-once指令(没有值)v-once所在节点在初次动态渲染后,就视为静态内容了。- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
v-pre指令(没有值)- 跳过其所在节点的编译过程。
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点会加快编译。
- 自定义指令
- 定义语法:
- 局部指令: 一个自定义指令由一个包含类似组件生命周期钩子的对象来定义
jsdirectives: { focus: { //vue2 //bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 //inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 //update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。 //componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。 //unbind:只调用一次,指令与元素解绑时调用。 inserted: function (el,binding) { el.focus() } //binding是一个多属性对象 name value oldValue(仅在update和componentUpdated可用) expression arg modifiers //vue3 提供的钩子函数就是自身的钩子函数 } },- 全局指令:
jsapp.directive('focus', { //app.directive('',{}) 指令名,配置对象 mounted(el) { //挂载在实例上,我们可以在对应的生命周期执行操作 console.log(el, app,'mounted'); el.focus(); } }); - 配置对象中常用的3个回调:
.bind:指令与元素成功绑定时调用。(bind函数只调用1次).inserted:指令所在元素被插入页面时调用。.update:指令所在模板结构被重新解析时调用。
- 备注:
- 指令定义时不加
v-,但使用时要加v-; - 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
- 回调中第一个形参el表示当前指令所绑定到的那个真实DOM对象,形参中的第二个参数binding,来接收参数
- 如果bind和update函数中的逻辑完全相同,则对象格式的指令可以写成函数格式:
- 指令定义时不加
- 定义语法:
注意事项
- el属性是固定的写法,表示当前vm 实例要控制页面上的哪个区域
- data对象就是要渲染到页面上的数据
- methods的作用,就是定义事件的处理函数
+ methods中的方法可以写成add:function( ){ },也可以简写成add( ){ },可以传参 - vue提供了内置变量,名字叫做
$event,它就是原生DOM的事件对象e,如果传参的话,注意写成@click='add(n,$event)',如果不传参直接在methods中用e即可 - 给checkbox绑定v-model时可以用提升用户体验,但是要注意这里要用,不然只有第一行的checkbox生效
Vue监视数据的原理
- 用户data数据-->
_data数据进行代理,并且给每一项数据进行包装,设置getter、setter-->数据在vm实例上可以直接找到-->当数据发生变化-->对应setter调用(数据劫持)-->形成虚拟DOM-->新旧DOM比对复用-->更新页面
- vue会监视data中所有层次的数据。
- 如何监测对象中的数据?
- 通过setter实现监视,且要在new Vue时就传入要监测的数据。
- 如需给后添加的属性做响应式,请使用如下API:
jsVue.set(targe,propertyName/index,value) vm.$set(target,propertyName/index,value)
- 通过setter实现监视,且要在new Vue时就传入要监测的数据。
- 如何监测数组中的数据?
- 通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新。
- 重新解析模板,进而更新页面。
- 通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 在Vue修改数组中的某个元素一定要用如下方法: js
使用这些API:push()、pop()、 shift()、unshift()、splice()、sort()、reverse() Vue.set()或vm.$set()
过滤器
- 基本使用
- 过滤器(Filters)是vue为开发者提供的功能,常用于文本的格式化。过滤器可以用在两个地方:插值表达式和v-bind属性绑定。(返回过滤器函数的新值)
- 过滤器应该被添加在JavaScript表达式的尾部,由管道符
|进行调用
js//在双花括号中通过"管道符"调用capitalize过滤器,对message的值进行格式化 <p>{{ message | capitalize }}</p> - 过滤器的注意点:
- 要定义到filters节点下,本质是—个函数
- 在过滤器函数中,一定要有return值
- 在过滤器的形参val中,就可以获取到"管道符"前面待处理的那个值
- 如果全局过滤器和私有过滤器名字一致,按照"就近原则”,调用的是"私有过滤器”
- 私有过滤器:它只能在当前vm 实例所控制的el区域内使用。(filters节点下定义的)
- 全局过滤器:多个vue实例之间共享过滤器js
// Vue.filter()方法接收两个参数: //第1个参数,是全局过滤器的"名字" //第2个参数,是全局过滤器的"处理函数" Vue.filter('capitalize',(str) =>{ return str.charAt(0).toUpperCase() + str.slice(1) + '~~'}) - 连续调用多个过滤器js
//把 message的值,交给filterA 进行处理 //把 filterA处理的结果,再交给filterB进行处理 //最终把 filterB处理的结果,作为最终的值渲染到页面上 {{ message | filterA | filterB }} - 过滤器传参js
// arg1 和arg2是传递给filterA 的参数 <p>i{ message l filterA( arg1, arg2) }</p> //过滤器处理函数的形参列表中: //第一个参数:永远都是"管道符“前面待处理的值 //从第二个参数开始,才是调用过滤器时传递过来的arg1 和 arg2参数 Vue.filter('filterA',(msg,arg1,arg2) => { //过滤器的代码逻辑... }) - Dayjs的使用js
//直接调用dayjs()得到的是当前时间ll dayjs(给定的日期时间)得到指定的日期 const dtstr = dayjs(time) . format('YYYY-MM-DD HH: mm: ss') return dtstr
侦听器
- 可以通过new实例时用watch配置,也可以用
Vue.$watch('',{对象配置}) - 侦听器的格式
- 方法格式的侦听器js
watch: { name(newVaule,oldValue){ }//不能使用immediate和deep } - 对象格式的侦听器js
watch: { name:{ handler(newVaule,oldValue){ } } }- 好处1:可以通过
immediate:true选项,初始化时调用一次handler - 好处2:可以通过
deep:true选项,让侦听器深度监听对象中每个属性的变化! ! !
js//如果要侦听的是对象的子属性的变化,则必须包裹一层单引号 'info.username'(){}- watch可以开启一些异步任务,里面的定时器
- 好处1:可以通过
- 方法格式的侦听器
计算属性
- 计算属性指的是通过一系列运算之后,最终得到一个属性值。这个动态计算出来的属性值可以被模板结构或 methods方法使用。
- 原理:底层借助了
objcet.defineproperty方法提供的getter和setter. - 所有的计算属性,都要定义到
computed节点之下计算属性在定义的时候,要定义成方法格式 - 特点:
- 虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性
- 计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行运算js
computed:{ a(){ return this.num * 2 } b(){ get(){ return this.num * 2 //这种写法如果绑定给v-model 那么get必须和set搭配使用 } set(val){ this.num = val / 2 } } }
axios的基本使用
- 调用axios 方法得到的返回值是 Promise 对象
- axios在请求到数据之后,把真正的数据包装成了一个大对象
js
axios({
methods:'GET',
url:'http://liulongbin.top:3006/api/getbooks',
params:{},//url中的查询参数
data:{}, //post请求的参数
}).then(function(result){})
{config,data,headers,request,status,statusText:}- 如果调用某个方法的返回值是
Promise实例,则前面可以添加await! - await只能用在被async“修饰”的方法中
- 利用解构赋值的思想直接获取js
document.querySelector('#btnGet').addEventListener('click', async function () { //解构赋值的时候,使用:进行重命名 const {data: res} = await axios({ method:'GET', url: 'http://www.liulongbin.top:3006/api/getbooks' }) console.log(res.data) }) axios.get()与axios.post()jsconst { data: res } = await axios.get('http://www.liulongbin.top:3006/api/getbooks',{ params: { id: 1 } }) console.log(res) // axios.post( 'url', { /* POST请求体数据*/ }) const { data: res } = await axios. post('http://w.liulongbin.top:3006/api/post',{ name: 'zs', gender: '女' }) console.log(res)- axios挂载在原型上使用
- 在main.js中
axios.defaults.baseURL ='请求根路径'全局配置axios请求根路径 - 在main.js中
Vue.prototype.$http = axios - 今后,在每个.vue 组件中要发起请求,直接调用
this.$http.xxx - 缺点:不利用API接口的复用
- 在main.js中
class和style写法
- class和style写法js
<p :class="{black : isBlack, yellow: isYellow}"></p> <p :class="[black,yellow]"></p> <p :style="styleData"></p> styleData:{ fontSize:'40px', color:'red', }
Vue学习笔记(二)--生命周期、组件化开发
生命周期
- 生命周期(Life Cycle)是指一个组件从创建→挂载→运行→销毁的整个阶段,强调的是一个时间段。
- 注意:生命周期强调的是时间段,生命周期函数强调的是时间点。

- 组件创建阶段
beforeCreate: 组件的props/data/methods尚未被创建,都处于created: 组件的porps/data/methods已创建好,都处于 。但是组件的 这个阶段可以用来发起Ajax请求,获取数据- 基于数据和模板,在内存中编译生成HTML结构(虚拟DOM)
beforeMount: 呈现的是未经Vue编译的DOM结构,所有对DOM的操作不奏效- 将内存中的虚拟DOM转为真实DOM插入页面
mounted: 页面中呈现的是经过Vue编译的DOM,对DOM的操作均有效(尽可能避免),至此初始化过程结束,一般在此进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件、等初始化操作。
- 组件运行阶段(当数据改变时)
beforeUpdate:此时:,即:页面尚未和数据保持同步。- 根据新数据,生成新的虚拟DOM,新旧虚拟DOM进行比较,最终完成页面更新,即:完成了Model View的更新
updated:此时:,即:页面和数据保持同步。(新DOM)
- 组件销毁阶段
beforeDestroy: vm中所有的: data、methods、指令等等,都处于可用状态,但是,马上要执行销毁过程,一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作destroy: 组件已经被销毁,此组件在浏览器中对应的DOM结构已被完全移除!
Vue-cli(脚手架)的使用
- vue项目中src目录的构成:
- assets文件夹:存放项目中用到的静态资源文件,例如: css样式表、图片资源
- components文件夹:程序员封装的、可复用的组件,都要放到components目录下
- main.js 是项目的入口文件。整个项目的运行,要先执行main.js
- App .vue是项目的根组件。
- vue项目的运行流程
- 在工程化的项目中,vue通过main.js把App.vue渲染到index.html的指定区域中!
- App.vue用来编写待渲染的模板结构
- index.html中需要预留一个el区域
- main.js把 App.vue渲染到了index.html所预留的区域中
- 在工程化的项目中,vue通过main.js把App.vue渲染到index.html的指定区域中!
- 组件和组件工程化
- 组件就是用来实现局部(特定)功能效果的代码集合
- 组件化开发指的是:根据封装的思想,把页面上可重用的UI结构封装为组件,从而方便项目的开发和维护。
- 注意事项js
const school = Vue.extend(options) 可简写为: const school = options //一个重要的内置关系: VueComponent.prototype.__proto__=== Vue.prototype - mixin混入
- 功能:可以一些公共逻辑抽离出来
data数据、methods、生命周期等等,其他组件可以直接混入合并自身使用 - 使用方式:
- 第一步在mixin.js文件中定义混合,例如:
js{ data(){...},methods:{....} }- 第二步在要使用的组件中引入,例如:
jsVue.mixin(xxx) //全局混入 import myMixin from '../..mixin' mixins:[myMixin] //局部混入 可以使用mixin中的data、methods、生命周期也会合并执行 - 缺陷:变量来源不明确,不利于阅读;watch和生命周期优先直执行mixin中的,其他的默认选择原组件的同名属性
- 功能:可以一些公共逻辑抽离出来
- 插件
- 功能:用于增强Vue
- 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
- 定义插件:js
对象.install(Vue,options){ Vue.filter(....) // 1,添加全局过滤器 Vue.directive(....) //2.添加全局指令 Vue.mixin(....) //3.配置全局混入(合) Vue.prototype.$myMethod = function () {...} //4 . 添加实例方法 Vue.prototype.$myProperty = xxXxX } Vue.use() //使用插件
组件使用!!!
- 私有子组件和全局组件
- 通过components注册的是私有子组件
- 在vue项目的main.js入口文件中,通过Vue.component()方法,可以注册全局组件js
//导入需要全局注册的组件 import Count from '@/components/Count.vue ' //参数1。字符串格式,表示组件的"注册名称"" //参数2。需要被全局注册的那个组件 Vue.component('MyCount',Count)
- 组件里的props(自定义属性)js
props:['name'] // 第一种方式(只接收) props:{name:{type:'',required: ,default: }} //第三种方式: (限制类型、限制必要性、指定默认值) > props是只读的,Vue底层会监测你对props的修改 >需要修改,可以把 props的值转存到data中 - 组件中的样式冲突问题
- 导致组件之间样式冲突的根本原因是:单页面应用程序中,多个组件的样式都是 呈现的
- style节点的scoped属性:让样式在局部生效,防止冲突。
- /deep/样式穿透
- 如果给当前组件的style节点添加了scoped属性,则的。如果想让某些样式对子组件生效,可以。
js/deep/ .title { color: blue; //加上 /deep/时,生成的选择器格式为[data-v-052242de] .title }
- 组件之间的数据共享
- js
<Left :mSg="message" :user="userinfo"></Left> - 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定父组件事件(事件的回调在A中)
- 利用hack,父组件给子组件穿一个函数形式的props属性,在子组件触发传值
- 在父组件中给子组件绑定一个自定义事件(实际是绑在了子组件的vc实例上),然后子组件在合适的时候利用this.$emit触发此事件,并把参数传给父组件js
//父组件中 <child @myEvent = 'getStudentName'/> //绑定事件 methods:{ getStudentName(){ //this指向当前组件 } } //子组件中 this.$emit('myEvent',数据) //因为事件时绑定在子组件的实例对象上的,所以可以直接通过this进行触发 - 在父组件中给子组件加ref,然后通过ref拿到子组件实例,并且可以再mounted时用$on给其注册自定义事件,并且穿一个回调函数来处理收到的参数,后续步骤同②,这样的好处是注册自定义事件的时候可以异步操作.
- js
//父组件中 <Student ref='student'/> mounted(){ //使用 $on 为组件实例对象绑定自定义事件myEvent //1.在methods中定义回调函数 this.$refs.student.$on('myEvent', this.getStudentName) //2.箭头函数写法 this.$refs.student.$on('myEvent', ()={ //this指向当前组件 }) //3.直接定义回调的写法 this.$refs.student.$on('myEvent', function(){ //this指向触发该自定义事件的子组件 }) } methods:{ getStudentName(){ //this指向当前组件 } } //子组件中 this.$emit('myEvent',数据) //触发
使用event注册自定义事件
jsevent.$on('onAdd', this.handler) //callback最好使用函数名字,方便解绑 event.$emit('onAdd', 参数数据)解绑自定义事件:
jsevent.$off('atguigu') //解绑一个自定义事件 不及时解绑会造成内存泄漏 event.$off(['',''])//利用数组解绑多个自定义事件 event.$off()//解绑所有自定义事件销毁组件实例
jsthis.$destroy() //销毁当前组件实例,原生事件可以触发(但不会有响应式),自定义事件全都会失效组件上可以绑定原生事件,需要native修饰符。
<Student @click.native='getStudentName'/>,不然认作自定义事件
- js
//1.安装全局事件总线,$bus就是当前应用的vm new vue({ ... beforeCreate(){ Vue.prototype.$bus = this }, ... }) //2.接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。 methods(){ demo(data){......} } mounted() { this.$bus.$on('xxxx',this.demo) } //3.提供数据: this.$bus.$emit('xxxx',数据) //4.最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件 - js
npm i pubsub-js //安装pubsub import pubsub from 'pubsub-js' //接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。 methods(){ demo(data){......} } mounted() { this.pid = pubsub.subscribe('xxx',this.demo) ////订阅消息 } pubsub.publish('xxx',数据) //提供数据: PubSub.unsubscribe(pid) //最好在beforeDestroy钩子中,去取消订阅。 - 依赖注入:
inject provide- 父组件可以直接向子孙组件传值js
//父组件 export default { provide(){ return{ val:'这是home的依赖' //依赖 } } } //孙组件 export default { inject:['val'] //注入 } - 开发组件库可以使用,其他场景不建议使用,导致数据来源不明确
- 父组件可以直接向子孙组件传值
- $refs引用
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在htm1标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
- 使用方式:js
<h1 ref="xxx">.....</h1>或<School ref="xxx"></School> //打标识 this.$refs.xxx //获取
inputVisible:控制输入框和按钮的按需切换; 默认值为 false,表示默认展示按钮,隐藏输入框this.$nextTick(cb)方法- Vue是异步渲染,data改变之后,DOM不会立即渲染
- 页面渲染会将data的修改做整合,多次相同的data修改只会渲染一次
$nextTick会在DOM渲染之后再去触发,以获取最新的DOM节点
动态组件
- 动态组件指的是
- vue提供了一个内置的
<component>组件,html<!--2.通过is 属性,动态指定要渲染的组件--> <component :is="comName"></component> <!--配置楼层的时候comName可以由内管配置好,然后后端返回,前端进行遍历即可--> <div v-for='(item,index) in list' :key='index'> <component :is="item.T_CODE"></component> <!--配置楼层的时候list可以由内管配置好,然后后端返回,前端进行遍历即可--> </div> <!--必须是动态的:is 不能是is+确切值--> - 动态组件切换时默认会重新创建新组件
- 组件名称默认使用注册名,可以提供一个name属性给组件来使用
- 注册名称是在使用标签时使用
- name名称在调试工具和添加属性时使用
异步组件
- 如何使用js
<FormDemo v-if='showFormDemo'></FormDemo> //异步组件 export default{ components:{ FormDemo:() => import('xxx') //异步引入 } mounted(){ // 在mounted中将showFormDemo置为true,或者在某个操作之后将showFormDemo置为true } } - 可用于首屏优化
缓存组件
- 需要频繁切换(tab切换),不需要重新渲染,Vue常见的性能优化之一
- 可以使用vue内置的
<keep-alive>组件html <keep-alive> <component :is="comName"></component> </keep-alive> - keep-alive对应的生命周期函数
- 当组件被时,会自动触发组件的
deactivated生命周期函数。 - 当组件被时,会自动触发组件的
activated生命周期函数
- 当组件被时,会自动触发组件的
- keep-alive的include属性
include属性用来指定:只有名称匹配的组件会被缓存。多个组件之间用逗号分隔:exclude属性指定:对应组件不会被缓存,两个属性只能用一个html<keep-alive include="Left,Right" exclude=""> <component ;is="comName"></component> </keep-alive>
插槽
- 插槽的基本使用
- 插槽(Slot) 父组件引用子组件时,可以往子组件内部插入一部分html结构,在子组件要插入的部分用
<slot>占位,这就是插槽 - 每一个slot插槽,都要有一个name名称,如果省略则默认名称叫做
default - 要渲染到对应插槽内,要用
v-slot:name,并用template包裹(无意义标签) slot-scope已废弃 v-slot:的简写形式是#
- 插槽(Slot) 父组件引用子组件时,可以往子组件内部插入一部分html结构,在子组件要插入的部分用
- 具名插槽html
<!--父组件--> <NamedSlot> <p slot='header'>将插入 header slot中</p><!--老式写法弃用--> <template #header> <!--除非只有默认插槽,否则要加在template上--> <p>将插入 header slot中</p> </template> <p>将插入到main slot中,即未命名的slot</p> <template v-slot:footer> <p>将插入 footer slot中</p> </template> </NameSlot> <!--NamedSlot组件--> <div class='container'> <header> <slot name='header'></slot> </header> <slot></slot> </header> <footer> <slot name='footer'></slot> </footer> </div> - 作用域插槽
- 在封装组件的过程中,可以为预留的
<slot>插槽绑定props数据,这种带有props数据的<slot>叫做“作用域插槽”。html<!--父组件--> <ScopedSlot :url='website.url'> <!--父组件给子组件传值--> <template v-slot = 'slotProps'> <!--接收子组件的传值 slotProps自定义--> {{slotProps.slotData.title}} <!--使用子组件的传值--> </template> </ScopedSlot> <!--作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程 --> <template v-slot = '{slotData}'> {{slotData.title}} </template> <!--子组件--> <slot :slotData = 'website'> <!--将子组件的website传给父组件使用--> {{website.subTitle}} <!--父组件内部不定义的时候,默认展示自身的title--> </slot>
- 在封装组件的过程中,可以为预留的
- 组合使用html
<ScopedSlot> <template #slotData = '{test1,test2}'> {{test1.xxx}} </template> </ScopedSlot> <template v-slot:slotData = '{test1,test2}'> {{test2.xxx}} </template> <!--子组件--> <slot name='slotData' :slotData = '{test1,test2}'> {{website.subTitle}} </slot> - 动态插槽名html
<template v-slot:[dyname]> This is Me. </template>
Vue封装的过度与动画
- 作用:在插入、更新或移除DOM元素时|在合适的时候给元素添加样式类名,

- 使用
<transition>包裹要过度的元素,并配置name属性:html<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition> - 备注:若有多个元素需要过度,则需要使用:
<transition-group>,且每个元素都要指定key值
VueX(状态管理模式)
- 了解vuex
- 什么时候使用vuex(中大型项目中)
- 多个视图依赖同一个状态
- 来自不同视图的行为需要变更同一个状态
- 把各个组件都需要依赖的同一个状态抽取出来,在全局使用单例模式进行管理。在这种模式下,任何组件都可以直接访问到这个状态,或者当状态发生改变时,所有的组件都获得更新。
- 什么时候使用vuex(中大型项目中)
- vuex基本使用
npm install vuex --save- 创建仓库文件并引入使用插件:
src/store/index.jsjsimport Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const store = new Vuex.Store({ state: { // 定义一个name,以供全局使用 name: '张三', // 定义一个number,以供全局使用 number: 0, // 定义一个list,以供全局使用 list: [ { id: 1, name: '111' }, { id: 2, name: '222' }, { id: 3, name: '333' }, ], }, getters: { getMessage(state) { // 获取修饰后的name,第一个参数state为必要参数,必须写在形参上 return `hello${state.name}`; }, }, mutations: { // 增加mutations属性 setNumber(state) { // 增加一个mutations的方法,方法的作用是让num从0变成5,state是必须默认参数 state.number = 5; }, setNumberIsWhat(state, number) { // 增加一个带参数的mutations方法 state.number = number; }, }, }); export default store; - 修改
main.jsjsimport Vue from 'vue'; import App from './App'; import router from './router'; import store from './store'; // 引入我们前面导出的store对象 Vue.config.productionTip = false; new Vue({ el: '#app', router, store, // 把store对象添加到vue实例上 components: { App }, template: '<App/>', }); - 使用vuex数据js
export default { mounted() { console.log(this.getName); console.log(this.$store.getters.getMessage); //组件中使用getters属性 this.$store.commit('setNumber'); //同步修改 this.$store.commit('setNumberIsWhat', { number: 666 }); // 调用的时候也需要传递一个对象 this.setNumberIsWhat({ number: 999 });//使用解构的mutations }, computed: { //官方建议1:操作this.$store.state.XXX最好放在计算属性中 getName() { return this.$store.state.name; }, //官方建议2: 是不是每次都写this.$store.state.XXX让你感到厌烦,解决方案 ...mapState(['name']), // 经过解构后,自动就添加到了计算属性中,此时就可以直接像访问计算属性一样访问它 ...mapState({ aliasName: 'name' }), // 赋别名的话,这里接收对象,而不是数组 ...mapGetters(['getMessage']), ...mapGetters({ aliasName: 'getMessage' }), // 赋别名的话,这里接收对象,而不是数组 }, methods: { // 注意,mapMutations是解构到methods里面的,而不是计算属性了 ...mapMutations(['setNumberIsWhat']), ...mapMutations({ setNumberIsAlias: 'setNumberIsWhat' }), // 赋别名的话,这里接收对象,而不是数组 }, };
- vuex异步操作
- storejs
const store = new Vuex.Store({ state: { name: '张三', number: 0, }, mutations: { setNumberIsWhat(state, payload) { state.number = payload.number; }, }, actions: { // 增加actions属性 setNum(content,payload) { // 增加setNum方法,默认第一个参数是content,其值是复制的一份store return new Promise(resolve => { // 我们模拟一个异步操作,1秒后修改number为888 setTimeout(() => { content.commit('setNumberIsWhat', { number: payload.number }); resolve(); }, 1000); }); }, setNum({ commit }) { // 直接将content结构掉,解构出commit,下面就可以直接调用了 return new Promise(resolve => { setTimeout(() => { commit('XXXX'); // 直接调用 resolve(); }, 1000); }); }, }, }); - 异步操作js
async mounted() { console.log(`旧值:${this.$store.state.number}`); await this.$store.dispatch('setNum',{ number: 611 }); console.log(`新值:${this.$store.state.number}`); await this.setNum({ number: 123 }); // 直接这样调用即可 }, methods: { ...mapActions(['setNum']), // 就像这样,解构到methods中 ...mapActions({ setNumAlias: 'setNum' }), // 赋别名的话,这里接收对象,而不是数组 },
- store
- vuex拆分
- 按属性进行拆分
- 新建四个文件,分别是
state.js getters.js mutations.js actions.js - 组装到主文件里面js
import Vue from 'vue'; import Vuex from 'vuex'; import { state } from './state'; // 引入 state import { getters } from './getters'; // 引入 getters import { mutations } from './mutations'; // 引入 mutations import { actions } from './actions'; // 引入 actions Vue.use(Vuex); const store = new Vuex.Store({ state: state, getters: getters, mutations: mutations, actions: actions, }); // 可以简写成下面这样: // const store = new Vuex.Store({ state, getters, mutations, actions }); export default store;
- 新建四个文件,分别是
- 按功能进行拆分 - Modulejs
const countAbout = { namespaced:true,//开启命名空间 state:{x:1}, mutations: {...}, actions: {...}, getters: { bigSum(state){ return state.sum * 10} }} // 3.开启命名空间后,组件中读取state数据: //方式一。自己直接读取 this.$store.state.personAbout.list //方式二。借助mapState读取: ...mapstate('countAbout' ,["sum",'school','subject']), // 4.开启命名空间后,组件中读取getters数据: //方式一。自己直接读取 this.$store.getters['personAbout/firstPersonName'] //方式二。借助mapGetters读取: ...mapGetters('countAbout',['bigSum']) //5.开启命名空间后,组件中调用dispatch //方式一。自己直接dispatch this.$store.dispatch("personAbout/addPersonwang",person) //方式二:借助mapActions: ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementwait:'jiawait'}) //6.开启命名空间后,组件中调用commit //方式一。自己直接commit this.$store.commit("personAbout/ADD_PERSON",person) //方式二。借助mapMutations: ...mapMutations('countAbout',{increment:'3IA',decrement:'IAN'}), 备注: mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
- 按属性进行拆分
路由
基本概念
- 前端路由指的是Hash地址与组件之间的对应关系。
- 用户点击了页面上的路由链接,导致了URL地址栏中的Hash值发生了变化
- 前端路由监听了到Hash地址的变化,把当前Hash地址对应的组件渲染都浏览器中
- 只要当前的 App 组件一被创建,就立即监听window对象的
onhashchange事件
基本使用步骤
- 在项目中安装
vue-router
jsnpm i vue-router@3.5.2 -S- 在src源代码目录下,新建
router/index.js路由模块,初始化代码 、jsimport Vue from 'vue'; import VueRouter from 'vue-router'; import Routes from './routes.js' Vue.use(VueRouter); //vue 的 第三方组件,都需要通过Vue.use( . . . )的方式去 install 组件。 export default new VueRouter { base:'/', //按需自定义公共路径前缀 mode: 'hash', routes } - 在
src/main.js入口文件中,导入并挂载路由模块jsimport router from './router' new Vue({ router, //注册路由模块 render: h => h(App), }).$mount('#app') - 建立
routes.jsjsimport Home from '../view/Home.vue'; export default [ { path: '/', // 重定向到home页面 redirect: '/home' }, { path: '/home', component: Home } ... ] - 注意点
router组件install的时候,会注册两个全局组件router-link: 跳转路由链接router-view: 在什么地方显示内容,占位符
- 在
router-link标签中加入replace属性开启,push是追加历史记录,replace是替换当前记录,默认为push $router: 包含所有的方法$router.push({path:'home'})$router.replace({path:'home'})//替换路由,没有历史记录
$route: 包含所有的属性path/query/params/meta...
- 实现切换(
active-class可配置高亮样式)html <router-link active-class="active" to="/about">About</router-link>
- 在项目中安装
路由重定向和默认子路由
- 在
src/router/index.js路由模块中,通过routes数组声明路由的匹配规则
- 路由重定向指的是: 用户在访问地址A的时候,强制用户跳转到地址C, 通过路由规则的
redirect属性,指定一个新的路由地址 - 默认子路由:如果
children数组中,某个路由规则的path值为空字符串,则这条路由规则,叫做“”jsconst router = new VueRouter({ routes: [ { path: '/' ,redirect: '/home' },//当用户访问/的时候,通过 redirect属性跳转到/home对应的路由规则 { path: '/home' , component: () => import('../view/Home.vue')}, //路由懒加载 { path: '/movie/:id', component: Movie }, //动态路由 只用配置一遍 { path: 'labout ' , component: About }, { path: '/about',// about页面的路由规则(父级路由规则> component: About, children: [ // 1.通过children属性,嵌套声明子级路由规则 { path: 'tab1' ,component: Tab1 },//2.访问/about/tab1时,展示Tab1组件 { path: 'tab2',component: Tab2 }// 3.访问/about/tab2时,展示 Tab2组件 ] } ] })
- 跳转(要写完整路径):html
<router-link to="/home/news">News</router-link>
- 在
路由传参
- query参数js
//跳转并携带query参数,to的字符串写法 <router-link :to='/home/message/detail?id=666&title=你好'></router-link> //<!--跳转并携带query参数,to的对象写法 <router-link :to="{ path : '/home/message/detail', query : { id:666, title :'你好' } }" ></router-link> //接收参数 $route.query.id $route.query.title - params参数js
routes:[{ name: 'xiangqing', path: 'detail/:id/:title',//使用占位符声明接收params参数 component:Detail }] //跳转并携带params参数,to的字符串写法 <router-link :to="/home/message/detail/666/你好">跳转</router-link> //跳转并携带params参数,to的对象写法 <router-link :to="{ name : 'xiangqing' , params :{ id:666, title : '你好' } }"></router-link> $route.params.id //接收参数 $route.params.title - 命名路由(简化长路径路由跳转)html
<!--简化前,需要写完整的路径--> <router-link to="/demo/test/welcome">跳转</router-link> <!--简化后,直接通过名字跳转--> <router-link :to="{name : 'hello'}">跳转</router-link> - 编程式导航跳转:js
this.$router.push({ name:'router1',params: { id: status ,id2: status3},query: { queryId: status2 }}); //编程跳转写在一个函数里面,通过click等方法来触发 this.$router.push('hash地址'),//跳转到指定hash地址,并增加一条历史记录 this.$router.replace('hash地址'),//跳转到指定的hash地址,并替换掉当前的历史记录 this.$router.go(数值n),//实现导航历史前进、后退 $router.back(),//在历史记录中,后退到上一个页面 $router.forward(),//在历史记录中,前进到下一个页面
- query参数
路由的props配置
js//第一种写法: props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件 // props:{a: 900} //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件 // props :true //第三种写法: props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件 props(route){ return { id : route.query.id, title:route.query.title }}路由钩子

- 全局守卫:在初始化和路由切换前后调用js
//全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{ console.log('beforeEach' ,to,from) if(to.meta.isAuth){//判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') == 'atguigu'){ //权限控制的具体规则 next()//放行 }else{ alert('暂无权限查看') //next({name:'guanyu'}) } }else{ next()//放行 } }) //全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title} else{ document.title = "vue_test" }}}) - 独享路由守卫:写在某一个路由里面的独享配置项js
beforeEnter(to,from, next){ console.log('beforeEnter',to,from) if(to.meta.isAuth){//判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'atguigu '){ next() }else{ alert('暂无权限查看:') // next({name : ' guanyu'}) } }else{ next() }} - 组件内守卫js
//进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter (to,from,next) {}, //离开守卫:通过路由规则,离开该组件时被调用 beforeRoutelLeave (to,from,next) {} - 页面权限优化js
const pathArr =['/home','/home/users','/home/rights'] if (pathArr.indexOf(to.path)!=-1) { const token = localstorage.getItem('token')} - 路由器的两种工作模式
- 对于一个url来说,什么是hash值?——#及其后面的内容就是hash值。
- hash值不会包含在HTTP请求中,即: hash值不会带给服务器。
- hash模式:
- 地址中永远带着#号,不美观。
- 若以后地址通过第三方手机app分享,若app校验严格,地址会被标记为不合法。
- 兼容性较好。
- history模式:
- 地址干净,美观。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
- 全局守卫:在初始化和路由切换前后调用