下载相关镜像-淘宝镜像:
http://www.npmmirror.com/?spm=a2c6h.14029880.0.0.3ba675d7ayxn4x
相关配置博客:
https://blog.csdn.net/qq_35275233/article/details/87900835?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164455854416780274110187%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164455854416780274110187&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-2-87900835.pc_search_result_cache&utm_term=npm%E6%B7%98%E5%AE%9D%E9%95%9C%E5%83%8F%E5%AE%89%E8%A3%85&spm=1018.2226.3001.4187
一、数据绑定
Vue有两种数据绑定方法:
1、单项绑定(v-bind):数据只能从data流向页面
2、双向绑定(v-model):数据不经能从data流向页面,还可以从页面流向data
备注:
+双向绑定一般都应用在表单类i元素上,如input、select等
+v-model:value可以简写为v-model,因为v-model默认收集的就是value的值
二、data与el的两种写法
1、el有2种写法
(1)new Vue的时候配置el属性
(2)先创建Vue实例,随后再通过vm.$mount('#root')挂载制定el值
2、data有2种写法
(1)对象式
data = {
name:'jack',
age: 18
}·
(2)函数式
data(){
return {
name:'jack',
age:18
}
}
注意:
在使用组件的时候,data必须使用函数式,否则会报错的
3、一个重要原则
由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实列了
三、MVVM模型
1、M:模型(model):data中的数据
2、V:视图(view):模板代码
3、VM:视图模型(ViewModel):Vue实例
观察发现:
1、data中所有的属性,最后都出现在了vm身上
2、vm身上所有的属性及Vue原型身上的所有属性,在Vue模板中都可以直接使用
四、数据代理
1、Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读、写)
2、Vue中数据代理的好处:
更加方便操作data中的数据
3、基本原理
通过Object.defineProperty()把data对象中的所有属性添加到vm上
为每个添加到vm上的属性,都指定一个getter和setter
在getter和setter的内部去操作(读、写)data中对应的属性
五、事件处理
事件的基本使用
1、使用v-on:xxx或者@xxx绑定事件,其中xxx是事件名
2、事件回调需要配置在methods对象中,最终会在vm上
3、methods中配置的函数,不要用箭头函数!否则this指向就是window而不是vm了
4、methods中配置的函数,都是被Vue管理的函数,this的指向是vm或者组件实例对象
5、@click="demo"和@click="demo($event)"效果一样,但是后者可以传参
六、事件修饰符
Vue中的事件修饰符:
1、prevent:阻止默认事件(常用)——比如a的跳转等
2、stop:组织时间冒泡(常用)
3、once:时间只触发一次(常用)
4、capture:使用事件的捕获模式
5、self:只有event.target是当前操作的元素时才触发事件
6、passive:事件的默认行为立即执行,无需等待时间回调执行完毕
七、键盘事件
1、Vue中常用的按键别名:
回车 -- enter
删除 -- delete
退出 -- esc
空格 -- space
换行 -- tab(搭配keydown使用,因为tab本身就会导致光标变换位置)
上 -- up
下 -- down
左 -- left
右 --right
2、Vue未提供别名的按键,可以使用按键原始的key值去绑定,但是要注意转为短横线形式
3、系统修饰符(用法特殊):ctrl、alt、shift、meta
(1)配合keyup使用:按下修饰键的同时,还需要按下其他键,随后释放其他键,事件才会被触发
(2)配合keydown使用:正常触发事件
4、也可以使用keyCode去制定具体按键(不推荐,因为按键编号在不同的设备上可能不同)
5、Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
例如:Vue.congig.keyCodes.huiche = 13
八、计算属性
1、定义:要用的属性不存在,要通过已有属性计算得来
2、原理:底层借助了Object.defineproperty方法提供的getter和setter
3、get函数什么时候执行?
(1)初次读取时会执行一次
(2)当依赖的数据发生变化时会再次调用
4、优势:与methods相比,内部有缓存机制(复用),效率更高,调试更能方便
5、备注:
1、计算属性最终会出现在vm上,直接读取使用即可
2、如果计算属性要被修改,那必须写set函数去响应修改,且set中修改要引起计算时依赖的数据发生改变
九、深度监视
Vue中的watch默认不监测对象内部值的改变(一层)
配置deep:true可以监测对象内部值改变(多层)
备注:
(1)Vue自身可以监测对象内部值的改变,但是Vue提供的watch默认不可以
(2)使用watch时根据数据的具体结构,决定是否采用深度监视
computed和watch之间的区别
1、computed能完成的功能,watch都可以完成
2、watch能完成的功能,computed不一定能够能完成,例如:watch可以进行异步操作
两个重要的小原则:
1、所有被Vue管理的函数,最好写成普通函数,这样this指向的才是vm或者组件实例对象
2、所有不被Vue管理的函数(定时器的回调函数、ajax的回调函数、Promise的回调函数等),最好写成箭头函数,这样
this的指向才是vm或者组件实例对象
十、绑定样式
1、class样式
:class="xxx" xxx可以是字符串、数组、对象
1、字符串写法,适用于样式的类名不确定,需要动态指定
2、数组写法,适用于要绑定的样式个数不确定、名字不确定
3、对象写法,适用于要绑定的样式个数确定、名字也确定,但要动态决定用不用
2、style样式
:style="{fontSize: xxx}"其中xxx是动态值
:style="[a,b]"其中a、b是样式对象
十一、条件渲染
1、v-if
写法:
(1)v-if="表达式"
(2)v-else-if="表达式"
(3)v-else="表达式"
适用于:切换频率较低的场景
特点:不展示DOM元素直接被删除
注意:v-if和v-else-if、v-else一起使用,但是要求结构不能被“打断”
2、v-show
写法:v-show="表达式"
适用于:切换频率较高的场景
特点:不展示的DOM元素不被一处,仅仅是使用样式隐藏起来
3、备注:使用v-if时,元素可能无法获取到,而使用v-show一定可以获取到
十二、列表渲染
v-for指令
1、用于展示列表元素
2、语法:v-for="(item,index) in xxx" :key="yyy"
3、可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
十三、key的作用和原理
面试题:react、vue中的key有什么作用?(key的内部原理)
1、虚拟DOM中的key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】
随后Vue尽心那个【新虚拟DOM】和【旧虚拟DOM】的差异比较,比较规则如下:
2、对比规则:
(1)旧虚拟DOM中找到了与新虚拟DOM相同的key:
若虚拟DOM中的内容没有变化,直接使用之前的真实DOM
若虚拟DOM中的内容变化了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
(2)就虚拟DOM中没有找到与新虚拟DOM中相同的key
创建新的真实DOM,随后渲到页面
3、用index作为key可能会引发的问题
(1)若对数据进行:逆序添加、逆序删除等破环顺序的操作:
会产生没有必要的真实DOM更新----页面效果没有问题,但是效率地下
(2)若结构中还包含输入类的DOM:
会产生错误的DOM更新----页面就会出现问题
4、开发中如何选择key?
(1)最好使用每条数据的唯一标识作为key,比如id、手机号、省份证号、学号等唯一值
(2)如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅仅用于渲染列表展示,用index作为key还是可以的
十四、Vue监测数据的原理
1、Vue会监视data中所有层次的数据
2、如何监测对象中的数据?
通过setter实时监控,且要在new Vue时就传入要监测的数据
(1)对象中后续追加的属性,Vue默认不做响应式处理
(2)如需给后续添加的属性做响应式,可以使用以下API:
Vue.set(target,propertyName/index,value) 或者
vm.$set(target,propertyName/index,value)
3、如何监测数组中的数据?
通过包裹数据更新元素的方法实现,本质就是做两件事:
(1)调用原生对应的方法对数组进行更新
(2)重新解析模板,进而更新页面
4、在Vue中修改数据中的某个元素一定要用如下的方法:
(1)使用这些API:push、pop、shift、unshift、splice、sort、reverse
(2)Vue.set() 或 vm.$set()
注意:Vue.set() 和 vm.$set() 不能给vm或者vm的根数据对象(data)添加属性!!!
十五、收集表单数据
若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值
若:<input type="radio" />,则v-model收集的是value值,且要给标签配置value值
若:<input type="checkbox" />
1、没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
2、配置了input的value属性:
(1)v-model的初始值是非数组,那么收集到的就是checked(勾选 or 未勾选,是布尔值)
(2)v-model的初始值是数组,那么收集的就是value值组成的数组
备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效数字
trim:输入首尾空格过滤
十六、过滤器
定义:对要显示的数据进行特定格式化之后再显示(适用于一些简单逻辑的处理)
语法:
1、注册过滤器:Vue.filter(name,callback) 或者 new Vue({filters:{}})
2、使用过滤器:{{xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"
备注:
1、过滤器也可以接受额外参数、多个过滤器可以串联
2、过滤器本质就是函数,返回一个新的对应数据,并没有改变原本的数据
十七、目前学过的内置指令
v-bind:单项绑定解析表达式,可以简写为:xxx
v-model:双向数据绑定
v-for:遍历数组、对象、字符串等
v-on:绑定事件监听,可以简写为@
v-if:条件渲染(动态控制节点是否存在)
v-else:条件渲染(动态控制节点是否存在)
v-show:条件渲染(动态控制节点是否展示)
v-text指令:
1、作用:向其所在的节点中渲染文本内容
2、与插值语法的区别:v-text会替换掉节点中的内容,{{xxx}}则不会
v-html指令:
1、作用:向指定节点中渲染包含html结构的内容
2、与插值语法的区别:
(1)v-html会替换掉节点中所有内容,插值语法不会
(2)v-html可以识别html结构
3、严重注意:v-html有安全性问题!!!
(1)在网页上动态渲染任意的HTML是非常危险的,容易导致xss攻击
(2)一定要在可信的内容上使用v-html,永远不要在用户提交的内容上!!!
v-cloak指令(没有值):
1、本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
2、使用css配合v-cloak可以解决网速慢时页面展示{{xxx}}问题
css里写
[v-cloak]{
display:none;
}
v-once指令(没有值):
1、v-once所在节点在初次动态渲染之后,就视为静态内容了
2、以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
v-pre指令:
1、跳过其所在节点的编译过程
2、可以利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
十八、自定义指令
1、定义语法
(1)局部指令
new Vue({
directives:{指令名:配置对象}
})
或
new Vue({
directives:{指令名:回调函数}
})
(2)全局指令
Vue.directice(指令名 ,配置对象) 或 Vue.directice(指令名 ,回调函数)
2、配置对象中常用的三个回调
(1)bind:指令与元素成功绑定时调用
(2)inserted:指令所在元素被插入页面时调用
(3)update:指令所在模板结构被重新解析时调用
3、备注:
(1)指令定义的时候不加v-,但使用的时候要加v-
(2)指令名如果是多个单词组成,要用短横线相连(kebab-case)命名方式,不要使用驼峰命名法;且在写配置的时候要用引号包裹
十九、生命周期
1、又名:生命周期回调函数、生命周期函数\生命周期钩子
2、是什么:Vue在关键的时刻帮我们调用的一些特殊名称的函数
3、生命周期函数的名字不可以更改,但是函数的具体内容是根据程序员的需要编写的
4、生命周期函数中的this指向是vm 或者 组件实例对象
常用的生命周期狗子:
1、mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
2、beforeDestroy:清除定时器、解绑自定义时间、取消订阅消息等【收尾工作】
关于销毁Vue实例:
1、销毁后借助Vue开发者工具看不到任何信息
2、销毁后自定义事件会消失,但是原生DOM时间依然有效
3、一般不会在beforeDestroy操作数据,因为即将销毁,也不会在触发更新流程了
二十、组件
组件定义—— 实现应用中局部功能的代码和资源的集合
模块化:当应用中的js都以模块来编写的,那这个应用就是一个模块化应用
组件化:当应用中的功能都是多组件方式来编写的,那这个应用就是一个组件化的应用
Vue中使用组件的三大步骤:
1、定义组件
2、注册组件
3、使用组件(写组件标签)
1、如何定义一个组件?
使用Vue.extend(options)创建,其中options与new Vue(options)时传入的那个options几乎一样,但是区别如下:
(1)el不要写,为什么?—— 最终所有的组件都要经过一个vm管理,由vm中的el决定服务哪个容器
(2)data必须写成函数,为什么?—— 避免组件复用时,数据存在引用关系
备注:使用template可以配置组件结构
2、如何注册组件?
(1)局部注册:靠new Vue时传入components选项
(2)全局注册:靠Vue.component('组件名',组件)
3、编写组件标签:
<school></school>
几个注意点
1、关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kabab-case命名);my-shcool(使用时需要用引号包裹)
第二种写法(所有单词首字母都大写):MySchool(需要使用Vue脚手架支持)
备注:
(1)组件名尽可能回避HTML中已有的元素名称,例如h2、H2都不行
(2)可以使用name配置项制定组件在开发者工具中呈现的名字
2、关于组件标签:
第一种写法:<school></school>
第二种写法:<school/>
备注:不适用脚手架时,<school/>会导致后续组件不能渲染
3、一个简写方式
const school = Vue.extend(options) 可以简写为: const school = options
关于VueComponent
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
一个重要的内置关系
1、一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
2、为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法
二十一、Vue脚手架
关于不同版本的Vue:
1、vue.js与vue.runtime.xxx.js的区别
(1)vue.js是完整版的Vue,包含:核心功能+模板解析器
(2)vue.runtime.xxx.js是运行版的Vue,只包含:核心功能,没有模板解析器
2、因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容
vue.config.js配置文件
1、使用vue.inspect > output.js可以查看Vue脚手架的默认配(但是只能看,改了也没有用)
2、使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
二十二、ref属性
1、被用来给元素或者子组件注册引用i西南西(id的替代者)
2、应用在html标签上获取到的是真实的DOM元素,应用在组件标签上获取的是组件实例对象(vc)
3、使用方式
打标识:<h1 ref='xxx'>...</h1> 或者 <School ref='xxx'></School>
获取:this.$refs.xxx
二十三、配置项props
功能:让组件接受外部传过来的数据
(1)传递数据:
<Demo name="xxx" :age="18">
<!-- 其中对age前面加冒号,是为了v-bind:这样收集的时候就会当js表达式解析,读到数字18了 -->
(2)接收数据:
第一种方式(只接收):
props:['name','age']
第二种方式(限制类型):
props:{
name:String,
age:Number
}
第三种方式9(限制类型、限制必要性、指定默认值):
props:{
name:{
type:String, //类型
required:true, //必要性
},
age:{
type:Number,
default:18 //默认值
}
}
备注:props是只读的,Vue底层会监测你对props的修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容给data一份,然后去修改data中的数据
二十四、mixin(混入)
功能:可以把多个组件共用难过的配置提取成一个混入对象
使用方式:
第一步定义混合,例如:
{
data(){...},
methods:{...}
...
}
第二步使用混入,例如:
(1)全局混入:Vue.mixin(xxx)
(2)局部混入:mixins:['xxx']
二十五、插件plugins
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
定义插件:
对象.install = function(Vue,options) {
//1、添加全局过滤器
Vue.filter(...)
//2、添加全局指令
Vue.directive(...)
//3、配置全局混入(合)
Vue.mixin(...)
//4、添加实例方法
Vue.prototype.$myMethod = function(){...}
Vue.prototype.$myProperty = xxx
}
使用插件:Vue.use()
二十六、scoped样式
作用:让样式在局部生效,防止冲突
写法:<style scoped>
二十七、TodoList案例
第一版TodoList
1、组件化编码流程:
(1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突
(2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
-一个组件在用:放在组件自身即可
-一些组件再用:放在他们共同的父组件上(状态提升)
(3)实现交互:从绑定时间开始
2、props适用于:
(1)父组件 ==> 子组件通信
(2)子组件 ==> 父组件通信(要求父组件先给子组件一个函数,然后通过参数传值
3、使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
4、props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但是不建议这样做
二十八、WebStorage
1、存储内容大小一般支持5M左右(不同浏览器可能还不一样)
2、浏览器通过Window.sessionStorage 和 Window.localStorage属性来实现本地存储机制
3、相关API:
1、xxxStorage.setItem('key','value')
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
2、xxxStorage.getItem('key')
该方法接受一个键名作为参数,返回键名对应的值
3、xxxStorage.removeItem('key')
该方法接受一个键名作为参数,并把该键名从存储中删除
4、xxxStorage.clear()
该方法会清空存储中的所有数据
4、备注:
(1)SessionStorage存储的内容会随着浏览器窗口关闭而消失
(2)LocalStorage存储的内容,需要手动清除才会消失
(3)xxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null
(4)JSON.parse(null)的结果依然是null
二十九、组件的自定义事件
1、一种组件间通信的方式,适用于:子组件 ==> 父组件
2、使用场景:A是父组件,B是子组件,B想给A传递数据,那么就在A中给B绑定自定义事件(实践的回调一定留在A中)
3、绑定自定义时间:
(1)第一种方式,在父组件中 <Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test">
(2)第二种方式,在父组件中:
<demo ref="demo>
------
mounted(){
this.$refs.demo.$on('atguigu',this.test)
}
(3)若想让自定义时间只触发一次,可以使用once修饰符,或将$on替换成$once
4、触发自定义事件:this.$emit('atguigu',数据)
5、解绑自定义时间:this.$off('atguigu')
6、组件上也可以绑定原生DOM事件,需要使用native修饰符
7、注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么使用箭头函数,否则this的指向会有问题!!!
三十、全局事件总线
1、一种组件间通信的方式,适用于任意组件间通信
2、安装全局事件总线
new Vue({
...
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
...
})
3、使用事件总线:
(1)接收数据:A组件想接收数据,则在A组件中给KaTeX parse error: Expected '}', got 'EOF' at end of input: …ed() { this.bus.KaTeX parse error: Expected 'EOF', got '}' at position 23: …',this.demo) }̲ } (2)提供数据:th…bus.
e
m
i
t
(
′
x
x
x
′
,
数
据
)
4
、
最
好
在
b
e
f
o
r
e
D
e
s
t
r
o
y
钩
子
中
,
用
emit('xxx',数据) 4、最好在beforeDestroy钩子中,用
emit(′xxx′,数据)4、最好在beforeDestroy钩子中,用off去解绑当前组件所用到的事件
三十一、消息订阅与发布(pubsub)
1、一种组件间通信的方式,适用于任意组件间通信
2、使用步骤:
(1)安装pubsub:npm i pubsub-js
(2)引入:import pubsub from ‘pubsub-js’
3、接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
methods(){ demo(data){...} } ... mounted(){ this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 }
4、提供数据:pubsub.publish(‘xxx’,数据)
5、最好在beforeDestroy钩子中,用Pubsub.unscribe(pid)去取消订阅
三十一、消息订阅与发布(pubsub)
1、语法:this.$nextTick(回调函数)
2、作用:在一下次DOM更新结束后执行其指定回调
3、什么时候用:当改变数据后,要基于更新以后的DOM进行某些操作时,要在nextTick所指定的回调函数中执行
三十二、vue脚手架配置代理
方法一
在vue.config中添加如下配置
devServer:{
proxy:'http:localhost:5000'
}
说明:
1、优点:配置简单,请求资源时直接发给前端相同端口(8080)即可
2、缺点:不能配置多个代理,不能灵活的控制请求是否走代理
3、工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先匹配前端资源)
方法二
编写vue.config.js配置具体代理规则:
module.exports = {
deServer: {
proxy: {
'/api1':{ //匹配所有以'/api1'开头的请求路径
target:'http:localhost:5000', //代理目标的基础路径
changeOrigin:true,
pathRewrite:{'^/api1',''}
},
'/api2':{//匹配所有以'/api2'开头的请求路径
target:'http:localhost:5001',
changeOrigin:true,
pathRewrite:{'^/api2',''}
}
}
}
}
changeOrigin设置为true时,服务器收到的请求头host为:localhost:5000
changeOrigin设置为true时,服务器收到的请求头host为:localhost:8080
changeOrigin默认为true
说明:
1、优点:可以配置多个代理,且可以灵活的控制请求是否走代理
2、缺点:配置略微繁琐,请求资源的时候必须加前缀
三十三、插槽
1、作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信方式
2、分类:默认插槽、具名插槽、作用域插槽
3、使用方式:
(1)默认插槽
父组件中:
<Category>
<div>html....</div>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot>插槽默认内容。。。</slot>
</div>
</template>
(2)具名插槽
父组件中:
<Category>
<template slot='center'>
<div>html结构1</div>
</template>
<template v-slot:'footer'>
<div>html结构2</div>
</template>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容。。。</slot>
<slot name="footer">插槽默认内容。。。</slot>
</div>
</template>
(3)作用域插槽
1、理解:理解:数据在组件自身,但根据数据生成的结构需要组件的使用者来决定(games数据在Category组件中,但是使用是在数据所遍历出来的结构有App组件决定)
父组件中:
<Category>
<template scope="scopeData">
<!-- 生成的是ul列表 -->
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Category>
<Category>
<template scope="scopeData">
<!-- 生成的是ol列表 -->
<ol>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ol>
</template>
</Category>
子组件中:
<template scope="scopeData">
<slot :games="games"></slot>
</template>
三十四、Vuex使用
1、组件中读取vuex中的数据:$store.state.xxx
2、组件中修改vuex的数据:$store.dispatch('actions中的方法名','数据') 或者 $store.commit('mutations中的方法名','数据')
备注:如果没有网络请求或者其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit
三十五、getters使用
1、概念:当state中的数据需要经过加工后再使用时,采用getters加工
2、在store.js中追加getters配置
const getters = {
bigSum(state){
return state.sum*10
}
}
.......
创建并暴露store
export default new Vuex.Store({
...
getters
})
3、在组件中读取数据:$store.getters.bigSum
三十六、模块化+命名空间
1、目的:让代码更好维护,让多种数据分类更加明确
2、修改store.js
```
const CountAbout = {
namespaced:ture, //开启命名空间
state:{
x:1
},
mutations:{...},
actions:{...},
getters:{
bigSum(state){
return state.sum*10
}
}
}
const personAbout = {
namespaced:true,
state:{...},
mutations:{...},
actions:{...}
}
const store = new Vuex.Store({
modules:{
countAbout,
personAbout
}
})
```
3、开启命名空间后,组件中读取state数据:
//方式一,自己直接读取
this.$store.state.personAbout.list
//方式二、借助mapState读取
...mapState('personAbout',['name','age'])
4、开启命名空间后,组件中读取getters数据
//方式一,自己直接读取
this.$store.gettters['personAbout/firstPersonName']
//方式二、借助mapGetters读取
...mapGetters('personAbout',['firstPersonName'])
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:'JIA',decrement:'JIAN'})
三十七、路由
1、安装vue-router,命令:npm i vue-router
2、应用插件:Vue.use(VueRouter)
3、编写router配置项
```
//引入VueRouter
import VueRouter from 'vue-router'
//引入路由组件
import About from '../components/About'
import Home from '../components/Home'
//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'home',
component:Home
}
]
})
//暴露router
export default router
```
4、实现切换(active-class可配置高亮样式)
<router-link active-class='active' to='/about'>About</router-link>
5、指定展示位置
<router-view></router-view>
2、几个需要注意的点
1、路由组件通常存放在pages文件夹,一般组件通常存放在components中
2、通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
3、每个组件都有自己的$route属性,里面存储着自己的路由信息
4、整个应用只有一个router,可以通过组件的$router属性获取到
3、多级路由
1、配置路由规则,使用children配置
```
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[ //通过children设置子路由
{
path:'news', //此处一定不要写斜杠/news
component:News
}
]
}
]
```
2、跳转(要写完整路径)
<router-link to='/home/news'>News</router-link>
4、路由的query参数
1、传递参数
```
跳转
<!-- 跳转并携带query参数,to的对象写法 -->
<router-link
:to="{
path:'home/message/detail',
query:{
id:item.id,
title:item.title
}
}"
>跳转</router-link>
```
2、接收参数
$route.query.id
$route.$query.title
5、命名路由
在配置路由的时候加上name属性即可
6、采用params
1、配置路由
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[ //通过children设置子路由
{
name:'xinwen',
path:'news', //此处一定不要写斜杠/news
component:News
}
]
}
]
```
2、传递参数
<!-- 跳转并懈怠query参数,to的字符串写法 -->
```
<router-link :to="`/home/message/detail/${item.id}/${item.title}`">跳转</router-link>
<!-- 跳转并携带query参数,to的对象写法 -->
<router-link
:to="{
name:'xinwen',
query:{
id:item.id,
title:item.title
}
}"
>跳转</router-link>
```
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置
**7、路由的props配置**
作用:让路由组件更方便的收到参数
{
name:‘xinwen’,
path:‘news’, //此处一定不要写斜杠/news
component:News,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给News组件
//props:{a:900}
//第二种,props为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给News
//props:true
//第三中,props值为函数,该函数返回的对象中每一组都会通过props传给News
props(route){
return {
id:route.query.id,
title:route.query.title
}
}
}
**8、<router-link>的replace属性**
1、作用:控制路由跳转时操作浏览器历史记录的模式
2、浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,路由跳转的时候默认为push
3、如何开启replace模式:<router-link replace ...></router-link>
**9、编程式路由导航**
1、组用:不借助<link-router>实现路由跳转,让路由跳转更加灵活
2、具体编码:
```
//$router的两个API
this.$router.push({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.replace({
name:'xiangqinhg',
params:{
id:xxx,
title:xxx
}
})
```
3、其他API
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //参数为正就是前进,参数为负就是后退
**10、缓存路由组件**
1、作用:让不被展示的路由组件保持挂载,不被销毁
2、具体编码
```
<keep-alive include="News"> //此处的News是只想要保持不被销毁的组件名
<router-view></router-view>
<keep-alive>
```
**11、两个新的生命周期钩子**
1、作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态
2、具体名字:
(1)activated路由组件被激活时触发
(2)deactived路由组件失活时触发
**12、路由守卫**
1、作用:对路由进行权限控制
2、分类:全局守卫、独享守卫、组件内守卫
3、全局守卫:
```
//全局前置守卫,初始化时执行,每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
}
}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'
}
})
```
4、独享守卫
独享守卫只有前置路由守卫,没有后置路由守卫
```
beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
}
}else{
next()
}
}
```
5、组件内守卫
```
//进入守卫,通过路由规则,进入该组件时被调用
beforeRouteEnter(to,from,next){
}
//离开守卫,通过路由规则,离开组件时被调用
beforeRouteLeave(to,from,next){
}
**13、路由器的两种工作模式**
1、对于一个url来说,什么是hash值?——#及其后面的内容都是hash值
2、hash值不会包含在HTTP请求中,即:hash值不会带给服务器
3、hash模式:
(1)地址中永远带着#号,不美观
(2)若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合格
(3)兼容性较好
4、history模式:
(1)地址干净,美观
(2)兼容性和hash模式相比略差
(3)应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
### 三十八、Vue3新特性
## 1、拉开序幕的setup
1、理解:Vue3中的一个新配置项,值为一个函数
2、setup是所有Composition AIP(组合API)的“表演舞台”
3、组件中所用到的:数据、方法等等,均要配置在setup中
4、setup函数的两种返回值:
(1)若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用(重点关注)
(2)若返回一个渲染函数,则可以自定义渲染内容(了解,因为渲染内容会直接覆盖模板里的内容)
5、注意点
(1)尽量不要与Vue2的配置混用
Vue2配置(data、method、computed等)中可以访问到setup中的属性、方法
但在setup中不能访问到Vue2的相关配置
如有重名,以setup优先
## 2、ref函数
1、作用:定义一个响应式的数据
2、语法:const xxx = ref(initValue)
创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
JS中操作数据:xxx.value
模板中读取数据不需要.value,直接采用<div>{{xxx}}</div>
3、备注:
接受的数据可以是:基本类型,也可以是对象类型
基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的
对象类型的数据:内部“求助”了vue3中的一个新函数——reactive函数
## 3、reactive函数
1、作用:定义一个对象类型的响应式数据(基本类型别用它,用ref函数)
2、语法:const 代理对象 = reactive(被代理对象)接受一个对象(或数组),返回一个代理对象(proxy对象)
3、reactive定义的响应式数据是深层次的
4、内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的
## 4、Vue3的响应式
**实现原理:**
1、通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等
2、通过Reflect(反射):对被代理对象的属性进行操作
3、MDN文档中描述的Proxy与Refelct
```
new Proxy(data,{
//拦截读取属性值
get(target,prop){
return Reflect.get(target,prop)
},
//拦截设置属性值或者添加新属性
set(target,prop,value){
return Reflect.set(target,prop,value)
},
//拦截删除属性
deleteProperty(target,prop){
return Reflect.deleteProperty(target,prop)
}
})
proxy.name = 'tom
```
## 5、reactive对比ref
1、从定义数据角度对比:
(1)ref用来定义:基本类型数据
(2)reactive用来定义:对象(或数组)类型数据
备注:ref也可以用来定义对象(或数组)类型数据,他内部会自动通过reactive转为代理对象
2、从原理角度对比:
(1)ref通过Object.defineProperty()和get和set来实现响应式(数据劫持)
(2)reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部数据
3、从使用角度对比:
(1)ref定义的数据:操作数需要.value,读取数据的模板中直接读取不需要.value
(2)reactive定义的数据:操作数据与读取数据均不需要.value
## 6、setup的两个注意点
1、setup执行的时机
在beforeCreate之前执行一次,this是undefined
2、setup的参数
(1)props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
(2)context:上下文对象
attrs:值为对象,包含:组件外部传递过来,但没有在props中声明的属性,相当于this.$attrs
slots:收到插槽内容,相当于this.$slots
emit:分发自定义事件的函数,相当于this.$emit
## 7、计算属性与监视
**1、computed函数**
与Vue2中的computed的配置功能一致
写法:
```
import {computed} from 'vue'
setup(){
...
//计算属性简写
let fullName = computed(()=>{
return person.firstName + '-' + person.LastName
})
//计算属性完整版
let fullName = computed({
get(){
return person.firstName + '-' + person.LastName
},
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
}
```
**2、watch函数**
与vue2中的watch配置一致
有两个小坑:
(1)监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失败)
(2)监视reactive定义的响应式数据中的某个属性时:deep配置有效
```
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
},{immediate:true})
//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
})
//情况三:监视reactive定义的响应式数据
//若watch监视的是reactive定义的响应式数据,则无法获得oldValue!!!
//若watch监视的是reactive定义的响应式数据,则强制开启深度监视
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效
//情况四:监视reactive定义的响应式数据中的某个属性
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true}) //此处的deep配置是奏效的
//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.job,()=>person.age],(newValue,oldValue)=>{
console.log('person的或者age变化了',newValue,oldValue)
},{immediate:true,deep:true}) //此处的deep配置是奏效的
```
**3、watchEffect函数**
(1)watch的套路是:既要知名监视的属性,也要知名监视的回调
(2)watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性
(3)watchEffect有点像computed:
但computed注重的是计算出来的值(回调函数的返回值),所以必须要写返回值
而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
```
//watchEffect所制定的回调函数中用到的数据只要发生变化,则直接重新执行回调
watchEffect(()=>{
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回调函数执行了')
})
## 9、自定义hook函数
1、什么是hook?——本质是一个函数,把setup函数中的composition API进行了封装
2、类似于vue2中的mixin
3、自定义hook的优势,复用代码,让setup中的逻辑更清楚易懂
## 10、toRef
1、作用:创建一个ref对象,其value值指向另一个对象中的某个属性值
2、语法:const name = toRef(person,'name')
3、应用:要将响应式对象中的某个属性单独提供给外面使用时
4、扩展:toRefs与toRef功能一致,但可以批量创建多个ref对象,语法:toRefs(person)——只会把person最外层的那部分属性转变为ref对象,嵌套在深层的不会转化
## 11、其他组合API
**1、shallowReactive与shallowRef**
(1)shallowReactive:只处理对象最外层属性的响应式(浅响应式)
(2)shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
(3)什么时候使用:
如果有一个兑现那个数据,结构比较深,但变化时只是外层属性变化===>shallowReactive
如果有一个对象数据,后续功能不会修改对象中的属性,而是生新的对象来替换===>shallowRef
**2、readonly与shallowReadonly**
(1)readonly:让一个响应式数据变为只读(深只读)
(2)shallowReadonly:让一个响应式数据变为只读(浅只读,还可以改变对象里面的内容)
(3)应用场景:不希望数据被修改时
**3、toRaw与markRaw**
(1)toRaw:
作用:将一个由reactive生成的响应式对象转为普通对象
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
(2)markRaw:
作用:标记一个对象,使其永远不会再成为响应式对象
当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
**4、customRef**
作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制
看官方那个输入防抖案例
**5、provide与inject**
作用:实现组与后代组件间的通信
套路:父组件有一个provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据
具体写法:
(1)组组件中:
```
setup(){
...
let car = reactive({name:'奔驰',price:'40W'})
provide('car',car)
...
}
```
(2)后代组件中
```
setup(props,context){
...
const car = inject('car')
return {car}
...
}
```
**6、响应式数据的判断**
(1)isRef:检查一个值是否为一个ref对象
(2)isReacitve:检查一个对象是否是由reactive创建的响应式代理
(3)isReadonly:检查一个对象是否是由readonly创建的只读代理
(4)isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理
**7、新组件——Teleport**
什么是Teleport?——Teleport是一种能够将我们的组件html结构移动到指定位置的技术
```
<teleport to="移动位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow=false">关闭弹窗</button>
</div>
</div>
</teleport>
```
**7、新组件——Suspense**
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
使用步骤:
(1)异步引入组件
```
imoirt {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
```
(2)使用Suspense包裹组件,并配置好default与fallback
```
<template>
<div class="app">
<h3>我是app组件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加载中.......</h3>
</template>
</Suspense>
</div>
</template>