vue3学习篇(2025)
vue3
vue3的优势
- 更容易维护:组合式API;更好的TypeScript支持
- 更小的体积;良好的TreeShaking;按需引入
- 更快的速度;重写diff算法;模版编译优化;更高效的组件初始化
- 更优的数据响应式;Proxy
- 渐进式框架:表示在项目中可以一点点引入来使用ue,不一定需要全部使用
(目前前端最流行的三大框架:Vue,React,=,Angular)
vue的本质:js库
Vue初体验:
动态展示数据
1 | <div id ="app"> </div> |
开发中从服务器把数据请求到data中,再通过插值语法动态地把数据放进去
声明式编程和响应式编程
vue受到MVVM模型启发,帮我们绑定数据,绑定dom;view视图和Model数据方法通过vuemodel这个桥梁来完成
OptionsAPI
data属性选项
在vue3中,data属性值必须是函数,用return返回对象的形式
(vue2中官方推荐是一个函数,传入对象也可以,但在vue3中不可以)
数据劫持
data中返回的对象会被Vue的响应式系统劫持,之后对该对象的修改或访问都会在劫持中被处理,一旦数据发生变化就会重新加载。
vue2和vue3的数据劫持方式不同:
1 | //vue2采用 |
options api–methods
methods只是个对象类型,没有作用域的说法
这些方法可以被绑定到模板中;
在该方法中,我们可以使用this关键字来直接访问到data中返回的对象的属性
1 | methos:{ |
methods里使用的this是vue帮你绑定好的this
注意:不应该使用箭头函数来定义method函数(例如 plus:()=>this.a++)
因为箭头函数绑定了父级作用域的上下文,this的指向是window,而不是组件实例,结果会是undefined
options API - computed计算属性
对于任何响应式数据的复杂逻辑操作,使用计算属性,比如需要对数据进行一些转化再现实,或对数据进行一些转化
计算属性将被混入到组件实例中,所有getter和seter的this上下文自动被绑定为组件实例
在计算属性中可以使用this
为什么不用methods?
methods会把所有data的使用过程都变成一个方法的调用
使用方法
核心步骤:1.导入computed函数2.执行函数在回调函数中return基于响应式数据做计算的值,用变量接收
应用如:基于原数据,动态计算新数据
1 | <script setup> |
- 计算属性默认对应的是一个函数使用时
1
2
3
4
5computed:{
计算属性名(){
return .....
}
}{{计算属性名}}
直接调用 - 和methods区别
计算属性是有缓存的
模板语法
要有一个意识:绑定可以分为绑定内容,绑定属性,绑定事件
Mustache模板语法
当data中的数据发生改变时,对应的内容也会发生更新
基本指令(用得少)
- v-once
v-once指令可以让我们渲染的内容只渲染一次,后续的修改不会被渲染
(当数据发生变化时,元素或者组件以及其所有的子元素将视为静态内容并且跳过)
该指令可以用于性能优化 - v-pre
跳过不需要编译的节点,显示原始的Mustache标签,加快编译的速度 - v-text
v-text指令,将内容插入到模板中,不会被编译成dom节点,而是直接被渲染成文本,如果有文本会被替代 - v-html
v-html指令,将内容插入到模板中,会被编译成dom节点,而不是文本 - v-cloak
这个指令保持在元素上直到关联组件实例结束编译
和CSS 规则如[v-cloak]{display:none}
一起用时
这个指令可以隐藏未编译的 Mustache 标签直到组件实例准备完毕
v-memo指令(性能优化)
先用一个例子理解:
1 | <div id= "app"> |
那么这个被v-memo
包裹的div里,只有name属性对应的值改变时才会重新渲染,day值改变将不会更新渲染
v-bind(绑定属性)
前面的指令主要是将值插入到模板内容中
想动态绑定一些属性,使用v-bind
,缩写为:
v-bind绑定基本属性
使用场景:如动态绑定img的src属性(两张图来回切换:三元运算符实现),a元素的href属性
1 | <div id="app"> |
v-bind绑定class属性
比如当数据处于某个状态时为一种样式
特殊:动态class可以写成对象语法
绑定class有对象语法和数组语法
动态绑定的class可以和普通class并存,互不影响
动态绑定calss属性–对象语法(常用)
下面的对象语法中只有一个键值对,里面可以写多个键值对,值应该是boolean语法
- 对象语法绑定一个键值对
这个例子,这种有和无切换的写法很常用,要想到1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<div id="app">
<button :class="{active: isActive}"@click="btnClick">点我切换样式</button>
</div>
<script>
const app = Vue.createApp({
data: function(){
return{
isActive:true
}
},
methods:{
btnClick: function(){
this.isActive = ! this.isActive
}
}
})
app.mount("#app")
</script>在写methods中时,我的this.isActive总是忘记写this,为什么这里要加this呢,不加可以吗?
首先不可以不加this,不然会找不到属性名报未声明的错误,其次伟大的AI老师这样说: - 对象语法绑定多个键值对
如果用对象语法绑定很多个键值对,都写在标签中可能不好看,那么这时候可以把这堆对象语法封装成一个函数放到methods属性中,在标签中只写这封装的函数名即可
动态绑定class属性–数组语法(用得少)
比如<h3 :class="['Mon','Tue','Wed','Thu','Fri','Sat','Sun']"></h3>
注意不可以写成双引号包双引号哦,比如这个:class=:"["Mon","Tue"]"
错误写法
数组语法也能使用变量,比如’Mon’改成data里的属性名放进去
数组语法中也能使用对象,可以但开发中几乎不用
v-bind绑定style属性(常用)
用来绑定一些css内敛样式
同样有对象语法和数组语法,后者类似前面的,很少用,下面只举例对象语法
动态绑定style,后面跟对象类型,一旦用对象类型,里面的值就要有限制
如<h3 v-bind:style="{color: myColor, 'font-size': '25px'}"
如<h3 v-bind:style="{color: myColor, 'fontSize': size+'px'}"
短横线的单词和数值记得用单引号包裹起来,短横线也可以用驼峰fontSize
如果size
是数字的话,要在拼单位'px'
如果里面东西很多,都写在标签中可能不好看,这时候也可以把这堆对象语法封装成一个对象放到data属性中,在标签中只写这封装的对象名即可(少用)
v-bind–动态绑定属性名称(少用了解)
比如不确定是color,还是font-size,用 :[属性名]="值"
的格式来定义
比如<div :[name]="value"></div>
,name的值在data里面取
v-bind–绑定一个对象(重要)
在给组件传参的时候用很多
如果在标签上挨个写属性属性名很麻烦,直接绑定一个对象,去data里往这个对象里慢慢放东西,会自动挨个遍历对象里的东西绑定上去
1 | <h3 v-bind="infos"></h3> |
v-on绑定事件
v-on:
简写@
,参数event
v-on绑定事件基本使用
一个标签上可以绑定多个事件,如果觉得绑定的事件太多,可以写成对象的形式<div v-on:click="dicClick" @mousemove="divMousemove"></div>
<div v-on="{click: divClick, mousemove: divMousemove}"></div>
两种写法都可,但是上面的可读性更强,下面的认识就好
v-on绑定事件参数传递
- 默认传递event对象
如果在绑定事件时没有传递任何参数,那么会默认绑定event对象作为参数,被传递过来 - 明确参数
有明确参数会把event替代掉参数没加引号会从data里面找属性对应的值,如果传递的参数是值,记得要加单引号
1
2
3
4
5
6
7
8<button @click="btnClick('nie', 23)"></button>
<script>
methods:{
btnClick(name, age){
console.log(name, age)
}
}
</script> - 明确参数+event对象
想要传参的时候也把event传过去,要$event
这样写1
2
3
4
5
6
7
8<button @click="btnClick('nie',23,$event)"></button>
<script>
methods:{
btnClick(name, age, event){
console.log(name, age, event)
}
}
</script>
v-on的修饰符
修饰符相当于自动帮你调方法对事件进行了处理
vue条件渲染
(v-if,v-else-if,v-else,v-show之类的,和vue2一样)
- v-show不支持template
- v-show不可以和v-else一起使用
- v-show元素无论是否需要显示到浏览器上,它的DOM实际都是有存在的,只是通过CSS的display属性来进行切换
- v-if当条件为false时,其对应的原生不会被渲染到DOM中;
- 如果我们的原生需要在显示和隐藏之间频繁的切换,那么使用v-show
- 如果不会频繁的发生切换,那么使用v-if;
templat元素的使用
template元素可以当做不可见的包裹元素,配合v-if使用,但是最终template不会被渲染出来
1 | <template v-if="isShowCode"><img src=".......""> |
遍历渲染
- v-for可以遍历数组,对象,数字(从1开始数)
- v-for可以和template结合
- v-for …in…和v-for …of…的效果是一样的
- v-for记得要有key
支持有一二三个参数:
一个参数:"value in object"
二个参数:"(value,key)in object"
三个参数:"(value, key, index) in object"
1 | <u1> |
vue数组侦听监测
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。
这些被包裹过的方法包括:
push();pop();shift();unshift();splice();sort();reverse()
不修改原数组的方法是不能侦听的
双向绑定– v-model
v-model基本使用
它负责监听用户的输入事件来更新数据
常用在表单,选项元素伤,创建双向数据绑定
简单原理类似:v-bind+v-on的使用,但是真实开发中都是使用v-model来实现
给表单输入框设置默认内容–v-bind<input type="text" v-bind:balye="message">
要让表单和vue的内容互相影响–v-model<label><input type="text" v-model="count"></label>
count在data里面
v-model的特殊绑定(重要)
v-model绑定checkbox单选框
单选框:v-model默认绑定的是布尔值,单选框value属性不会影响v-model的值1
2
3
4
5
6<label for="agree">
<input id="agree" type="checkbox" v-model="isAgree"> 同意协议
</label>
data(){
return{inAgree: false}
}v-model绑定checkbox多选框
多选框:绑定到属性中的值是一个数组,除了v-model,必须要绑定一个value值,当选中时会自动把值添加到属性数组里1
2
3
4
5
6
7
8
9
10
11
12
13<div class="hobbies">
<h3>你有什么爱好呀</h3>
<label for="play">
<input id="play" type="checkbox" v-model="hobbies" value="play"> 玩游戏
</label>
<label for="music">
<input id="music" type="checkbox" v-model="hobbies" value="music"> 听音乐
</label>
</div>
data(){
return{ hobbies: []
}
}v-model绑定radio
使用v-model绑定同一个值会有互斥的效果,不再需要name属性进行互斥;也必须要写value属性,v-model会把选中的按钮的值赋值给data中接收的数据
1
2
3
4
5
6
7
8
9
10
11
12<div class="gender">
<label for="male">
<input id="male" type="radio" v-model="gender" value="male">男
</label>
<label for="female">
<input id="female" type="radio" v-model="gender" value="female">女
</label>
</div>
data(){
return{
gender: "female"
}}v-model绑定select
select添加mutiple属性是多选,不添加这个属性是单选;select多选时只可以使用size属性
v-model要添加到select标签上,和前面不一样!
单选时,传递的是选中的值;多选时,传递的是数组(类似checkbox多选框)
v-model的值绑定
上面写的例子,这些东西都是自己写死的,但是真是开发中这些值是服务器给的,前端进行值绑定
比如说,服务器传递来的数据是对象数组,把数组放多选框中,如下图select的值绑定
再比如的chexobox的值绑定,如下图
v-model修饰符
可以同时使用多个修饰符
- .lazy
将v-model绑定的事件变为change事件,只有在提交时候(比如回车)才会触发 - .number
自动将内容转换成数字
如果让用户输入数字,只要用户输入了就会变成字符串类型,这时用v-model.number会把用户输入的数字转化成数字类型 - .trim
去除收尾空格
组件(这部分笔记看之前的把,会用就好)
vue3项目创建
create-vue创建项目
vue3推荐使用:create-vue是Vue官方新的脚手架工具,底层切换到了 vite(下一代构建工具),为开发提供极速响应
- 要已安装16.0以上的node.js
- npm init vue@latest 这一指令将会安装并执行 create-vue
是这样的(我有一步和不一样所以项目有点不一样)
视频是这样的
然后进入项目安装依赖运行
是这样的
进入页面是这样的
项目目录和关键文件
关键文件:
1.vite.config.js-项目的配置文件 基于vite的配置
2.package.json-项目包文件 核心依赖项变成了 Vue
3.x和 vite3.main.js-入口文件 createApp函数创建应用实例
createApp(app).mount(…)设置挂载点,前半段在创建实例,后半段在把实例往上挂载
4.app.vue-根组件 SFC单文件组件 script-template-style
变化一:脚本script和模板template顺序调整,结构和样式放在一起更利于维护
变化二:模板template不再要求唯一根元素
变化三:脚本script添加setup标识支持组合式API
(加上setup允许在script中直接编写组合式API)
5.index.html-单页入口 提供id为app的挂载点
(但是我的打开.js文件的地方是.ts文件,文件里的代码和视频是一样的,检查发现是在创建项目的时候是否使用JSX语法的地方应该是NO,我选了YES) 语言类型:.ts 文件是 TypeScript 文件,而 .js 文件是 JavaScript 文件。TypeScript 是 JavaScript 的超集,它为 JavaScript 添加了静态类型和其他一些语言特性。
类型检查:TypeScript 具有静态类型检查功能,可以在编译时捕获一些常见的错误,并提供更好的代码提示和自动补全。JavaScript 不具备这种类型检查功能,它是一种动态类型的脚本语言。
语法扩展:TypeScript 扩展了 JavaScript 的语法,使得可以使用类、接口、泛型等特性,以及其他一些新的语法和功能。JavaScript 本身不支持这些语法扩展。
编译过程:TypeScript 需要先编译成 JavaScript,然后才能在浏览器或 Node.js 环境中运行。而 JavaScript 文件可以直接在浏览器或 Node.js 中执行,无需额外的编译步骤。
可能会弹提示因为你在跑vue3的项目但是插件用的还是vue2,去扩展禁用“vetur”安装“vue-Official”
vite
新一代前端构建工具
优势:
开发过程中无需打包,可快速冷启动
轻量快速的热重载
真正的按需编译,不再等待整个应用编译完成
用vite创建vue3工程
看官方文档,更新版本命令可能会变动
初始化工程:根据官方命名创建的工程(最开始的样子,自己没有改动)
构造函数和工厂函数:
- 构造函数一般首字母大写,用new构造
- 工厂函数一般首字母小写,不用构造,直接调用就好
别名配置
别名配置
但是做完这一步虽然别名能用,但是没有提示
做下面的配置可以让编译器有提示
组合式API
组合式API-setup选项
组合式API:一系列函数
写法1
2
3
4
5
6
7
8
9
10<script>
export default {
setup(){
},
beforeCreate(){
}
}
</script>执行时机:在beforeCreate钩子之前执行
setup中写代码的特点
setup函数中,获取不到this(this是undefined)
在setup函数中写的数据和方法需要在末尾以对象的方式return,才能给模版使用
1 | <script> |
<script setup>
语法糖
script标签添加setup标记,不需要再写导出语句,默认会添加导出语句,帮助我们return,在template里面直接用就好了
1 | <script setup> |
组合式API - reactive和ref函数
- reactive:接受对象类型数据的参数传入,返回一个响应式的对象
1 | <script setup> |
- ref:接收简单类型或者对象类型的数据传入并返回一个响应式的对象
本质:如果接收的类型是简单类型,那么会在原有的类型外层包一个对象,包成了复杂类型,再接入reactive实现响应式
注意:访问数据需要通过.value,因为它包了一层
1 | <script setup> |
reactive 对比 ref
都是用来生成响应式数据
不同点
- reactive不能处理简单类型的数据
- ref参数类型支持更好,但是必须通过.value做访问修改
- ref函数内部的实现依赖于reactive函数
在实际工作中的推荐
- 推荐使用ref函数,减少记忆负担,小兔鲜项目都使用ref
组件(这部分笔记看之前的把,会用就好)
开发中一定要进行好组件拆分
组件使用
注册全局组件(用得少)
1 | app.component("组件名",{ |
每个组件里面可以有自己的逻辑(data,methods等)
注册局部组件(常用)
放在app中,和data同级
1 | components:{ |
组件名称规则
定义组件名有两种方式
- 短横线分隔符my-cat
使用时``` - 大驼峰标识MyCat
使用时<my-cat></my-cat>
或<MyCat></MyCat>
在项目中使用组件(快速回忆)
main.js中
1 | import { createApp } from 'vue' |
App.vue中
1 | <template> |
组件放在components文件夹
compenments/AppHeader.vue
1 | <template> |
组件也可以导入其他组件
组件通信
父子组件通信
父传子props
数据在父组件上,父用子组件展示数据,要把数据传递给子组件展示
父用子组件时,通过属性的形式传递东西
1
2
3
4<template>
<show-info name="why" age="18" height="1.88" />
<show-info name="kobe" age="30" height="1.87"/>
</template>子组件拿到父传递的数据,放到对应的位置上
子组件要通过props
来接收父组件传递过来的属性
简而言之,就是父组件通过属性的形式给这些名称赋值,子组件通过props拿到这些名称,使用这些名称展示数据props 的数组写法
子组件使用props数组的形式接收数据(同1中写法)export default{ pops:["name","age","height"] }
props的对象形式语法(常用)
这种写法可以对父传递的数据进行验证
默认值和必传一般二选一1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import default{
props:{
name:{
type: String,
default: "我是默认的name哦"
},
age:{
type: Number,
default: 0
},
height:{
type: Number,
required: true, //必传
}
}
}type常见的类型(图片转内容)
特殊类型--对象
如果type使用的是Object类型
那么默认值必须写函数(箭头不箭头都可以),函数返回一个对象
比如1
2
3
4
5
6
7
8
9friend{
type: Object,
default:()=>({name : "nie"})
},
message:{
type:Object,
default(){}
return {message:"hello,nie"}
}特殊类型 – 数组
如果type的类型是数组,那么也需要用函数1
2
3
4hobbies:{
typr: Array,
default: ()=>["虞寻歌","枫糖","雾刃"]
}规则命名
如果是小驼峰,那么组件用小驼峰或者-小写(和之前类似的规则一样,vue中组件命名都遵循这样的写法)父传子子没接受
如果父传子了一个属性,但是子没有接受,就称之为非prop的Attributue,该属性会默认添加到子组件的根元素上(根节点上)
如果不喜欢没添加的属性被传递到根元素上,可以给子组件的export default{}
里面添加inheritAttrs: false
这是可以用$attrs.xxx
获取,如图
子传父$emit
在子组件中触发一些事件时进行子传父
子组件发生的事件要改变父组件的内容
要让子组件发出去一个自定义事件
第一个参数是自定义的事件名称,第二个参数是参数
子组件触发自定义事件
1 | <button @click="btnClick(10)">-1</button> |
父组件修改数据
1 | <sub-counter @sub="subClick"></sub-counter> |
小结:子组件监听对应事件发生,用this.$emit(“ye”)发送事件,父组件在子组件上用@ye=”yemethods”接收事件,触发父组件的方法methods:{yemethods(){} }
emits数组
- emits数组语法(用的多)
在子组件中用emits定义所有自定义事件名称,注册说明一下,这样父组件会有提示
export default {
emits:[“自定义事件1”,”自定义事件2”],
methods:{}
} - emits对象语法(用得少)
用对象写法的好处是可以对参数进行验证
比如验证发送的数组必须要小于101
2
3
4
5emits:{
add: function(count){
if(count<=10){
return true}
return false}}
插槽Slot
认识插槽
在组件中预留插槽,插槽中具体放什么东西由使用者决定,而不是组件写死
插槽的使用
- 插槽基本使用
抽取共性做成插槽来占位,预留不同,让外部(组件使用者,如父组件)决定到底显示什么元素
把要显示的东西放到子组件标签中,这部分内容会放到子组件的插槽中
插槽用一个div包裹住,这样好控制
(图片) - 插槽默认值
如果父组件使用时没有传递东西,就会显示子组件插槽中的内容;如父组件使用时有传递东西,那么子组件插槽中的内容会被父组件传递的内容替换掉
具名插槽(多个插槽)
具名插槽的缩写v-slot:
可以换成#
告诉是给哪个插槽传入什么内容
插槽如果没有起名字,默认名default
动态插槽名
渲染作用域
父级模板的所有内容都是在父级作用域中编译的
子模板中的所有内容都是在子作用域中编译的
作用域插槽
看图,有点绕(特殊场景用,平时用得少)
非父子组件通信–provide和inject
provide和inject (用的较少)
依赖注入,父组件通过provide提供数据,子组件通过inject使用数据
特点:父组件不知道子组件在哪里使用熟悉,子组件也不知道inject来自哪里(其实也不需要知道)
想要在provide中使用this获取data里的数据,需要把provide写成函数,函数才有作用域
非父子组件通信–全局事件总线$bus(常用)
全局事件总线库–hy-event-store
通过第三方库使用事件总线
其他的第三方库也可以,逻辑都是差不多的
- 先安装
pnpm install hy-event-store
- 创建总线js
创建一个文件夹utils/event-bus.js1
2
3import {HEventBus} from 'hy-event-store'
const eventBus = new HYEventBus()
export default eventBus - 在组件中导入
1
import eventBus from './utils/event-bus'
- 发送事件
1
eventBus.emit("事件名",有要传的参数写后面)
- 监听事件
1
eventBus.on("事件名",(有参数的话这里写形参接收)=>{})
- 事件总线的移除
如果使用时间总线的组件被移除, 那么这个书剑总线也应该被移除
使用methods进行时间总线逻辑的处理,在移除的时候要有事件名和移除的方法 - 注意
父子组件可以用但是不推荐,父子组件还是乖乖用前面的父子通信吧
在真实开发中使用总线的时候把移除的代码写上最好
组件化补充
组件生命周期
组件创建、挂载、更新、卸载的过程
要在这个过程的某个阶段添加自己的逻辑代码,使用生命周期
在vue2中有很详细的理解过程,这里不重新写了,看这个图片,很清晰
这些函数写在export default
里面,和data(){}
通级别
两个最重要的,其他几乎不用
组件中的$ref引用
在vue中不推荐使用原生DOM操作,那么想要获取一个元素或组件,就要用$ref
在标签上绑定一个ref属性和属性值
如<h3 ref="title"></h3>
在js代码中this.$refs.title
获取元素
ref获取组件实例
组件实例有一个$refs属性,他有一个对象Objetc,有注册过ref attributed的所有DOM元素和组件实例
如果用ref拿组件<banner ref="banner"/>
,this.$refs.banner
拿到的是组件实例
拿到组件实例之后,父组件可以主动调用子组件中的方法
(两个图和一个图)
父组件还可以拿到组件实例后拿到组件的根元素this.$refs.banner.$el
,如果子组件有两个跟元素,那么拿到的是第一个node节点
要拿到组件实例跟元素中的节点,this.$refs.banner.$el.nextElementSibling
(真实开发中几乎不会这样用)
开发中尽量一个组件只有一个根元素
动态组件(了解)
内置组件keep-alive
让某一个组件保持存活的状态
在一个组件不再使用时,默认情况下会被销毁掉,下次用再创建,如果切换频繁,使用keep-alive
会让它依然保持存活
在使用组件时,用<keep-alive></keep-alive>
标签把组件包裹起来,还有匹配对应组件名
同时组件要添加name属性
存活组件的生命周期
使用activated(进入活跃状态,如选中),和deactivated函数
方式一:v-if逻辑判断
方式二:动态组件comonent(简洁)
is中的组件要来自两个地方,全局注册的组件和局部注册的组件
CompositionAPI
options api
之前学的都是这个,弊端是对同一个数据的操作有些零散
在vue3的composition API中,对一个数据的所有操作都在一个地方完成,把一些分散的东西放在一起,封装一个函数,维护简单,使用的时候注册使用
在vue3中,composition API是主流,要向这边转换
Setup函数的基本使用
setup函数默认有两个参数:props和context
setup里定义的数据和函数需要返回
setup里定义的数据默认不是响应式的,要变成响应式,需要用ref()
进行包裹
template中,对于ref对象中的内容会自动解包,所以模板语法中直接用即可,不需要.value
setup里面不能使用this
setup逻辑的封装思路
如果一套逻辑不止在一个组件中使用,可以把setup里面的逻辑封装成一个.js文件,在文件中记得return;在setup中引入这个.js文件,解构使用即可
使用时
1 | upset(){ |
Setup中的响应式数据
数据响应式reactive函数
- 基本使用
如果想为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive的函数:1
2
3
4
5import {reactive} from 'vue'
const state =reactive({
name:"coderwhy",counter:100
})
return {state}
- 因为当使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集(数据会被劫持)
- 其实编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的;
- 数据限制
reactive API对传入的类型是有限制的,它要求必须传入对象或者数组类型
数据响应式ref函数(常用)
定义简单类型的数据(也可也定义复杂类型的数据)
ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源
它内部的值是在ref的value属性中被维护的;
模板中引入ref的的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过ref.value的方式来使用;
但是在setup函数内部,它依然是一个ref引用,所以对其进行操作时,需要使用 ref.value的方式
1 | import {ref} from 'vue' |
- 解包小细节
1
2
3
4
5
6
7
8
9<!--默认情况下在template中使用ref时,vue会自动对其进行解包(取出其中value)·-->
<h2>当前计数:{{counter}}く/h2>
<button @click="increment">+1</button>
<button @click="counter++">+1</button>
<hr>
<! ---使用的时候不需要写.value--
<h2>当前计数:{{info.counter}}</h2>
<!---修改的时候需要写.value-
<button @click="info.counter.value++">+1</button>
ref和reactive的使用场景
- reactive的使用场景
reactive多应用于本地的数据
多个数据之间是由关系的(有联系,比如数据的聚合,组织在一起会有特定的作用)1
2
3
4
5const account=reactive({
username:"nie"
password:"123456"
})
return {account} - ref的使用场景(其他场景几乎都用ref)
定义本地的一些简单数据
定义从网络中获取的数据使用ref1
2
3
4
5const musics = ref([])
onMounted(() => {
const serverMusics·=["虞寻歌","雾刃","枫糖"]
musics.value =serverMusics
})
readonly(用较少)
vue代码要符合单向数据流原则,如果在开发中遵循单向数据流,那么不用这个函数也一样
比如,父组件把Info对象传给子组件,子组件可以获取info对象展示。默认情况子组件可以把info对象中的数据进行修改,同时父组件的Info数据也会被修改。
实际中不希望子组件轻易修改数据,如果要修改,那么应该让子组件把事件传递给父组件,让父组件来修改
规范的写法如图:
readonly使用方法:
先引入import {readonly} from 'vue'
再用readonly函数包裹数据const rdInfo = readonly(account)
再导出return {rdInfo}
readonly返回的对象都是不允许修改的;
但是经过readonly处理的原来的对象是允许被修改的;
比如const info =readonly(obj),info对象是不允许被修改的;
当obj被修改时,readonly返回的info对象也会被修改;
但是不能去修改readonly返回的对象info;
其实本质上就是readonly返回的对象的setter方法被劫持了而已;
一些判断函数(了解)
- isProxy
检査对象是否是由reactive或readonly创建的proxy - isReactive
检查对象是否是由reactive创建的响应式代理
如果该代理是readonly建的,但包裹了由reactive创建的另一个代理,它也会返回true;、 - isReadonly
检查对象是否是由 readonly 创建的只读代理, - toRaw
返回reactive或readonly代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用) - shallowReactive
创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象) - shallowReadonly
创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)
setup语法糖
不需要再export default{setup(){ return{}}}
把setup写在script标签中即可
1 | <script setup> |
侦听函数watch
在某些情况下,希望在代码逻辑中侦听一个或者多个数据的变化,这个时候就需要用侦听器watch来完成了
数据变化时执行回调函数,两个额外参数immediate:true
控制立刻执行,deep:true
开启深度侦听
侦听单个数据
默认有两个参数:newValue/oldValue1
2
3
4
5
6
7
8
9<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
// 2. 调用watch 侦听变化
watch(count, (newValue, oldValue)=>{
console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
})
</script>侦听多个数据
侦听多个数据,第一个参数改写成数组的写法1
2
3
4
5
6
7
8
9
10<script setup>
// 1. 导入watch
import { ref, watch } from 'vue'
const count = ref(0)
const name = ref('cp')
// 2. 调用watch 侦听变化
watch([count, name], ([newCount, newName],[oldCount,oldName])=>{
console.log(`count或者name变化了,[newCount, newName],[oldCount,oldName]`)
})
</script>1
2
3
4
5
6watch:{
//1.默认有两个参数:newValue/oldValue
message(newValue,oldValue){console.log("message数据发生了变化:",newValue,oldValue)
F}
info(newValue, oldValue){console.log("info数据发生了变化:",newValue,oldValue)}
}监听对象类型
如果侦听的目标是对象类型,那么拿到的是代理对象
想获取原生对象用下面的方法:console.log({...newValue })
console.log(Vue.toRaw(newValue))
immediate参数
在侦听器创建时立即出发回调,响应式数据变化之后继续执行回调
1 | <script setup> |
- deep参数
通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep
这样可以监听对象的某个属性
1 | watch:{ |
1 | <script setup> |
- 使用this.$watch侦听
可以在created的生命周期中,使用this.$watchs
来侦听:
第一个参数是要侦听的源;
第二个参数是侦听的回调函数callback;
第三个参数是额外的其他选项,比如deep、immediate;1
2created(){
this.$watch('message',(newValue, oldValue)=>{console.log(newValue,oldValue);},{deep: true,imediate: true})}
侦听
侦听多个数据源
侦听器还可以使用数组同时侦听多个源:
1 | const name =ref("NIE"); |
watchEffect停止侦听
如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可
1 | const stopWatch=watchEffect(()=>{console.log("watchEffect执行~",name.value, age.value); |
Setup-Hooks思想
把一些操作封装一个.js,在setup中调用即可,看下面代码就明白了
vue router
路由的基本使用
路由的基本使用
1.创建路由对象routes: 映射关系history: 修改url的模式(hash/history)
2.让路由对象生效app.use(router)
3.<router-view></router-view>
的占位
4.<router-link to="/home/news">新闻</router-link>
进行路由的切换
路由的默认路径
path配置的是根路径:/
redirect
是重定向,也就是我们将根路径重定向到/home的路径下,这样就可以得到想要的结果了.
1 | const routes=createRouter({ |
路由的嵌套
嵌套路由将路由配置到路由对象中,然后通过router-view进行展示
1 | const routes=[ |
router-link属性
router-link事实上有很多属性可以配置:
to属性:是一个字符串,或者是一个对象
replace属性:当点击时,会调用router.replace()
,而不是 routerpush()
;
active-class属性:设置激活a元素后应用的class,默认是router-link-active
exact-active-class属性:链接精准激活时,应用于渲染的<a>
的 class,默认是' router-link-exact-active
;
路由懒加载
Vue Router默认就支持动态来导入组件:
这是因为component可以传入一个组件,也可以接收一个函数,该函数 需要放回一个Promise;而import函数就是返回一个Promise
使用/*webpackChunkName:"home-chunk"*/
对分包进行命名
1 | const routes=[ |
在模板中获取到ID<h2>User:{{ $route.params.id }}</h2>
在代码中获取IDconsole.log(route.params.id);
在created中获取值this.$route.params
路由的传参–query
1 | this.$router.push({ |
接受用{{$route.query}}
he
这样传递的参数会在地址的后面跟?name=nie&age=23
动态路由基本匹配
很多时候我们需要将给定匹配模式的路由映射到同一个组件:
1 | {path: '/user/:id' |
路由匹配规则加*
在/:pathMatch(.*)
后面又加了一个 *
1 | {path:'/:pathMatch(.*)*', |
它们的区别在于解析的时候,是否解析/:path:/:pathMatch(.*)*'
Not Found: [ “user”, “hahah”, “123” ]path:'/:pathMatch(.*)'
Not Found: user/hahah/123
URL的hash
URL的hash也就是锚点(#),本质上是改变window.location的href属性
页面的前进后退
router也有back:
通过调用history.back()
回溯历史,相当于router.go(-1);
router也有forward:
通过调用 history.forward()
在历史中前进。相当于 router.go(1);
router的go方法:
1 | //向前移动一条记录,与router.forward()相同 |
动态管理路由
动态添加路由
比如说,向服务器发送一个请求,判断用户是管理员还是普通用户,根据用户角色的不同动态加载路由
1 | let isAdmin = true |
删除路由(了解,很少用到)
方法1:添加一个name相同的路由,由于路由name是唯一的,所以后面的name会替代前面相同name的路由,进行删除
1 | router.addRoute({ |
方法2:通过removeRoute方法,传入路由的名称name,进行删除;
1 | router.addRoute({ |
方法3:通过addRoute方法的返回值回调(特少用):
1 | let removeRoute = router.addRoute({routeRecord}) |
路由其他方法
router.hasRoute()
:检查路由是否存在 router.getRoutes()
:获取一个包含所有路由记录的数组
路由导航守卫
在跳转路由之前拦截并进行判断
守卫有很多很多,但是最常用的是全局前置守卫
全局的前置守卫–beforeEach
- 语法
在进行任何的路由跳转之前,传入这个函数的都会被回调,在导航触发时会被回调router.beforeEach((to,from)=>{})
to
到哪里去,即将进入的路由对象from
从哪里来,即将厉害的路由对象
还有第三个参数next
可选,不推荐使用
有返回值:
false:取消当前导航
不返回或undefined:进行默认导航
返回一个路由地址(可以是string类型路径,也可以是一个对象包含path\query\params等信息),进行跳转 - 在进入订单页面时,判断用户是否登录
如何判断用户是否登录呢
一般在localStorage中存储用户信息,保存一个token如果存在,则用户登录,否则未登录1
2
3
4
5
6
7
8
9router.beforeEach((to,from)=>{
const token = localStorage.getItem('token')
if(!token && to.path.indexOf('/order')!==-1){
//或if(!token && to.path==="/order"){
return '/login'
}
})
//想要推出登录,直接删除token即可
localStorage.removeItem('token')
完整的导航解析流程(来自官方文档)(了解)
导航被触发->在失活的(要离开的)组件里调用 beforeRouteLeave 守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve(异步组件被解析之后,跳转之前)
调用全局的 afterEach 钩子。触发 DOM 更新。
调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
Vuex状态管理
虽然pinia比vuex更优秀,但是vuex还是要掌握
vuex有五个核心概念
vuex的学习笔记在vue2中也有,那边记得很详细清楚,这里不再写了
pinia状态管理
pinia学习笔记在pinia文章中也有,那边记得很详细清楚,这里不再写了
网络请求
这部分学习笔记看网络请求文章
综合小例子
模板语法案例 – 书籍购物车(重要)
案例说明
要求:
1.在界面上以表格的形式,显示一些书籍的数据
2.在底部显示书籍的总价格;
3.点击+或者-可以增加或减少书籍数量(如果为1,那么不能继续-)
4.点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空~)
思路
先把大的架子搭起来,再完成一个个模块
搭建界面内容
表格标题+表格标题简单美化
实际开发中表格内的数据量和内容取决于服务器传过来的,所以表格内容用v-for
在现在练习时,books数据是自己写的,但是内容都写进data中有些多了不好看,放到data.js
文件夹,用script引入文件夹,在data中直接可以读取到data.js
文件夹的books
原因是script引入的是全局作用域,所以可以直接用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40<style>
table{
border-collapse: collapse;
}
th,td{
border: 1px solid #aaa;
padding: 8px 16px;
}
</style>
<div id="app">
<table>
<thead><tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
....
</tr></thead>
<tbody><tr v-for="item in books" :key="item.id">
<td>{{item.index+1}}</td>
<td>{{item.name}}</td>
<td>{{item.price}}</td>
...
</tr></tbody>
</table>
</div>
<script>
const app = Vue.createApp({
data(){
return{
books: books
}
}
//真实的开发一般这样做
created(){
fetch("url").then(res=>{
this.books=res
})
}
})
</script>完善–计算价格,价格前拼接人民币符号
购买数量的前后应该有加号减号两个按钮
用计算属性计算总价
关于价格的前面要加人民币符号,使用一个methods方法给所有的价格前面拼接符号,牢记这种思想
(vue2有过滤器,vue3删掉了,用拼接的方法也好用)1
methods:{formatPrice(price){return "$"+ price}}
数量加减操作
前面使用v-for循环渲染表格和生成加减号,那么点击加减号如何判断改变的是哪条数据的加减号呢
思路:让加减方法传递和接收index
或者item
,拿到这两个数据之一,对index.count
或者item.count
进行操作减号失效禁用–v-bind动态绑定
注意减号减到1就不能再减了,让这个减号按钮禁用(添加属性disable)<button v-bind:disabled="item.count <=1" @click="decrement(index,item)">-</button>
使用v-bind动态绑定移除书籍操作
还是和减号的思路一样,要确定移除的是哪一本,传递并接收参数,在方法中对这个参数进行操作this.books.split(item,1)
购物车是否有内容不同展示
当移除掉所有数据后,清除购物车表格,进行汉字提示
使用v-if,v-else条件渲染,根据books.length
判断,不等于0那就是有,等于0就是没有数据
<template v-if="books.length">
购物车案例完整代码
1 | <style> |
选中某一行 – 排他思想
点击某一行的数据,选中这一行数据(用active类,控制active添加到这一行数据上)
对这个类进行动态绑定
1 |
|