系列文章目录
- 之前没跟对up主,vue基础没打好;跟着尚硅谷从头第二次学Vue,收获了很多并且记下来
- 万字笔记,平常开发用的多的都在这了
如果有出错的地方请多多指教!
文章目录
- 系列文章目录
- vue概述
- Vue的引入(非Vue-cli的原生版)
- 第一个Vue代码
- MVVM模型
- vue中的数据代理和数据劫持
- Vue.set方法
- 插值语法
- 指令语法
- 自定义指令
- 计算属性
- 监听函数
- 绑定class
- 绑定style
- 生命周期函数
- 自定义事件
- 全局事件总线
- 消息发布与订阅
- data的两种写法
- $nextTick
- Vue集成第三方动画库animate.css
- 组件
- 插槽slot
- Vue是如何实现全局配置的
- Vue-cli 中Vue到底是怎么跑起来的
- ref属性
- props:往组件中传参
- mixin
- 之后就是Vue3部分//
- Vue3 两种创建方式
- Vue3使用Vuex
- vue3使用路由router
- setup
- setup中的两个参数
- ref和reactive
- Vue3中计算属性
- Vue3中的监听函数watch
- watchEffect
- 自定义hook函数
- toRef和toRefs
- provide&inject
vue概述
vue是什么
- 是一种
web前端框架
- 官方:vue是动态构建用户界面的渐进式框架
- 作者:尤雨溪/鱿鱼须
vue的特点
- 组件化:提高代码的复用,可以更好地维护
- 声明式编码:**不用直接操作DOM,**提高开发效率
- 虚拟DOM:当数据变化的时候,可以减少操作DOM,提高效率
Vue的引入(非Vue-cli的原生版)
-
在官网下载vue的js
-
在html中用src的方式引入本地的vue.js
第一个Vue代码
1.创建
-
先创建一个Vue实例
-
Vue实例的构造需要
配置对象
-
往配置对象中填写,data,el等等都是配置对象的属性
-
Vue实例需要和html中的
容器产生关系
:el:“id选择器” -
数据的绑定和引用:
data中的数据可以通过{{}}在html中引用
-
整体例子如下
2. 注意事项
- div和vue实例是一一对应的,一个div只能对应一个vue,
即使是用css选择器选择到了多个div,也只接管第一个div
- {{}}中填的是
js表达式
,只要是能生成一个值,可以放在任何需要值得地方,就叫js表达式
,比如- 变量a
- a+b
- fun1() 等等等等都称为js表达式
- data中数据改变了,页面中用到data中的地方也会改变
MVVM模型
- M:model=>对应data中的数据
- V:view => 模板
- VM:ViewModel (视图模型) => vue实例对象
- 人话:将
数据和展示页面
之间用vue
搭建一个桥梁,vue来将数据绑定到视图层,同时监听视图层的变化,反映给数据
vue中的数据代理和数据劫持
1. Object.DefineProperty实现数据代理
(1) 介绍和应用场景
- 涉及双向绑定的原理
- 作用:可以往一个对象中添加属性,
根据传参的配置对象,可以让这个属性和普通对象属性有些不同
- 应用:当
对象中的属性值来自对象外部的变量时
,更改变量的值,对象中的值也想同步更改
这样的age,如果outerAge改为20,对象中age还是19 - 如果想外部的改变可以同步到内部,就需要用到Object.DefineProperty
(2) 使用方法
- Object.defineProperty(对象,要添加得键,{配置对象})
- 配置对象的主要属性有:
-
value:18 //添加得属性得value
-
enumerable:true //是否可以被枚举 for in是否可以获取到 默认:false
-
writeable:true //value是否可以被修改 默认:false
-
configurable:true //是否可以被删除 默认:false
-
get(){} //当这个属性被获取的时候调用
-
set(){} //当这个属性被修改的时候调用
-
Object.defineProperty(对象,要添加得键,{配置对象})
let person={
name:'szk'
}
Object.defineProperty(person,"age",{
value:18 //添加得属性得value
enumerable:true //是否可以被枚举 for in是否可以获取到 默认:false
writeable:true //value是否可以被修改 默认:false
configurable:true //是否可以被删除 默认:false
get(){} //当这个属性被获取的时候调用
set(){} //当这个属性被修改的时候调用
})
(3) 用这个使属性和外部数据绑定
- get set在调用的时候可以来同步内外数据
(4) 这样就实现了数据代理
修改代理的(调用setter),就可以修改到真正的,就是数据代理
2. data 和 _data
- 看vm可以看到有一个属性叫做
_data
,它和vue实例中的data是同一个东西,一毛一样
3. vue中的数据代理
- vue将_data中的所有数据属性
通过Object.defineProperty添加到vm实例上
,并且提供了getter和setter方法
- 于是通过vm直接获取数据的时候就
调用getter,获取_data中的值
,当修改的时候调用setter修改_data中的值
- 举个例子画个图
那数据代理有什么用呢?
- 既然vm上挂的属性就是_data中的数据代理,那么
{{vm._data.name}}和{{name}}是等价的,{{vm_data.name='szk2'}}和{{name='szk2'}}也是等价的
- 所以就是为了写代码的方便,在{{}}直接写数据,或者直接修改就能操作到_data中
4. vue中的数据劫持
- 数据劫持就是将vue代码里我们写的
data加工了一下,让每个属性有了getter和setter
- 这就可以实现
数据变了,改变页面
- 先看看我们写的data和加工之后的data有什么区别
- 实际上vue通过
监听者observer来监听data中的数据,这个getter和setter就是监听者里面的方法
- getter就是监听者获取data中数据的,
setter则是监听当数据发生变化的时候执行操作的
当修改属性的时候,setter被调用,在setter方法中就会让订阅者执行重新解析模板的操作,从而改变了页面
5. 将两者结合总体流程
- 结合起来看,当数据改变时,页面也跟着改变就是这个流程
Vue.set方法
- 用来向已经运行中的vm._data中
添加属性
—>就是在运行时添加响应式数据
- 也可以用来
更改响应式的数组数据
- 如果直接添加属性,添加的属性是
非响应式的,没有getter和setter
- 用set方法添加的是响应式的
1. 往对象中添加属性
看看直接添加属性会怎样
- 案例中给“学生留了位置”,但没有在data中定义数据给student,想之后再添加
- 之后在控制台添加一下试试
使用set方法添加属性试试
Vue.set(要添加的到的对象,'添加的字段',‘添加的值’)
- 或者
vm.$set(要添加的到的对象,'添加的字段',‘添加的值’)
注意点
- set方法只能针对
data中对象的属性的添加,而不能直接在data上添加
2. 用set修改数组属性
Vue.set(要更改的数组对象,要修改的下标,‘修改的值’)
- 响应式数组不能直接用
下标进行修改
,只能使用vue中封装的操作数组的几种方法
- 除此之外,使用set也可以做到
插值语法
- 被vue接管的div叫做
模板
,在之中会有相应的语法 - 就是
{{}}
,里面写的是js表达式
标签体中使用插值语法
- 之所以{{data}}里填data的东西可以显示,是因为{{}}中的是vue实例里面的东西
指令语法
- 都是
v-xxx
开头
(1) v-bind
- v-bind用来绑定标签中的属性,简写是
:
- 语法:
v-bind:要绑定的属性名='js表达式'
- 比如,想绑定div中的herf属性,可以这样写
- v-bind 是
单项数据绑定
:data中的值改变可以影响页面,但页面的值改变了不会影响data
(2) v-model
- 作用和v-bind类似
- v-model 实现
数据双向绑定
:页面值改变了会影响data中的值
- v-model只支持部分标签,
一般应用在表单元素中
- 默认收集的就是value,可以简写成
v-model = 'js表达式'
在实际收集表单数据的时候需要注意几个问题
- 对于
radio
这种单选框,一定要给其指定value
,不然绑定的数据就是true/false而不是需要的值 - 对于
checkbox
这种多选的,接收的时候要用数组接收
,否则接收的也是true/false - button最终会触发
form的submit
,可以通过@submit.prevent
来组织表单提交后刷新页面 - 对于
需要传值为数字的
,可以使用后缀v-model.number
,这样收集到的就会是数字
(3) v-on
- 作用是:
绑定事件
,简写是@ - 用来将事件(比如鼠标点击,得到焦点等等),与函数绑定在一起
- vue2中的函数在配置对象中写在
methods中
- 例子需求:点击按钮后,alert一个信息
注意:- 默认传一个
event参数
- 如果传自己的参数,又想用到event,需要用
$event
在传参表中声明,否则不会有event参数传入 - 没有参数可以省略()
- 简写是 @事件名=‘函数名’
- 默认传一个
事件修饰符
- Vue中的事件修饰符:
- prevent :组织默认事件
- stop:组织事件冒泡
- once:事件只触发一次
- capture:使用时间的捕获模式
- self:只有event.target是当前操作的元素才触发事件
- passive:事件的默认行为立即执行,无需等待时间回调执行完毕
- 使用方法:直接
.
出来就可以
键盘事件
- 使用方法
@keyxxx='函数名'
比如:@keyup=‘handleKeyup’ - 提供了几种常用键的快捷方法,直接
.
出来
- 例子:
(4) v-text
- 用来
向其所在标签插入文字
,而且会覆盖掉节点中的内容 - 非常简单
(5) v-html
- 就是支持
html解析的v-text
- v-html有安全性问题,动态渲染任意的html是很危险的,容易遭到黑客攻击,
要慎用
(6) v-cloak
- 解决网速过慢时,
html结构出来了,但vue还没来,导致网站只显示一堆{{}}这样的结构
- 加入v-cloak 配合css的属性选择器,就可以做到
等模板全部渲染好之后才展示页面
- 使用方法:在标签中加入
v-cloak 只有属性没有值
(7) v-once
- v-once所在节点初始化渲染后就变成
静态的了
- 可以让
初次动态渲染的东西一直停留在页面上
(8) v-pre
- 让
vue跳过页面解析过程
- 给不需要使用到vue语法的地方加,可以提高效率,加快了编译
(9) 条件渲染
1. v-show
- 可以显示/隐藏内容
v-show="布尔值表达式"
- 表达式为true则显示,false为隐藏
show的隐藏是将元素隐藏了,但结构还在
2. v-if
- 结构:
v-if:="布尔值表达式"
//后两者可选,可以不写
v-else-if:="布尔值表达式"
v-else:="布尔值表达式"
- 作用也是显示/隐藏内容,和v-show的不同在于
v-if实际渲染的时候如果是false是将整个元素结构都删除了,而show只是隐藏了
- 注意:v-if 和 v-else-if 之间不能空有别的元素,必须是
连贯的
(10) 列表渲染 v-for
- 用来在模板中渲染可迭代对象
- 注意:
必须要执行key
1. 使用方法
- 最好是迭代对象中有id属性,作为key,作为每次循环出来的“身份证号”
- 如果没有的话,也可以让遍历产生的
index作为key
,在一些特殊情况下会出错,下文会讲
// 最好是迭代对象中有id属性,作为key,作为每次循环出来的“身份证号”
<div v-for='item in data中要迭代对象' :key='item.id'>
//用自己生成的index作为key的话这样写
<div v-for='(item,index) in data中要迭代的对象' :key='index'>
- 注意:
- 数组对象遍历出一个个对象
- 对象中遍历出的是每一对
键值对
- 如果要获取index的话,index一定是括号中的第二位 (value,index)
一定要写key,不然会降低效率,甚至会出错
2. 为什么要用key
- vue在
虚拟DOM时对于更新了的内容会进行对比算法
,key这时就会被作为对比的依据
- 如果使用index作为key时,如果
插入数据的顺序破坏了原本的index(此处就是往头的地方插入了数据)
,则新数据会拥有旧数据的index
- vue会去对比新旧DOM中
有相同key的地方
,有新的内容就会代替掉,一样的地方就会复用
- 此处虽然input中内容其实不同,但在
DOM中vue认为他们是一样的
- 最终导致新插入的数据的
input中的值其实是老数据中index为0的input中的值
- 而如果每个
key都是唯一的话
,就不会进行上述的对比算法
,只有新旧DOM中都有key为同一个值时才会对比 - 这样就避免了上述的错误
- 具体可以看B站尚硅谷这一篇讲解很详细
3.列表过滤
- 为了在前端实现
模糊查询
- 实现逻辑:对输入的值进行
双向绑定
----->在计算属性中通过数组的filter函数过滤出符合关键字的数组对象——>渲染过滤过的数组对象
自定义指令
- 指令如v-bind 那些vue内置的叫内置指令,自定义指令就是自己定义的v-xx的指令
- 在自定义指令中,需要
亲自操作dom元素
1. 直接写函数的简单写法
- 指令可以想象成跟
计算属性类似的东西,可以写成一个函数
- 指令的名字在使用时需要加上
v-指令名字
,比如定义了一个叫big的指令,在标签中使用时就要用v-big=‘’
- 步骤:
- 在directives中写
函数,参数第一个element是dom所在元素,第二个参数binding是和这个dom绑定了的一个对象
- 在标签中使用
v-刚才写的函数名='value'
,这个value就是会放到bingding对象中的value的值
- 在directives中写
- 例子需求:写一个big指令,可以让标签里的数组扩大十倍
- 来看看这个bingding的对象:关注里面的value值,这个值第一次的时候就是v-big=‘n’ 绑定的n,但后面这个值和data中就没关系了
2. 写成一个对象的形式
- 类似于生命周期函数的写法
- 自定义组件生效在vue中流程是这样的:
- 这三个时期对应
对象中的三个函数bind,inserted,update
- 之所以要在不同生命周期写是因为:有些对dom元素的操作,必须是在
dom渲染到页面上才奏效的,如dom.focus,如果用上一点中说的函数法,就无法控制在那个阶段指定,一些具体细节就无法调整
3. 命名注意
- 不要用驼峰命名法,最终他都会转成小写,
使用-对多单词进行分割
- 比如:bigNumber 就写成big-number,在定义函数的时候就写成 big_number(){}
4.全局配置
- 刚才的写法是在组件中局部的,作用域在组件内,如果想全局调用,就是用
Vue.directives("自定义操作名",在局部里写的完整函数or对象)
计算属性
- 拿已有的属性进行
加工和运算,得到一个全新的属性
- 储存在配置对象里面的
computed
- 底层原理还是用的
Object.definedProperty
,计算属性得出的属性也会被挂在在vm
上
1. 使用
computed:{
要计算的属性:{
get(){
return 计算出的内容
}
//但一般set用的少,因为一般是根据data中数据计算出属性,而不是改变属性来改变data中的值
set(value){
this.属性0=value
}
}
}
实例:
2. get的调用时机
- 当
初次读取计算属性的时候
- 之后会把这个计算的属性进行缓存,如果
所依赖的数据没有发生变化,则一直是使用这个缓存
====>如果用的是函数就得重复调用多次,使用计算属性节省了很多资源开销 - 当
所依赖的数据发生变化时
,又会调用get
3.简便写法
- 一般只用到get,而不用set,可以把计算属性简写成
像个函数的形式
监听函数
- 用来
监视属性的修改
,做出反应
1. 用法
watch:{
要监视的属性名:{
handler(oldVal,newVal){
要进行的操作
}
}
}
2. 监听多级属性的时候
- 当要监听的属性位于
对象中时
,写法有点区别,要用字符串表示属性
3. 深度监听
- vue中的watch默认不监听
对象内部属性的改变
- 通过
deep:true
属性可以让其监听到对象内部 - 数据结构简单的时候可以不开启,因为开启会消耗资源
- 需求例子:监听person对象,person中的name改变时进行操作
4. 简写
5. 计算属性和监听属性的使用场合
- 计算属性能完成的,监听属性
都能完成
- 计算属性对于只用来
在模板显示一个值,而不需要对这个值进行后续操作
,用计算属性一般会简单 - 如果对
更新的值需要有进一步操作,如一些异步操作时,则只能使用监听属性
- 原因是计算属性只能依赖
return 的返回值
,而监听函数可以有更多操作
绑定class
- 使用v-on可以绑定class样式
v-on:class='data中数据'
1. 绑定字符串
2. 绑定对象
如果绑定的是对象,对象的键名为class动态变化的值,value为布尔值,为true就添加到class,false不添加
绑定style
- v-on绑定一个style对象
- 在对象中写css样式,但是要注意
使用驼峰命名
,比如:background-color : red 在对象中要写成backgroundColor:‘red’
生命周期函数
- 在vue渲染页面的
特殊时间点调用的函数
,就叫做生命周期函数 - 当
要实现的功能一到某个阶段时就得开始调用
,就需要使用生命周期函数 - 比如:我想让页面一展现就开始某种特效,如果使用methods,可能需要有个事件触发才能执行,而使用了生命周期函数就可以在
挂载时就执行,不需要用一个click之类的事件进行触发
- 生命周期函数中的this,指的是
vm (Vue实例对象)
1. beforeCreate
- 这个生命时期
有初始化的vue实例,但是但没开始数据代理
- 这意味着这个阶段
拿不到vm中的data和method
2. created
- 这个生命周期
完成了数据代理,可以拿到vm中的data和method
3. beforeMounted
- 这个生命时期是
vue已经生成了虚拟DOM,但还没放到页面上成为真实DOM
- 这个阶段对
DOM的操作,最终渲染出来都不奏效
- 网页显示的是
未经编译的DOM结构
4. mounted
- 页面展现的是
经过vue编译的DOM,就没有{{}}这些东西了
- 这个是vue完成模板的解析,
初次
把真实DOM放入
页面后(挂在完毕),调用mounted 初次
就是相对于之后数据更改后重新渲染模板时,就不算初次了,不会调用mounted- 一般定时器,网络请求,订阅消息等可以在这个阶段执行
5. beforeUpdate
- 这个生命时期是当数据更新时,
数据已经更新了,但页面还是旧的,新数据没有放到页面上去
6. updated
- 更新数据完毕时,
页面和数据都是新的
7. beforeDestroy
- 想销毁vm实例就调用
vm.$destroy()
,vm就会被销毁,但销毁之前还有beforeDestroy
- 可以理解成
濒死状态
,这个时期一般可以关闭定时器,取消订阅消息等首尾操作 - 但是这里
修改的数据,已经不会更新了
8. destroyed
- vm已经被销毁了
- 自组件全部无效,监听器,自定义事件也无效了
9. 常用的生命周期函数
- mounted:发送ajax请求,启动定时器,绑定自定义事件,订阅消息(初始化)
- beforeDestroy:清除定时器,解绑自定义事件,取消订阅消息等(收尾工作)
10. vue3中生命周期会有所区别
- 在setup函数中使用生命周期函数
- 名字有所区别
- 调用时需要按需引入
自定义事件
- 可以用于子组件给父组件传参
1. 第一种使用方法
父组件中:
- 在子组件的
标签中
写v-on:自定义事件名=‘绑定的函数’
- 意思是触发这个事件的时候,就会调用handleClick函数
子组件中
使用this.$emit('自定义事件名',参数1,参数2....)
- 意思就是点击之后,
就触发这个自定义事件,传值为123
,相当于是个回调函数,返回123可以被父组件中接收
触发流程
2. 第二种使用方式
- 首先在
子组件标签上写上ref
this/$refs.
就可以取到子组件的实例对象- 通过
on('自定义事件名称',this.要调用的方法,参数)
也可以声明自定义事件 - 这样做的好处:
这样做就灵活很多,可以在不同的生命周期函数中调用
- 注意点:在这种写法中,如果在参数中直接在里面写
普通函数的回调
,里面的this并不是父组件的实例对象,而是子组件的实例对象
- 如果回调写的是箭头函数,
箭头函数没有自己的this,向外寻找this,这里找到的就是mounted的this,就是父组件的实例对象,就有name属性
- 总之
on方法里的回调中的this一定要是父组件的实例对象才有用
- 绑定的本质是把
这个自定义事件用on方法添加到子组件的实例对象上
3. 解绑事件
this.$off()
,这里this是父组件的实例对象
4. 如果同时也想使用原生事件
- 一旦给子组件标签定义了
自定义事件,那原生的事件如@click就会失效,全部被当成自定义组件
- 这时还想其触发,就
@click.native
就可以触发原生的事件
全局事件总线
- 用于任何组件之间的通信
- 不是新的api,而是程序员们利用
自定义事件玩出来的
一种可以实现任何组件通信的方式
1. 思想
- 自定义事件只能实现
父子组件之间的通信,那对于兄弟组件怎么办呢?
- 那就让大家都有一个
都能使用的工具人,中转一下
- 那个
大家都能访问的工具人,就是全局事件总线的精髓所在
- 这样来看,工具人需要满足一些条件:
- 工具人要被
所有的组件都可以看到
- 工具人身上可以
添加自定义事件
- 工具人要被
2. 谁来当工具人
- 先来想想这个工具人应该在哪才能被大家都看得到
- 之前提到过,Vue上配的东西之所以能实现全局配置,就是因为
Vue实例和VueComponent实例顺着原型链都会最终找到Vue的原型对象
- 因此在VueComponent中找不到的方法和属性会顺着原型链
找到Vue的原型对象上去
- 那所以这个Vue原型对象
Vue.prototype
就是大家都可以看到的地方
- 位置找到了,那谁来当呢
- 讲自定义组件的时候提到,绑定事件的本质就是
往子组件的实例对象上用on函数添加了自定义事件
- 那说明这个工具人,肯定长得像个Vue实例或者VueComponent实例
- 而我们一定要做的就是创建Vue实例对象,就让
Vue实例对象来当工具人
3. 实现
(1)确定工具人
- 所以工具人位置在Vue.prototype上,这个人就是个vm,直接来吧
- bus有总线的意思,其实叫什么名字都可以,这样工具人就找好了,以后就叫他bus
(2)接收端
- 发送端需要给bus使用$on函数添加一个事件,包含一个回调函数用来接收数据
(3)触发端
- 触发端就要触发$bus中绑定的事件,可以传参
- 在组件1中成功获得’黄金之风’
- 此时再来看这张图
消息发布与订阅
- 消息的发布与订阅也能实现组件间自由通信,但是
需要借助外部的js库
npm i pubsub-js
1. 发布者
- 发布者发送,publish
pubsub.publish('事件名称',传的参数)
2. 接收者
- 接收者
订阅
pubsub.subscribe('订阅的事件名称',绑定的回调函数)
- 注意绑定的回调函数接收的形参
第一个参数是这个事件的名称,第二个才是真正传来的参数
3. 总结流程
data的两种写法
1. 对象式写法
- data后跟对象的形式
const vm=new Vue({
data:{
person:'szk'
}
})
2. 函数式写法
- data 是一个函数,return的返回值才是页面中拿得到的数据
- 注意:
这个函数不能是箭头函数,箭头函数没有自己的this,箭头函数的this是window,而普通函数的this是调用者Vue
const vm = new Vue({
data:function(){
return {
person:"szk"
}
}
})
可以简写成
const vm = new Vue({
data(){
return {
person:"szk"
}
}
})
$nextTick
- 前提知识:
在一个函数中有多次data中值的修改,vue会走完整个函数才重新渲染页面,而不是遇到一个数据修改就重新渲染一次
- $nextTick使用场景:当一个函数中某些操作希望
vue不要走完整个函数就重新渲染页面
时使用
1. 使用方法
this.$nextTick(function)
2. 看个案例就明白了
-
需求:需要在点击按钮的时候,让input框出现,同时获取焦点
-
如果不使用这个
-
使用后
Vue集成第三方动画库animate.css
- vue中的动画系统可以让你
花更多精力关注dom本身,而少关注因为动画带来的各种class问题
- 这里拿
animate.css
举例子,animate.css官网
1. 安装animate.css,并且在需要的地方引入
npm install animate.css
import 'animate.css'
2. 写一段会让dom元素出现和消失的代码
- 这里就是点击按钮,h1就会出现
3. 给单元素加上动画
- 格式
<transition
appear
name="要绑定的class名"
enter-active-class="进入时动画名"
leave-active-class="离开时动画名"
>
<h1 v-show="isShow">hello</h1>
这个是需要变换的元素
</transition>
例子
4. 给多元素加动画
- transition标签中
只能放一个dom元素
- 如果想一组元素都有动画,需要用到
transition-group标签
- 注意:
每个元素都需要有个key属性,作为其唯一标识
组件
- 组件:
局部功能代码和资源的集合
——在vue中组件一般就是html,css,js
的集合
1. 为什么要组件化编程
- 传统的前端,依赖关系混乱,代码复用性差
图片来自尚硅谷
- 而用了组件化后
- 实现代码的良好
复用,简化编码,提高效率
2. 先在一个HTML中创建组件(非单文件组件)
- 虽然之后在
vue-cli
中我们实际都写单文件组件(一个.vue文件就是一个单独的组件)
- 先学习非单文件组件,对于理解
vue中组件的编写
更好
1. 创建组件
- 格式
const comp1=Vue.extend({ //传入一个配置对象
// 组件中一定不能写 el:
// 这个是最后在vue开发者工具里显示的名字
name:"student"
//template中写的是这个组件的模板
template:`
<div> {{name}}-----{{age}} </div>
`,
//data跟Vue实例中的很像,但注意一定要写成函数的形式
data(){
return{
name:"szk",
age:90
}
},
methods:{},
// ....vue实例中能写的基本都能写
})
- 有几个注意点:
- 组件中一定不能写
el:""
,这个只有在vue实例中才能写
- data中
不能写成对象的形式
,要写成函数+返回值
的形式 ,如果不这样的话,最终挂载到实例上,各个组件的data就会混乱,相互可以修改对方组件中的数据,如果用的是函数+返回值的方式,data中的值是调用函数返回的,而不是从data对象中获取的,就不会出现这个问题
- 组件中一定不能写
2. 注册组件
- 之后在
vue实例上注册组件,当然组件之间可以实现嵌套,就是在组件中可以注册组件,让其称为子组件
- 格式:
const vm=new Vue({
components:{
// key就是 要在模板(html)中使用这个组件的标签名,comp1就是上面一点钟创建的组件对象
student: comp1
people: comp2
}
})
3. 使用组件
- 在模板(html)中使用刚才创建的组件
<div id="root">
<div>
这里就是刚才在实例中注册的components的名字,用标签的形式使用
<student></student>
<people></people>
</div>
</div>
4.完整案例
3. 关于component的一些深入理解
(1)Vue.extend到底干了什么
返回一个新的VueComponent构造函数
- 将源码中一些先不去了解的地方删掉后,可以看到
- Vue.extend调用了一个方法,生成一个
VueComponent构造函数并返回
- 由此得出结论,Vue.extend方法
返回一个VueComponent构造函数,而且每次调用产生的VueComponent是全新的
(2)组件里的this是什么
和vue实例长得差不多的VueComponent实例对象
- 当标签在渲染的时候就通过上文中的
VueComponent构造函数实例化出来VueComponent对象
- 看看component中的this是什么
4. 来写单文件组件 .vue文件
- vscode推荐安装
vetur插件
,为.vue文件提供提示 - .vue文件分为三部分,template,script,style
- 格式:
<template>
<!--写html模板 -->
</template>
<script>
// 写js或ts代码
</script>
<style scoped>
/* 写样式css或less等 */
</style>
组件的创建
- 将上述的student组件写到单文件组件中就是这样的
- 注意点:script中需要
把这个组件模块化暴露出去,推荐使用export default{} 默认暴露
组件的引用
- 由老大哥app组件统领一切组件
组件的命名
- 一个单词:全小写或首字母大写
- school.vue
- School.vue
- 多个单词:用
-
连接,或者大驼峰- my-school.vue
- MySchool.vue
插槽slot
- 使用场景
在组件中,有一些地方需要单独定制,在使用这个组件标签的时候再决定定制的地方怎么写
- 就是用来写
组件中的局部
的
1. 使用
- 需求案例:在组件school中,大体结构相同,但有两个地方使用的不一样的东西
在组件中设置插槽
<slot name='slot的名字'> </slot>
在使用组件的地方往插槽放东西
- 最好将需要插入的元素用
template
标签包起来,这样做而不是用div包起来是因为最终vue会脱去template标签,就可以少出现一层莫名其妙的div结构
<子组件标签>
<template slot='插槽名字'>
</template>
</子组件标签>
2. 总结
- 插槽就是在
使用子组件标签时,对子组件中内容可以更自定义化地定制
组件中定义一个插槽
Vue是如何实现全局配置的
- 涉及到
原型和原型链的内容
- 其实就是
组件实例的原型对象VueComponent 的原型对象是Vue的原型对象
实现的 - 只要满足上面的关系,VueComponent实例中找不到的方法和属性会顺着原型链
找到Vue的原型对象
- 因此只要只要进行
Vue.filter
这样的操作,在Vue原型对象上加属性,就可以同时应用到组件上,实现全局配置
Vue-cli 中Vue到底是怎么跑起来的
- 首先要对vue中组件有了解
- 整个过程简化为这张图
ref属性
- 对于普通标签来说,就等于是
vue中的id
- 对于组件标签来说,ref可以取到的是组件对象,而id选择器取到的还是这个dom元素
1. 使用
- 直接在标签上加上
ref="给他取的名字"
,取的时候就用this.$refs.这个名字
- 会挂在在组件/vm上作为属性
- 在普通标签中,拿到的跟id选择器拿到的是一样的
- 而对于
子组件标签中
,ref拿到的是组件对象
,id拿到的是DOM元素
props:往组件中传参
- 父组件可以向子组件传参
- 对于使用的模板相同,但是
渲染的数据不同,且数据由父组件决定时,使用props传参
- 分为三种方式
1.第一种方式接收
- 父组件中在标签中用
:+传参名称='传参内容'
的方式传参 - 子组件中定义
props属性接收参数
- 接受的参数和data中的一样会被挂载到组件对象上,可以直接用{{}}插值语法调用
- 第一种方式是
简单声明,参数名放在一个数组中
2. 第二种方式接收
- props中接收是一个对象,里面就是
参数名: 参数的数据类型
- 可以用来约束接收参数的数据类型,不符合控制台报错
3. 第三种方式接收
- 一个参数名是一个对象
4. 注意点
- 父组件传参的时候传入字符串必须是
:参数名=''
,使用v-on动态绑定的形式,这样引号中会被当成是js表达式,否则数字传过去也是字符串
- 当传参是个非组件属性的
字符串时,则不能加 :
,因为:参数名=''的冒号中会被当成表达式,如果填字符串就代表数据的引用
,如果data或者computed中没这个属性就会报错 - 如果传参是从data或computed等挂在在了组件属性上的数据,那就用
:
- props传进去之后,不建议修改
- props的优先级
高于data
,如果props中的值和data中同名,渲染props中的
mixin
- 对于各个组件的
公共js部分
可以抽取出来写在mixins 中
1.使用方法
- 先创建一个
mixin.js
用来写抽出来的部分 - 在mixin.js中需要export导出,可以默认导出也可以分别导出,例子这里用默认导出
- 在需要使用的组件中导入这个js,使用属性
mixins:[]
- 之后这个组件中就会拥有这个方法和属性
- 注意:所有的mixins中的内容只能来自一个mixin.js文件,不能是多个js文件里都引入作为mixins;[]中内容
2. 全局引入mixin
- 全局引入mixin,所有的组件,包括App组件,都可以调用mixin中的方法和数据
之后就是Vue3部分//
Vue3 两种创建方式
1. vue-cli方式
- 先全局安装/更新vue-cli
- -g全局安装
- vue create 项目名称
# 先全局安装/更新vue-cli
npm install -g @vue/cli
# vue create 项目名称创建项目
vue create myPro1
2. vite创建(启动贼快)
npm init vue@latest
cd myPro2
npm install
npm run dev
sass安装
npm install sass-loader@^10 sass -D
Vue3使用Vuex
- 在Vue中实现
集中式数据管理
的插件
- 相比全局事件总线的优势: 当组件很多的时候,全局事件总线写起来贼麻烦
- 什么时候需要用:
多个组件依赖同一个状态
1. 运行流程图
- 组件调用
action(dispatch)
:客户给服务员提需求—————> action调用mutations(commit)
:服务员让厨师做菜————>mutations修改state中数据值(厨师做好了菜)
————>客户拿到菜(拿到state中的值)
1. 安装和引入
- 版本:vue2 只能用vuex3
npm i vuex@3
- vuex4只能在vue3中使用
npm i vuex
- 在store文件夹中写一个index.js,之后vuex的具体内容写在里面
- index模板:
import { createStore } from "vuex";
export default createStore({
state:{},
getters:{},
mutations:{},
actions:{},
modules: {},
});
- 在main.js中引入
2.state
- 是vuex中的数据存储地方,可以类比为
数据库
- 里面的数据是全局可以访问到的
store端
组件中使用:
- 从vuex中引入useStore
- 使用useStore返回一个store实例
- 通过store.state. 获取
3.mutations
store端
- 相当于后端的
DAO层
,只能写同步方法,用来直接与state中的数据进行交互,最好不涉及业务逻辑
- 是个对象,里面可以写很多方法
- 被调用的方式有两种:
- 被action调用:需要经过一定的业务逻辑,再操作state中数据
- 被组件直接调用:没什么业务逻辑,直接操作state
- 被actions调用
- 需要经过一定的业务逻辑,再操作state中数据
- action中函数的上下文参数context就有
commit('mutations中要被调用的方法名',参数)
组件端
commit('mutations中要被调用的方法名',参数)
4. actions
- 在actions中可以执行异步操作,操作state的业务逻辑可以写在这里
- 有两个参数,(contaxt上下文参数,传的参数)
- 上下文参数中就有commit,可以通过这个调用mutations中的方法
store端
组件端
- 组件端使用
store.dispatch('actions中的方法名',传参)
调用
5. modules
- 将vuex中也进行组件化,每个组件都包含属于自己的state,mutations,actions,getters
store端
组件端
注意:使用这种方法调用一定要在每个模块vuex中加入namespaced:true
vue3使用路由router
- vue-router是一个插件库,用来实现
单页面应用
:整个应用只有一个完整的的页面,点击导航连接页面不刷新,只会局部刷新
,数据通过ajax请求获取
1. 安装和基础配置
npm i vue-router
- 新建一个router文件夹,里面写一个index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/test01',
name: 'test01',
component: ()=>import ('../views/router/TestRouter01.vue') //这里填的就是组件的路径
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
- 之后再main.js中引入这个js,并且use
2. 配置各组件的路由
3. 在组件中的使用
1. 函数式使用route
注意这里是导入的useRouter 而不是useRoute
,router是路由器,掌管push,back,forward这些路由器操作,而route是路由信息相关操作
2. 标签式使用路由
<router-link to='路径'>跳转</router-link>
// to后用对象形式写
<router-link :to="{
name:'test01',
params:{
name:'dio',
age:101
}
}">跳转</router-link>
4. 嵌套路由
- 当多个路由都属于
同一个路由之下
,就像父子一样,就要用到嵌套路由
- 在路由的配置中,在
children
属性中又可以配置数组对象,每个对象由path和component组成,称为子路由 注意在子路由中的path不用加/
5. 路由传参
query形式的传参
-
直接在需要跳转的地方声明就可以,路由配置不用改
-
方式一:直接在url中拼接,用
?分隔url和传参,之后写成键值对形式,不同参数之间用&连接
,这种写法不能写:to 直接写to,后面当成字符串
-
方式二:
用:to=''
,后面当成js表达式解析,path是路径,query中写一个对象里面是传的参数
-
需要接收的时候
import { useRoute } from 'vue-router'
const route=useRoute()
将route实例化console.log(route.query)
就可以看到传来的query的值
params传参
-
restful风格传参,路由需要修改,需要跳转的地方也要修改
-
方式一:将需要传的参数直接用
/拼接在url中
方式二:如果写成对象形式,一定记得,不能用path,必须用路由的name
-
在路由的地方,需要做出标识,用
/:参数名称
的方式标注
-
在组件中使用和query类似
6. 缓存组件
- 路由切换的时候,不需要的组件是被
销毁的,里面的用户输入的数据如果再次加载的时候就没了
- 使用
<keep-alive include='要缓存的组件名'>
就可以对组件进行缓存,不会销毁,再次加载的时候用户输入的东西还在 - 缓存多个的时候用数组包裹
7. 路由守卫
- 不是所有导航项都可以点,
满足条件才能触发路由
,可以理解成前端的鉴权
- 分为
全局前置路由守卫和后置路由守卫
,分别在切换路由之前和切换路由之后调用
前置路由守卫
router.beforeEach((to,from,next)=>{
next() //next函数用来放行,否则统统拦截
})
后置路由守卫
router.afterEach((to,from)=>{
})
独享路由守卫
- 某个路由独有的路由守卫,只有前置没有后置
const routes=[{
path:'/personal',
name:'personal',
components: Personal,
beforeEnter((to,from,next)=>{
//判断逻辑
})
}]
组件中的路由守卫
- 在vue2中有
beforeRouteEnter,beforeRouteLeave,beforeRouteUpdate
- 在vue3中在setup中则变为
onBeforeRouteUpdate,onBeforeRouteLeave
由于setup作用时,路由已经生效了,因此没有了onBeforeRouteEnter
setup
1. 什么是setup
- set up是Vue3 推荐使用的
组合式API
- 在Vue3中,尽量都采用组合式API写法,虽然可以在Vue3写vue2的代码,但最好不要一个组件中两种写法混用
- set up
本质就是个函数
2. 如何使用
- setup 就是个函数,里面可以有
数据,各种函数,生命周期函数
- 既然是函数,就要有返回值,setup的返回值就是要
暴露给模板中的东西
,可以是数据,可以是方法 - return 出去的东西在模板中就可以用
插值语法{{}}
取到了
<script>
export default {
name:"comp1",
setup(){
let a=0 //曾经data的定义,全部放在setup中
function fun1(){ //methods中的函数,也丢在setup中
console.log(a) //由于是定义在setup这个函数中,定义的数据可以直接使用,不需要this
}
watch(){ //监听函数等等都可以在里面写
return { //既然是函数,就要有返回值,返回值就是要暴露给模板中的东西,可以是数据,可以是方法
a,
fun1
}
}
}
}
</script>
setup中的两个参数
- setup中可以接收两个参数,分别是
porps和context
props
- 用来接收父组件传来的参数
- 步骤:
- 在父组件中传参
- 在子组件中用porps属性声明接收的对象
- setup中的(props)参数可以接收参数,在setup中就可以使用了
context上下文对象
- context中最重要的是里面的emit方法,对应vue2中的
$emit
- emit的使用方法:
- 父组件中定义自定义事件
- 子组件中用
context.emit('事件名',传的参数)
- 最好也声明一下emits:[‘事件名’]
ref和reactive
- ref函数用来定义一个
响应式数据
- reactive函数用来定义一个
对象类型的响应式数据,但不能定义基本类型
1. 使用
- 首先将要使用的ref和reactice引入
- 之后就用函数调用的方式把
源数据放进去
- 可以看到ref将源数据变成了一个
RefImpl对象,本质通过Object,defineProperty实现响应式
- 而reactive将源数据变成了一个
Proxy对象
,本质通过Proxy
实现响应式
2. 两者区别和使用场景
- ref可以用来定义
基本类型或者对象类型的响应式数据
,定义对象的响应式时其实是求助了Proxy - reactive只能定义
对象类型的响应式数据
- ref返回的是
RefImpl对象,需要通过.value的方式才能取到里面的值
- reactive直接
.属性名
就可以获取属性
- 对于对象形式的使用reactive更方便,因为不用每个都.value
- 一般可以把数据封装成对象形式,用reactive更加方便
Vue3中计算属性
- 和vue2中类似,但是可以写在setup中
1. 用法
- 只用通过计算属性
将现有的值计算后导出,而不用在计算属性中操作别的数据时,使用简易写法
,一般不在computed中修改数据,简易写法一般够用
const 属性名=computed(()=>{
return 计算出来的属性
})
Vue3中的监听函数watch
- 和vue2中类似,但有很多小细节
1. 用法
- 监视一个数据
watch(要监听的数据,(newValue,oldValue)=>{
console.log('xxx的数据被修改了')
},{immediate:true,deep:true})
- 监视多个数据,写一个数组
watch([要监听的数据1,数据2...],(newValue,oldValue)=>{
console.log('xxx的数据被修改了')
},{immediate:true,deep:true})
- 监视对象中的某个属性时
watch(()=>对象.属性,(newValue,oldValue)=>{
console.log('xxx的数据被修改了')
},{immediate:true,deep:true})
2. 注意点
- 监视的数据必须是响应式的,
即为ref()或者reactive()包裹的数据
- 监测
ref处理的数据,不需要.value
- 当监视的是
reactive处理的整个对象
时,oldVal无法获取 - 当监视的当监视的是
reactive处理的整个对象
时,强制deep:true 深度监视,对象内部属性被修改也能监视到
- 但如果监视的是对象中的某个对象时,
deep:true 深度监视才能监视的到
watchEffect
- 作用:自动监听回调函数里面
用到的响应式数据的改变
- 和watch的区别:
不用指出监视是什么属性,回调中用到什么属性就监视什么属性
- 与computed的有点相似,但有点区别:
watchEffect更注重过程(回调函数体),而计算属性注重返回值
1. 用法
const person=reactive({
name:'jojo'
})
watchEffect(()=>{
let name=person.name
//这里用到了响应式数据person,因此就会监视person.name
console.log(‘回调执行’)
})
自定义hook函数
- 类似于vue2重点mixin,将setup中使用的组合式API进行
封装
,可以供其他组件中使用 - 本质是个
函数
,供其他组件引入后调用,获得其返回值
1. 使用
- 首先创建一个js文件,将其导出
- 使用端:引入这个函数,之后就可以使用了
2. 优势
- 实现了代码的高复用
- hook中可以包含任何的组合式api,``将组合式api进行了封装
- 使用端不用关注hook函数中是如何实现的,只关注其提供的功能(返回的参数),可以让setup中逻辑更清晰易懂
toRef和toRefs
- 作用:将数据变成响应式数据
- 当对象中的属性想写成一个个的响应式数据的时候,toRef可以大大提高效率
toRef用法
toRef(对象名,要单独变成响应式数据的属性名)
setup() {
const person=reactive({
name:'jojo',
age:90,
hobby:{
violin:'twoSetViolin',
pho:'saul'
}
})
return {
name:toRef(person,'name'),
hobby:toRef(person,'hobby')
}
}
toRefs的用法
- 上文中如果一个对象有100个属性,那还是得写一百行才能把一个个属性都变成响应式,toRefs就是来解决这一点,配合
拓展运算符
可以一次性将所有属性都变成响应式 ...toRefs(对象)
provide&inject
- 实现父组件和子孙组件之间很方便的通信
- 在父组件provide,需要的地方inject注入即可
1. 使用
祖宗组件中provide(要传的数据名,要传的数据)
setup(){
const person={name:'jojo',age:90}
provide('personData',person)
}
子孙组件中inject('数据名')
const person2=inject('personData')