一.Vue核心
01.初始Vue
02.Vue模板语法({{name}})
03.数据绑定(单向v-bind和双向v-model)
04.el和data的两种写法(el v.$mount挂载和data对象式和函数式)
对象式:data:{name:‘zs’} 函数式:data(){ return } →必须有return
05.MVVM模型(Vue的设计参考了该模型,模型(data)、视图(模板)、视图模型(vue实例对象),所以vm一般指Vue的实例对象)
(data中所有的属性,最后都出现在vm身上)
*06.数据代理
061.object.defineProperty方法(较高级的添加属性的方法,能给追加的属性进行限制)(给person添加age属性)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dSeOuhB9-1676712361576)(C:\Users\17748\AppData\Roaming\Typora\typora-user-images\image-20230208223601921.png)]
还有其他配置项,如:get()、set()
get(){ return } 当有人读取person里的age属性时,getter被调用
set(value){ } 当有人修改person里的age属性时,setter被调用
062.何为数据代理(通过一个对象代理对另一个对象中属性的操作(读/写))
(vm._data就是我们使用的data(此处无代理,是数据劫持))、数据代理原理图示
(Vue通过数据代理将_data里的数据直接挂载在vm上,即使用object.defineproperty,定义getter、setter,更加方便的操作data中的数据)
(Vue中的数据代理:通过Vue对象来代理data对象中属性的操作(读写))
基本原理:通过object.definproperty()把data对象中所有属性添加到vm上。为每一个添加到vm上的属性,都指定一个gettersetter。在gettersetter内部去操作(读写)data中的数据。若有人通过vm去修改放到vm身上的属性,setter工作。
代理实质:通过vm读取或修改vm身上的name或address,其实改的就是data中的name和address
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7KTVaKev-1676712361577)(C:\Users\17748\AppData\Roaming\Typora\typora-user-images\image-20230218153101197.png)]
数据劫持简单解释:
把data的数据进行加工才放到_data中,只要修改data中的数据,就会让页面上用到该数据的地方自动更新(响应式),所以必须让vue能检测得到data中的数据改变了
07.事件处理
071.(v-on:或@绑定事件。事件的回调需要配置在与data同级的methods对象中,最终会在vm上。methods里配置的函数不要用箭头函数,否则this就不是vm了。methods中配置的函数,都是被Vue管理的函数,里面的this指向的是vm或者组件实例对象。@click=‘demo($event ,…)’,里面定义几个参数,回调函数里就能接收到几个参数)
(只有data里的数据才会做数据代理和数据劫持,methods里的不会,因为只有data里的数据需要直接用到页面中)
(click、scroll(给滚动条加的滚动事件)、wheel(鼠标滚动轮的滚动事件))
072.事件修饰符(如@click.prevent → 阻止默认事件(如点击链接时不要跳转))
(stop阻止事件冒泡,once事件只触发一次,capture使用事件的捕获模式,self只有event.target是当前操作的元素才是触发事件,passive事件的默认行为立即执行,无需等待事件回调执行完毕)
(嵌套关系时先捕获再冒泡,冒泡阶段才是处理事件的,所以使用capture时由捕获阶段处理事件,会由原来的21变为12)
(self一定程度上也能阻止冒泡,)
073.键盘事件(@keyup(常用)、@keydown)
(event.keyCode、event.target.value)
( 使用方法 → @keyup.esc = ‘ showinfo ’ → 即按下esc键并松开时才会触发该事件)
(Vue中常用的按键别名:回车enter、删除delete、退出esc、空格space、换行tab、上up、下、左、右)
(特殊键 tab,本身作用就是使焦点从当前位置切走,所以必须配合keydown使用,同理Ctrl、alt、meta一样)
(为提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为短横线命名法)
(系统修饰键:Ctrl、alt、shift、meta。配合keyup使用:按下修饰键的同时再按下其他键,随后释放其他键,事件才会触发。配合keydown使用:正常触发事件)
(Vue.config.keyCodes.自定义键名=键码,可以定制按键别名)
08.计算属性
data中的数据只要一发生变化,Vue就会重新解析页面
(使用方法:与data同级,computed: { fullname:{ get(){ return } ,set(value){}} } )→set非必要
其中的get什么时候被调用:1.初次读取fullname时,2.所依赖的数据发生变化时
和methods的最大区别:computed内部有缓存机制,值不变时多次读取fullname只会调用一次get(),效率更高,调试方便
定义:要用的属性不存在,要通过已有的属性计算得来
原理:底层借助了object.defineproperty方法提供的getter、setter
注意:计算属性最终会出现在vm上,直接读取使用即可,如果计算属性要被修改,必须写setter去响应修改,且set中要引起计算时依赖的数据发生改变
简写:::当确定不需要set进行修改,只需要get进行读取时,可简写为computed:{ fullname() { return } }
09.监视属性
091.(使用监视方法1:和data同级 watch:{ ishot: { handler(newvalue,oldvalue){} },能检测data和computed里的数据)
方法2:vm.$watch(‘ishot’:{ handler(…){}} )
区别:当需要监视的对象暂未明确时,可以使用方法二
(其他配置项:immediate:true → 初始化时让handler调用一下)
(当被监视的属性变化时,回调函数自动调用,进行相关操作,监视的属性必须存在,才能进行监视!)
092.深度监视
监视多级结构中数据的变化:watch:{isHot:{ 'number.a ':{}}
当需要监视numbers:{a:1,b:2},number里每个数据的改变时,需要用到深度监视,只需要在watch里加deep:true配置项即可
*Vue中的watch默认不监视对象内部值的改变(一层)
*配置deep:true可以监视对象内部值的改变(多层)
注意:*Vue自身可以检测对象内部值的改变,但Vue提供的watch默认不可以
*使用watch时根据数据的具体结构,决定是否采用深度监视
简写:::watch:{ ishot (newvalue,oldvalue) { } }
方法二:vm.$watch( ‘ishot’ , function(newvalue,oldvalue) {} )
计算属性和监视属性都是computed对象里有一个fullname对象,里面才包着函数
10.计算属性和监视属性的区别
*computed能完成的功能,watch都能完成
*watch能完成的功能,computed不一定能完成,如watch可以进行异步操作,因为computed靠的是return返回值,而返回值不能等一等再返回,所以computed不能进行异步操作,如定时器等
注意:
*所被Vue管理的函数,最好写成普通函数,不被Vue管理的函数(如定时器、Ajax、Promise的回调函数…),最好写成箭头函数(箭头函数没有自己的this,需要往外找),这样的this的指向才是vm或组件实例对象
11.class与style绑定
111.class样式
绑定class样式(需要后期改变class的样式)——字符串写法(适用于样式的类名不确定,需要动态指定)→ :class=‘mood’
(随机数0~3:Math.floor ( Math.random()*3 ) >>> Math.random 取(0,1) >>> .floor 向下取整 >>> .ceil 向上取整)
绑定class样式——数组写法(适用于要绑定的样式个数、名字不确定 >>> :class=“classArr”)
绑定class样式——对象写法(适用于要绑定的样式个数、名字确定,但要动态决定用不用 >>> :class=“classObj”,在data里truefalse控制)
112.style样式
:style=" { fontSize : fsize + ‘px’ } " data: { fsize : 40}
一般写成 :style=‘styleObj’ data: { styleObj : { fontSize : fsize + ‘px’ }}
另外数组写法用的比较少,略
12.条件渲染
(v-show、v-if)
*v-if写法:v-if = ’表达式‘ v-else-if = ’表达式‘ v-else = ’表达式‘ >>>不展示的Dom元素会被移除 >>>适用于切换频率较低的场景,
*v-show写法:v-show = ’表达式‘ >>>不展示的DOM元素display隐藏,不会移除 >>>适用于切换频率较高的场景
*< template > 模板标签 → 不会影响结构,不会出现在页面中,但只能配合 v-if 使用
13.列表渲染
131.v-for
<li v-for="(p,index) in persons" :key="p.id"> {{p.name}}-{{p.age}} </li> >>>遍历数组 >>>index用不到可以不写
<li v-for="(value,k) of car" :key="k"> {{k}}-{{value}} ></li> >>>遍历对象
还能遍历字符串、遍历指定次数
132.key的原理
【面试题1】
1.react、Vue中的key有什么作用?(key的内部原理)
1)虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM和旧虚拟DOM的差异比较,比较规则如下:
2.对比规则:
*旧虚拟DOM中找到了与新虚拟DOM相同的key:
若虚拟DOM中内容没变 >>> 直接使用之前的真实DOM;
若虚拟DOM中内容变了 >>> 生成新的真实的DOM,随后替换掉页面中之前的真实DOM
*旧虚拟DOM中未找到与新虚拟DOM相同的key :
创建新的真实DOM,随后渲染到页面中
3.用index作为key可能会引发的问题:
*若对数据进行:逆序添加、逆序删除等破坏顺序操作时,会产生没有必要的真实DOM更新 >>>页面效果没问题,但效率低
*如果结构中还包含输入类的DOM时,会发生错误DOM更新 >>> 界面有问题
4.开发中如何选择key?
*最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
*如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没问题的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1XnO0Mx-1676712361578)(C:\Users\17748\AppData\Roaming\Typora\typora-user-images\image-20230210163255981.png)]
133.列表过滤(filter)
134.列表排序(arr.sort)
135.Vue检测数据原理
data -> 将data加工(里面每个元素都加上getter和setter) -> vm._data=data
data一变,vm._data里的数据跟着变,setter监测到后重新解析页面,生成虚拟DOM进行新旧对比,最后渲染页面
1)Vue会监视data中所有层次的数据
2)如何监测对象中的数据:通过setter实现监视,且要在new Vue时就传入要监测的数据,即在data里
*对象中后续追加的属性,Vue默认不做响应式处理
*如需给后添加的属性做响应式,可以使用Vue.set 或 vm.$set
3)如何监测数组中的数据:通过包裹数组更新元素的方法实现,本质就是做了两件事:
*调用原生对应的方法对数组进行更新,然后重新解析模板,进而更新页面
4)在Vue修改数组中的某些元素一定要用如下方法:
*使用这些API:push()、pop()、shift()、unshift()、splice、sort、reverse
*Vue.set() 或 vm.$set()
注意:Vue.set() 或 vm.$set() 不能给vm 或 vm的根数据对象添加属性
136.Vue.set的使用
Vue.set(this.student.sex='男') or this.$set(this.student.sex='男')
137.Vue监测数据改变的原理
14.收集表单数据
1)若<input type=“text”,则v-model收集的是value值,用户输入的就是value值
2)若<input type=“radio” ,则v-moedl收集的是value值,且要给标签配置value值
3)若<input type=“checkbox” ,
*没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
*配置input的value属性:**v-model的初始值是非数组,则收集的就是checked,,若初始值是数组,收集的是value组成的数组
注意:v-model的三个修饰符:
*lazy:失去焦点再收集数据
*number:输入字符串再转为有效的数字
*trim:输入首尾空格过滤
15.过滤器(filter)
16.内置指令
v-text:向其所在的节点中渲染文本内容,与插值语法的区别:v-text会替换掉几点中的内容,{{xx}}则不会 (<div v-text=“name” )
v-html:向指定节点中渲染包含html结构的内容。与插值语法的区别:v-html会替换掉节点中的所有内容,且v-html能识别html结构
注意:v-html有安全性问题!!!在网站上动态渲染任意HTML是非常危险的,容易导致XXS攻击,一定要在可信的内容上使用v-html,永远不要用在用户提交的内容上
v-cloak:(没有值),本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性;使用css配合v-cloak可以解决网速慢时页面展示出{{×××}}的问题
<h1 v-cloak> {{name}} </h1> >>> [v-cloak]:{ display:none } >>>[v-cloak]选择所有含v-cloak属性的元素
v-once:所在节点在初次动态渲染后,就视为静态内容了,以后的数据改变也不会引起v-once所在结构的更新,可以用于优化性能
<h1 v-once>初始化的n值是:{{n}} </h1>
v-pre:跳过其所在节点的编译过程,可以利用它跳过使用指令语法、没有使用插值语法的节点,会加快编译(也没有值)
17.自定义指令
定义语法:
1)局部指令:new Vue ({ directives: {指令名:配置对象} }) 或 new Vue ({ directives {指令名:回调函数} })
2)全局指令:Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
配置对象中常用的三个回调:
*bind:指令与元素成功绑定时调用
*inserted:指令所在元素被插入页面时调用
*update:指令所在模板结构被重新解析时调用
注意:
*指令定义时不加 v- ,但是使用时需要加 v-
*指令名如果是多个单词,要使用 kebab-case 命名方式,不要用 camelCase命名
18.生命周期:
(生命周期回调函数、生命周期函数、生命周期钩子)
定义:Vue在关键时刻帮我们调用的一些特殊名称的函数
-
生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
-
生命周期函数中的this指向是vm或组件实例对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8i7wAmVy-1676712361580)(C:\Users\17748\AppData\Roaming\Typora\typora-user-images\image-20230211160026068.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KY6Zx5R2-1676712361581)(C:\Users\17748\AppData\Roaming\Typora\typora-user-images\image-20230211161243397.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NHgrKmSj-1676712361582)(C:\Users\17748\AppData\Roaming\Typora\typora-user-images\image-20230211161344309.png)]
常用的生命周期钩子:
1)mounted(){ } >>> Vue完成模板的解析并把初始的真实DOM放入到页面后(挂载完毕),调用mounted
>>>发送Ajax请求、启动定时器、绑定自定义事件、订阅消息等初始化操作
2)beforeDestroy(){}:清除定时器、解绑自定义事件、取消订阅消息等收尾工作
关于销毁Vue实例:
- 销毁后借助Vue开发者工具看不到任何消息
- 销毁后自定义事件会失效,但原生DOM事件依然有效
- 一般不会再beforeDestroy操作数据,因为即使操作数据也不会再触发更新流程了
二.Vue组件化编程
2.1模块与组件、组件与组件化
- 模块:对外提供特定功能的js程序 >>> 复用js、简化js、提高js运行效率
- 组件:用来实现局部(特定)功能效果的代码集合(html、css、js、image…) >>> 复用编码、简化项目编码、提高运行效率
- 模块化:当应用中的js都以模块来编写,那这个应用就是一个模块化的应用
- 组件化:当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
**2.2 非单文件组件:**一个文件总包含n个组件
2.2.1 Vue中使用组件的三大步骤:
1)定义组件(创建组件)
2)注册组件
3)使用组件(写组件标签)
2.2.2 如何定义一个组件:使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那份options几乎一样,但区别如下:
- el不用写,因为最终所有的组件都要经过一个vm管理,由vm中的el决定服务于哪个容器
- data必须写成函数,为了避免组件被复用时,数据存在引用关系(即每次使用data里的数据都是一样的,不会被改)
*使用template可以配置组件结构
2.2.3 如何注册组件:
-
局部注册:靠new Vue的时候传入components选项
-
全局注册:靠Vue.components(‘组件名’,组件)
2.2.4 编写组件标签:
2.2.5 关于VueComponent
1)school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
2)我们只需要写,Vue解析时会帮我们创建shool组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
3)注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!
4)关于this指向:
-
组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是:VueComponent实例对象,即vc
-
new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是:Vue实例对象,即vm
(简单来说,VC就是小型的vm,但是VC里的data必须写成函数式,且不能写el)
2.2.6一个重要的内置关系
(VueComponent.prototype._ _ proto _ _ === Vue.prototype)
为什么要有这个关系:让主讲实例对象(VC)可以访问到Vue原型上的属性、方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5bJ5FLUe-1676712361583)(C:\Users\17748\AppData\Roaming\Typora\typora-user-images\image-20230214152406605.png)]
**2.3 单文件组件:**一个文件中只包含有1个组件
三.使用Vue脚手架
3.1 初始化脚手架
-
Vue脚手架:是Vue官方提供的标准化开发工具(开发平台)
-
具体步骤:
1)(仅第一次执行):全局安装:@Vue/cli >>> npm i -g @vue/cli
2)切换到你要创建项目的目录,然后使用命令创建项目:vue create ×××
3)启动项目:npm run serve
-
脚手架文件结构:
.gitignore:git版本管制忽略的配置,,babel.config.js:babel的配置文件,,package.json:应用包配置文件,,package-lock.json:包版本控制文件
-
注意:如果下载缓慢,请配置npm淘宝镜像:(电脑终端执行) npm config set registry https://registry.npm.taobao.org
-
Vue脚手架隐藏了所有webpack相关的配置,若想查看具体的webpack配置,请执行:vue inspect > output.js
-
( babel : es6 转成es5,,eslint:检查语法)
3.2 关于不同版本的Vue:
1)Vue.js和Vue.runtime.×××.js的区别:
- Vue.js是完整版的Vue,包含:核心功能+模板解析器
- Vue.runtime.×××.js是运行版的Vue,只包含:核心功能,没有模板解析器
- (Vue=Vue核心+模板解析器(占三分之一),代码写完后交给webpack打包,而模板解析器并不需要打包)
2)因为Vue.runtime.×××.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容
3.3 render函数
单文件组件创建Vue实例对象vm时,不能使用template
new Vue({ new vue({
el:'#app', render: h => h(App)
render(createElement){ })
return createElement('h1','你好啊!') 精简后>>>
}
})
//其中createElement是一个方法,创建html元素
3.4 vue.config.js配置文件
使用vue.config.js对脚手架进行个性化定制,如入口文件修改、关闭语法检查等,详情见:https://cli.vuejs.org/zh
3.5 ref属性
-
被用来给元素或子组件注册引用信息 (id的替代者)
-
应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
-
使用方式:
打标识:<h1 ref="×××">....</h1> 或 <School ref="×××"></School>
获 取:this.$refs.×××
3.6 props配置项
-
功能:让组件接收外部传过来的数据(父传子,最简单。若要子传父,前提是父先给子一个函数,子在合适的时候调用该函数)
-
传递数据:(父组件)
<School name="×××"/>
-
接收数据:(与data同级)(子组件)
props:['name'] //方式一(传递数据) props:{ name:String //方式二(限制类型) } props:{ name:{ required:true, type:String, default:'zs' //方式三(限制类型、限制必要性、指定默认值) } }
-
注意:props是只读的,Vue底层会检测你对props的修改,如果进行了修改,就会发出警告(结果也能修改成功,但是控制台会报错且后续可能会引起奇奇怪怪的错误),若业务需求确实需要修改,那么可以复制一份props的内容到data中,然后去修改data中的数据
-
与数据冲突时,以props为主?
3.7 mixin(混入/合)配置项
-
功能:可以把多个组件公用的 配置 (如data、methods、生命周期等等)提取成一个混入对象
-
使用方式:(如student和school两个组件里的method都一样,可以提取到一个mixin.js文件中并对外暴露)
-
使用混入:1)全局混入:入口文件中import ××× ,并Vue.mixin( ××× )
2)局部引入:在需要引入的组件中import ××× from …,与data同级 mixins : [’ ××× ']
-
数据冲突时,以本身组件为主,声明周期冲突时,都要
3.8 插件
-
功能:用于增强vue
-
本质:是一个包含install方法对象,install第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
-
定义插件:创建plugins.js文件,export default { install (vue,x,y,) {Vue.filter({ }) …}},添加实例方法、添加全局指令、给原型对象添加方法…
-
使用插件:Vue.use( )
3.9 scoped
- 作用:让样式在局部生效,防止冲突
- 写法:<style scoped
3.10.webstorage(浏览器本地存储)(js里就有)(SessionStorage 和 LocalStorage)
1)存储内容大小一般支持5MB左右(不同浏览器可能不同)
2)浏览器端通过Windows.sessionStorage和Windows.localStorage属性来实现本地存储机制
3)相关api:
- ××××Storage.setItem( ‘ key ', ’ value ') >>> 该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其值
- ××××Storage.getItem( ‘ person’) >>>读取存储中的数据
- ××××Storage.removeItem( ‘ key ') >>> 删除存储中的数据
- ××××Storage.clear( ) >>> 清空存储中的所有数据
4)注意:
- SessionStorage存储的内容会随着浏览器窗口关闭而消失
- LocalStorage存储的内容,需要手动清除才会消失
- ××××Storage.setItem( ‘ key ', ’ value ') 如果××××对应的value获取不到,那么返回值是null
- JSON.parse(null)的结果依然是null
3.11 组件的自定义事件
1)一种组件间通信的方式,适用于:子组件 ===>父组件
2)使用场景:子组件给父组件传数据,就要在父组件中给子组件绑定自定义事件(事件的回调在父组件中)
3)绑定自定义事件:
-
方式一:在父组件中:
<Demo @abc="test"/> 或 <Demo v-on="test"/>
-
方式二:在父组件中:
<Demo ref="demo"/>
....
mounted(){
this.$refs.×××.$on('abc',this.test)
}
- 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法
4)触发自定义事件:this.$emit ( ‘abc’ , 数据)
5)解绑自定义事件:this.$off ( ‘abc’ )
6)组件上也可以绑定原生DOM事件,需要使用native修饰符 >>> <student @click.native = ‘demo’
7)注意:通过this. r e f s . × × × . refs.×××. refs.×××.on(‘abc’,回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出现问题( $on >>> 当abc方法一被调用时执行后面的回调函数)
3.12 全局事件总线
1)一种组件间通信的方式,能实现任意组件间通信
2)安装全局事件总线:
new vue({
....
beforeCreate(){
Vue.prototype.$bus = this //$bus就是当前应用的vm
}
})
3)使用事件总线:
-
接收数据:A组件接收数据,则在A组件中给$bus绑定自定义事件,事件的回调函数留在A组件中
methods:{ demo(){ } } ..... mounted(){ this.$bus.$on('hello',this.demo) //一触发hello的自定义事件,就执行demo的回调函数 }
-
提供数据:
this.$bus.$emit('hello',数据) >>> 触发hello的自定义事件,并把数据传过去
4)最好在beforeDestroy钩子中,用 $off 去解绑当前组件所用到的事件
3.13 消息订阅与发布
1)一种组件间通信的方式,适用于任意组件间通信、
2)使用步骤:
-
安装pubsub:npm i pubsub-js (还有很多其他的包)
-
引入:import pubsub from ’ pubsub-js ‘
-
接收数据:A组件想要接收数据,则在A组件中订阅消息,订阅的回调留在A组件中
methods:{ demo(msgName,data){} //hello的回调demo能接收到到2个参数(消息名和传递过来的数据) } mounted(){ this.pubId = pubsub.subscribe('hello',this.demo) //订阅名为hello的消息,一发布就执行demo。pubId为后面销毁用 }
-
提供数据:
pubsub.public('hello',数据)
4)最好在beforeDestroy钩子中,用pubsub.unsubscribe ( this.pubId ) 取消订阅
3.14 nextTick
(也是生命周期钩子!!)
1)语法:this.$nextTick (回调函数)
2)作用:在下一次DOM更新结束后执行其指定的回调
3)什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
(vue在解析回调函数时,不是每执行完一句代码就解析一次页面,而是当整个回调执行完之后才重新解析一遍页面,所以有时候刚创建input输入框但是并没有出现到页面中时,后面执行的输入框自动获得焦点的代码就会失效,因为执行该代码时还没重新渲染页面)
3.15 Vue封装的动画与过渡
-
作用:在插入时、更新或移除DOM元素时,在合适的时候给元素添加样式类名
-
图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QHJYlhPe-1676712361585)(C:\Users\17748\AppData\Roaming\Typora\typora-user-images\image-20230216150011156.png)]
1)动画:
<transition appear> //若加了name属性,那下面的样式名是 .名字-enter-active,加了appear属性初始化页面时就有动画
<h1 @click='isShow = !isShow' > hello! </h1>
</transition>
isShow = true
.v-enter-active{ //进入的过程
animation : showActive 1s linear // 调用动画showActive 1s 匀速
}
.v-leave-active{ //离开的过程
animation : showActive 1s linear reverse //动画反转
}
@keyframes showActive{ //定义动画,showActive为动画名
from{
transform : translateX (-100%)
}
to{
transform : translateX (0px)
}
}
2)过渡:
<transition name='hello' appear>
<h1 @click='isShow = !isShow' > hello! </h1>
</transition>
isShow = true
h1{
transition : 1s linear //谁需要过渡效果就往谁身上加
}
hello-enter{ //进入的起点
transform : translateX (-100%)
}
hello-enter-to{ //进入的终点
transform : translateX (0)
}
hello-leave{ //离开的起点
transform : translateX (0)
}
hello-leave-to{ //离开的终点
transform : translateX (-100%)
}
3)注意:若有多个元素需要过渡,则需要使用:<translate-group , 并且每个元素都要指定key值
四.Vue中的Ajax
4.1常用的发送Ajax请求的方式有:xhr(鼻祖)(麻烦,不常用)、jQuery(有封装Ajax请求,但其专业是封装DOM操作)、axios(用得多)、fetch(直接在Windows身上就有这个方法,和xhr平级。特点是给请求返回的数据包2层Promise,且有兼容性问题IE×)
4.2 vue中的axios
使用方法:npm i axios,import引入
axios.get('url').then(
response=>{ //请求成功的形参,返回的都是对象
console.log('请求成功了', response.data)
},
error=>{ //失败的形参
console.log('请求失败了', error.message)
}
)
4.3 代理服务器
开启代理服务器:(vue.config.js,修改后要重启)
方式一:
devServer:{
proxy:'需要请求的数据的来源的服务器的地址'
}
缺点:1)不能配置多代理,只能配置一个
2)不能灵活地控制走不走代理 (若前端资源与请求的资源重名时,直接从本身取数据,不走代理,优先匹配前端资源)
方式二:
devServer:{
proxy:{
'/api':{ //'/api'为请求前缀,请求地址只要含该前缀就走代理
target:'<url>', //代理目标的基础路径
pathRewrite : {'^/api':''}, //重写路径,向目标服务器发送请求时将所有/api前缀去掉
ws:true, //用于支持websocket,默认为true
changeOrigin:true, //用于控制请求头中的host值(即请求地址撒不撒谎),默认为true
},
'/demo':{
target:'<url>',
pathRewrite : {'^/demo':''},
},
..... //能配置多个代理服务器
}
}
4.4 vue-resource
(发送Ajax请求的一个vue里的插件库,是对xhr的封装,,现已不维护,慎用!!!)
1)使用方法:npm i vue-resource 入口文件中import 并Vue.use(),,至此所有vm和VC身上都多了一个 $http
this.$http.get('url').then(response=>{},error=>{})
4.5 slot插槽
- 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件===>子组件
1)默认插槽:
<Category>
<img src='' alt=''
</Category>
Category组件中:
<div>
<h1></h1>
<slot>这里可以写内容,当组件标签体里面没有内容时显示,有内容时不显示</slot>
</div>
2)具名插槽:
<Category>
<img slot='center' src='' alt=''/>
<a slot='footer' href=''></a>
</Category>
Category组件中:
<div>
<h1></h1>
<slot name='center'>这里可以写内容,当组件标签体里面没有内容时显示,有内容时不显示</slot>
<slot name='footer'>这里可以写内容,当组件标签体里面没有内容时显示,有内容时不显示</slot>
</div>
注意:1)多个内容可以放在同一个插槽里,不会覆盖,可以追加
2)如果使用了<template标签,可以使用 v-slot : footer。
3)作用域插槽
数据在组件自身,但根据数据生成的结构需要组件的使用者来决定
App组件: (1.往插槽里放结构>>>是插槽的使用者,而需要用到的数据games在Category组件中)
<Category>
<template scope='demo'> //3.用scope接收传过来的数据(是一个对象),命名为demo
<Ul>
<li>g in demo.games</li>
</ul>
</template>
</Category>
Category组件:
<div>
<h1></h1>
<slot :games='games'></slot> //2.将数据games传给插槽的使用者,(这里可以传多个数据,所以使用者接收的是一个对象)
</div>
data:games:[]
注意:
-
上面的scope 也可以用 slot-scope
-
作用域插槽也可以有名字
五.Vuex
5.1 vuex是什么
-
专门在Vue中实现集中式状态 (数据) 管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间的通信
-
什么时候使用Vuex >>> 1) 多个组件依赖于同一状态 2)来自不同组件的行为需要变更同一状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tADO9ydo-1676712361586)(C:\Users\17748\AppData\Roaming\Typora\typora-user-images\image-20230216211907805.png)]
5.2 使用Vuex
1)npm i vuex@3 (vue2用vuex3,vue3用vuex4)
2)src目录下新建store文件>index.js,配置index.js :
import Vuex from 'vuex'
import Vue from ’vue'
Vue.use(Vuex) //默认需要先使用Vuex才能new出vuex实例,而不放在入口文件中是因为顺序问题
const actions={ } //用于响应组件中的动作
const mutations = { } //用于操作数据
const state = { } //用于存储数据
export default new Vuex.Store({ //创建并暴露store
actions,
mutations,
state
})
3)并在入口文件中import引入store,并在new的vm中添加配置项store
4)例子:
//组件中
data里: n:1
methods:{
add(){
this.$store.dispatch('jia',this.n) //如果不需要经过actions,此处直接调commit,越过dispatch
}
}
store的index.js里:
const actions={
jia(context,value) => { //context(上下文)其实是迷你store,actions里的函数名一般用小写
context.commit('JIA',value)
}
}
const mutations = { //mutations里的函数名一般用大写
JIA(state,value)=>{
state.sum += value
}
}
const state = { sum : 0 } //用于存储数据
5.3 store里的另一个配置项getters
- 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工
- 在store.js中追加getters配置
const state = { sum:0 }
const getters = {
bigSum(state){ return state.sum * 10} //注意必须有返回值return
}
export default new Vuex.Store({
......,
getters
})
- 组件中读取数据:$store.getters.bigSum
5.4 四个map方法的使用
1)mapState方法:用于帮助我们映射state中的数据为计算属性
2)mapGetters方法:用于帮助我们映射getters的数据为计算属性
在标签里总是写 {{ $store.state.sum }} {{ $store.getters.bigSum }}太长太麻烦,可以利用计算属性解决:
import {mapState,mapGetters} from 'vuex'
computed:{
he(){
return this.$store.state.sum //法1.写计算属性
}
// 法2.mapState生成计算属性,读取state中的数据
...mapState({ he:'sum',xuexiao:'school',xueke:'subject' }) //对象写法
...mapState(['sum','school','subject']) //数组写法
//即帮我们批量生成了he(){return this.$store.state.sum}、xuexiao(){...}等方法
}
mapGetters同理!!!
3)mapActions方法:
用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(×××)的函数
<button @click='add(n)'>点我加一</button> //!!!这里的add方法必须小括号传参,否则使用mapActions时默认为鼠标点击事 件,而无法获取需要用到的数据n
import {mmapActions,mapMutations} from 'vuex'
data里有 n:1
methods:{
add(){
this.$store.dispatch('jia',this.n) //法一.亲自使用方法调用dispatch提交数据
}
//法二.借助mapActions生成对应的方法,调用dispatch去联系Actions
...mapActions({add:'jia'},{subtract:'jian'}) //此为对象写法,同样还有数组写法
}
4)mapMutations方法:(同理)
用于帮助我们生成与mutations对话的方法,即:包含$store.commit(×××)的函数
5.5 vuex的模块化写法
1)目的:让代码更好维护,让多种数据分类更加明确
2)修改store.js配置:
const countOptions={ //这样就有2个名字分别为countOptions和personOptions的Store
namespaced:true, //后续凡是需要精确到哪个Store里就必须要开启命名空间
actions:{},
mutations:{},
state:{}
}
const personOptions={...}
export default new Vuex.Store({
modules:{
countOptions,
personOptions
}
})
3)使用mapState等方法的写法:
组件中:
import {mapState} from 'vuex'
computed:{
sum(){
...mapState('countOptions',['sum','school',subject]) //直接到名为countOptions的Store里的state找数据
}
}
methods:{
...mapMutations('countOptions',{ add:'JIA' }) //这里也需要告诉map..去哪个Store里找数据
}
4)如果不用mapState等方法,而是自己写:
computed:{
sum(){ //state里包含countOptions和personOptions
return this.$store.state.countOptions.sum //注意:名字写在state后面!!
},
firstPersonName(){
return this.$store.getters['personOptions/firstPersonName']
//getters里面包含的是 personOptions/firstPersonName:"zs",countOptions/sum:0
//原本是getters.personOptions/firstPersonName 但其写法不符合规范,需要换成上面的写法
}
}
methods:{
add(){
this.$store.commit('personOptions/ADD_PERSON',personObj) //注意名字写在前面!!!!
}
}
5)也可以把不同名字的Store拆分成文件,并对外分别暴露,在相关文件引入
六.vue路由(Vue-router)
>>>路由就是一组key-value的对应关系 >>> 多个路由,需要经过路由器的管理
6.1 vue-router 是 vue的一个插件库,专门用来事项SPA应用
6.2 SPA是什么?
1)单页web应用
2)整个应用只有一个完整的页面
3)点击页面中的导航链接不会刷新页面,只会做页面的局部更新
4)数据需要通过Ajax请求获取
6.3 路由是什么?
1)概念:一个路由就是一组映射关系(key-value),key为路径,value可能是function或component
2)路由的分类:
-
后端路由:* value是function,用于处理客户端提交的请求
*工作过程:服务器接收到一个请求时,根据请求的路径找到匹配的函数来处理请求,返回响应数据
-
前端路由:* value是component,用于展示页面内容
*工作过程:当浏览器的路径改变时,对应的组件就会显示
6.4 路由的基本使用
1)安装:npm i vue-router@3 (vue2用vue-router@3,vue3用vue-router@4)
2)引入并应用:import VueRouter from , Vue.use(VueRouter)
3)配置router:src下新建router,下index.js
import VueRouter from 'vue-router'
import Home from '...'
const router=new VueRouter({
routes:[
{
path:'/home',
component:Home //还可以有name配置项
},
{},
......
]
})
export default router
4)在入口文件中引入router,并加入配置
import router from '...'
......
new Vue({
el:'#app',
render:h=>h(app),
......
router
})
5)实现切换:(<router-link,,,to=‘ ’)(active-class可配置高亮样式)!!!最终<router-link标签会变成 a标签
<router-link active-class='active' to='/about'>About</router-link>
6)指定展示位置:
<router-view></router-view>
注意:
-
路由组件通常放在pages文件夹下,一般组件放在components文件夹下
-
通过切换,‘隐藏’ 了的路由组件,默认是被销毁了的,需要的的时候再去挂载
-
每个组件都有自己的 r o u t e 属性,里面存储这自己的路由信息 ( 各自 route属性,里面存储这自己的路由信息 (各自 route属性,里面存储这自己的路由信息(各自route不同)
-
整个应用只有一个router,可以通过组件的 r o u t e r 属性获取到( router属性获取到( router属性获取到(router都相同)
6.5 多级(嵌套)路由
1)配置路由规则,使用children配置项,子级路由的path里不需要加斜杠 ‘/ ’
2)跳转 <router-link to = ’ /home/news ’ => 这里要写完整路径!!!
6.6 路由的query参数
1)传递参数
//跳转路由并携带query参数,to的字符串写法
<router-link :to="`/home/message/detail?id={m.id}&title={m.title}`"></router-link>
//to的对象写法
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}"
2)接收参数:$route.query.id
6.7 路由的params参数
1)配置路由,需要声明接收params参数,使用占位符
{
name:'xiangqing',
path:'detail/:id/:title', //使用占位符声明接收params参数
component:Detail
}
2)传递参数
<router-link :to="`/home/message/detail/{m.id}/{m.title}`"> //跳转路由并携带query参数,to的字符串写法
<router-link :to="{ //to的对象写法
name:'detail', //注意!!!如果使用params的对象写法,必须靠name实现跳转,不能用path
params:{
id:m.id,
title:m.title
}
}"
接收:$route.params.id
6.8 路由的props参数
1)作用:让路由组件更方便地收到参数,不用再多次在标签体中使用$route.query.id
{
name:'xiangqing',
path:'detail',
component:Detail,
props:{ id:1, title:'hello'} //props的第一种写法:值为对象,所有key-value值都会以props形式传给组件
props:true //props的第二种写法:值为布尔值,为真则把该路由收到的所有params参数,以 props的形式传给组件
props($route){
return {id:$route.query.id, title:$route.query.title}
} //props的第三种写法:值为函数,返回一个对象,同样以props传给组件
}
6.9 <router-link的replace属性
1)作用:控制路由跳转时操作浏览器历史记录的模式
2)浏览器的历史记录有2中写入方式:分别是push和replace,push是追加历史记录,replace是替换当前记录,路由跳转时候默认为push
3)如何开启replace模式:
<router link replace ......> </router-link>
6.10 编程式路由导航
$router里的api:push、replace、back、forward、go(n) => 路由走n步(n可以为负)
1)作用:不借助<router-link实现路由跳转,让路由跳转更加灵活
2)使用方式:
this.$router.push({
name:'xiangqing',
params:{
id:×××,
title:'×××'
}
})
6.11缓存路由组件
因为路由切走后,原来的路由组件会被销毁,所以原本在路由组件里输入的数据会被清空
1)作用:让不展示的路由组件保持挂载,不被销毁
2)使用方式:
找到需要继续挂载的路由组件的根展示区,用 <keep-alive标签包裹起来,并加上include=‘组件名’属性,否则该展示区的所有组件都会缓存
<keep-alive include='News'> //需要缓存多个组件时::include="['News','message']"
<router-view></router-view>
</keep-alive>
6.12 两个新的生命周期钩子
路由组件所独有的两个钩子,用于捕获路由组件的激活状态
activated(){ //路由组件被激活时触发(即组件展示出来的时候)
console.log('组件被激活了')
}
deactivated(){ //组件失活时触发(组件消失在我们面前的时候)
console.log('组件失活了')
}
6.13 路由守卫
- 作用:对路由进行权限控制
- 分类:全局守卫、独享守卫、组件内守卫
1)全局前置路由守卫 —— 初始化的时候、每次路由切换之前被调用
router.beforeEach((to,from,next)={
if(to.path == '/home/news' || to.path == '/home/message'){ //这里除了path还可以用name作为路由标识
if(localStorage.getItem('school')){ 若需要判断的路径较多可以使用meta配置项
next() if(to.meta.isAuth)→去往的组件是否需要权限
}else{
alert('无权限查看!!')
}
}else{
next()
}
})
2)全局后置路由守卫 ——初始化时、每次路由切换之后被调用
router.after( (to,from) => { //修改网页的title
document.title = to.meta.title //前提先在各个组件的meta配置项里配置相应的title名
})
3)独享路由守卫
只对当前路由做出限制
beforeEnter((to,from,next)=>{}) //没有afterEnter
4)组件内路由守卫
进入路由守卫:通过路由规则,进入该组件时被调用
beforeRouterEnter((to,from,next)=>{})
离开路由守卫:通过路由规则,离开该组件时被调用
beforeRouterLeave((to,from,next)=>{})
6.14 hash和history模式
对于一个url来说,hash值就是#及其后面的内容
浏览器路径中从#开始到后面的都是hash值,不会作为路径的一部分发送给服务器
整个路由器有两种工作模式:默认hash。在路由配置文件中添加配置项 mode:‘history’ 切换为history工作模式(路径比较干净)
区别:
hash模式:地址不美观,兼容性好,若将地址通过第三方手机分享,若app校验严格,则地址会被标记为不合法
history模式:地址美观干净,兼容性较差,应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
项目上线:打包生成最纯粹的 .html、.css、.js文件。。终端:npm run build >>> (只打包src和public)打包完成后出现一个dist文件夹,打包好的文件必须放到服务器上进行部署(把html、css、js等静态资源文件放到后端新建static文件夹中,并在server.js中使用app.use(express.static(__dirname + ‘/static’ ))),此时就能通过访问该服务器整个项目了,即项目上线啦!
history模式下若访问服务器,切换了好几个路由之后,路径就会很长,此时一刷新就会出现报错(该路径下没有相关资源),一般后端解决。若是node服务器,可以通过connect-history-api-fallback的中间件解决
七.Vue UI组件库
移动端常用UI组件库:
Vant:https://youzan.github.io/vant
Cube UI :https://didi.github.io/cube-ui
Mint UI : https://mint-ui.github.io
NutUI
PC端常用UI组件库:
Element UI : https://element.eleme.cn
IView UI : https://www.iviewui.com
Element UI按需引入:…
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pcmnqzc4-1676712361587)(C:\Users\17748\AppData\Roaming\Typora\typora-user-images\image-20230218151108501.png)]
**2)全局后置路由守卫** ——初始化时、每次路由切换之后被调用
router.after( (to,from) => { //修改网页的title
document.title = to.meta.title //前提先在各个组件的meta配置项里配置相应的title名
})
**3)独享路由守卫**
只对当前路由做出限制
beforeEnter((to,from,next)=>{}) //没有afterEnter
**4)组件内路由守卫**
进入路由守卫:通过路由规则,进入该组件时被调用
beforeRouterEnter((to,from,next)=>{})
离开路由守卫:通过路由规则,离开该组件时被调用
beforeRouterLeave((to,from,next)=>{})
#### **6.14 hash和history模式**
对于一个url来说,hash值就是#及其后面的内容
浏览器路径中从#开始到后面的都是hash值,不会作为路径的一部分发送给服务器
整个路由器有两种工作模式:默认hash。在路由配置文件中添加配置项 mode:'history' 切换为history工作模式(路径比较干净)
**区别**:
hash模式:地址不美观,兼容性好,若将地址通过第三方手机分享,若app校验严格,则地址会被标记为不合法
history模式:地址美观干净,兼容性较差,应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
**项目上线**:打包生成最纯粹的 .html、.css、.js文件。。终端:npm run build >>> (只打包src和public)打包完成后出现一个dist文件夹,打包好的文件必须放到服务器上进行<u>部署</u>(把html、css、js等静态资源文件放到后端新建static文件夹中,并在server.js中使用app.use(express.static(__dirname + '/static' ))),此时就能通过访问该服务器整个项目了,即项目上线啦!
history模式下若访问服务器,切换了好几个路由之后,路径就会很长,此时一刷新就会出现报错(该路径下没有相关资源),一般后端解决。若是node服务器,可以通过connect-history-api-fallback的中间件解决
### 七.Vue UI组件库
**移动端常用UI组件库:**
Vant:https://youzan.github.io/vant
Cube UI :https://didi.github.io/cube-ui
Mint UI : https://mint-ui.github.io
NutUI
**PC端常用UI组件库**:
Element UI : https://element.eleme.cn
IView UI : https://www.iviewui.com
Element UI按需引入:......
[外链图片转存中...(img-pcmnqzc4-1676712361587)]