Vue3学习笔记
最近想复习一下Vue3的整体过程,打算重新学一遍的Vue3+TS的教程,现在准备开始学,并且做好笔记。
Vue3官网 Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org)
尚硅谷笔记链接大全:资料
一共是71集,全长大约14个小时,从2024.03.04下午16:30开始学习,至此,开始记录。
编码规范为:TS+组合式API+Setup语法糖
Webpack和Vite的构建区别
Webpack:从入口进入,先扫描路由,然后扫描每一个模块,然后开始bundle,最后ServerReady,也就是说不管你用不用到的东西,都给你加载了,然后再给你看结果,就会比较慢。
Vite:我直接给你看结果,你想看哪个我给你加载哪个,等于变聪明了。
一句话:Vite比Webpack快。
Vue3创建步骤
然后根据所需进行配置即可。
然后
项目整体文件介绍
.vscode - extensions.json
:表示所需要安装用到的扩展插件.
删了也没事,但是留着也不占地方.
env.d.ts
:这里面的配置代码,主要目的就是让TS认识各种类型的文件. 例如css,sass,jpg等各种类型的文件.
index.html
:项目的入口文件,通过该文件引入的src/main.ts
文件,可以让整个项目跑起来
vite.config.ts
:整个项目的配置文件,可以配置插件,代理等之类的
src/main.ts简单介绍
在 Vue 3 中,createApp 是用来创建一个 Vue 应用实例的工厂函数。
import { createApp } from 'vue'
这行代码是导入了应用的根组件。
import App from './App.vue'
把vue挂在到指定元素上
createApp(App).mount('#app')
结构
选项式API和组合式API
选项式API一般是在Vue2中使用.
白色框这些都属于是配置项,也就是选项式API,学Vue2就等于是在学这些配置项。
选项式API的英文名叫OptionsAPI,但是在Vue3中提出了一种新的组合式API(CompositionAPI) 。Vue2的API是OptionsAPI风格的。Vue3的API是CompositionAPI风格的。
OptionsAPI(选项式API)的弊端就是,数据、方法、计算属性等等都被拆分开了,当你需要改动一个东西的时候,那么你就需要同时在Data、methods等等这些方法里面去修改,不便于修复和维护。
CompositionAPI(组合式API)把一个整体都放在一个函数方法里(Function),这样很符合逻辑而且也方便维护。
Vue3 核心语法
Setup
Setup
是Vue3中的一个新的配置项,它的值是一个函数,他是CompositionAPI
“表演的舞台”,组件中所有用到的数据、方法、计算属性、监视……等,均配置在Setup
里面。
Vue2的代码
改为Vue3
数据编写方法更集中了,但是如果通过方法来更改参数的值,那么会改变值但就不是响应式数据了,就无法同步到页面中。
setup函数的返回值也可以直接是一个渲染函数,可以直接指定渲染的内容,例如
此时的页面上就会只显示你好
这两个字,之前写的div之类的都没有用了,但是这个是不常用的。
Vue2中的语法比如data、methods是可以和vue3中的setup共存的,但是vue2的data、methods等,是可以通过this来调用setup里面的数据的,但是setup调用不了vue2其中的数据。
setup语法糖
语法糖说白了就是更简单的写法。
在script标签中,我想要无非就是数据以及函数方法,但是每个都还要进行导出,假如数据量比较大,其中一个我忘记return导出了,那岂不是出现很大的问题了,而且多了要一一导出代码也看着比较臃肿,所以就有了这个东西setup语法糖
,至于为什么叫这个,我估计是这样写起来会有种尝到甜头的感觉,很方便。
于是代码就变成了这样,所有的数据以及函数都不需要导出了,就很方便。
name名称插件
这样写看着也有些麻烦,写了两个script
,但可以通过一个官方插件来缩短成一个script
,开发依赖,所以加上-D
然后在vite.config.ts
中引用,注意两个白线的地方。
此时就可以只写一个script
了
这种写name
的情况可以应对,有时候公司的组件会写成文件夹名/index.vue
,这种情况,就可以使用name来区分。
响应式数据
ref创建基本类型响应式数据
在vue2中,你的数据只要是写在data()里面,那么就默认是响应式数据。
- ref
reactive可以构建对象类型响应式数据也可以构建基本类型响应式数据。
首先引用ref
然后你想让哪个数据变成响应式数据就用ref包裹住。
此时name就已经变成了一个响应式数据,所谓响应式数据就是你在修改这个name
值的时候,在页面上会同步更新,很方便。
此时如果console.log
打印一下name
的话,那么就会发现已经变成了一个对象,但是在引用的时候不需要加.value
,因为vue3会自动帮你加。
但是在JS以及TS中!必须要写.value
!不然将会无法操作这个值!
ref也可以对对象类型进行创建响应式数据,和reactive一样,也是直接包裹住即可,但是当你在JS以及TS中进行调用的时候,值得注意的是.value
的位置
reactive 构建对象类型响应式数据
此时对于一个对象类型的数据,也变成响应式数据了
数组也是同样的包裹,此时该数组也变成响应式数组了,可以直接更改arr[0].name = '王五'
,此时页面也会进行渲染。
reactive也是深层次的,不管数据内嵌套了多少层,知道找到了就可以变成响应式。
- reactive包裹的对象打印出来一般是
Proxy
开头。
- 而ref包裹的对象打印出来一般是
RefImpl
开头。
ref
表面上什么类型都可以处理,但是对于对象类型,底层还是通过reactive
实现的。
ref和reactive的区别
ref的.value
每次都要手写,就很麻烦,如果忘了可能又是一个bug,所有volar插件提供了一个功能,可以在vscode的设置中找到
一开始是没有勾选的,给它勾上就行了。
reactive重新分配一个对象,就会失去响应式。
如果这样,那么car将会失去响应式数据效果,那么应该如何解决这种情况。
只需要使用Object对象的assign方法
进行整体替换,如果有多个对象那么都是添加到第一个参数上。
但是如果使用的是ref进行定义响应式数据,就可以直接进行更改,可以理解为只要是带.value
那么就必然是响应式数据,无论基础类型还是对象类型。
torefs和toref
torefs:把一个reactive所定义的所有对象都变成一个ref所组成的对象。
上面的代码,如果在 =
后面没有加toRefs
那么name和age将不会是响应式数据,并且修改nage和age也会同步修改person.name和person.age。
toref:可以单独拎出来reactive定义的对象中的一个数据,并且也变成ref响应式数据。
computed计算属性
在Vue3中computed
是一个函数,需要引用使用,computed
拥有缓存,比function函数要聪明一些,例如要进行计算一个名称首字母大写。
此时在页面上调用fullName
即可出现首字母大写的名字组合,如果多次调用也只会计算一次,因为当computed是带有缓存的,但是这个是只读的,不能修改。
可读可写响应式数据需要调用get、set方法,set是带参数的,参数则是fullName的值
简单来说就是如果需要从多个数据中重新计算出来一个新的,就用computed
计算属性就可以了,计算属性计算出来的数据也是一个ref响应式数据
watch 监视
1.监视ref定义的基本类型数据
首先还是引用watch,然后定义一个sum = ref(0)
当你改变了sum值后,数据结果为
重点:在vue3中watch
函数返回一个用于停止监控的函数,这个返回的函数就是用来取消监控的,从而停止执行回调函数。
例子:当sum大于10的时候停止监控
2.监视ref定义的对象类型数据
监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视。
前面使用的时候只调用了watch函数的前两个参数,开启深度监视就需要添加第三个参数,假设sun是一个对象数据,那么代码为:
这样无论是修改整个对象数据,还是修改对象属性都会被监视。
第三个参数,是类似于配置项的参数,有多个配置,比如
watch的第一个参数:被监视的数据
watch的第二个参数:监视的回调
watch的第三个参数:配置对象(deep、immediate等…)
newValue**,oldValue打印出来的值是相同的,这个问题的原因是地址值没有变,在打印出来的时候还是原来的地址,所以会相同,房子没换,换了壁纸和沙发。
3.监视reactive定义的对象类型数据
reactive是默认开启深度监视的,所以可以直接监视对象内部属性的变化,无需开启深度模式,但是也是不可以关闭的,官方说话:隐式创建深度模式。
watch函数和function一样都可以写多个。
4.监视ref或reactive定义对象类型数据中的某个属性
若监视对象类型的某个属性是基本类型,需要写成函数形式(箭头函数)
可以在watch的第一个参数中,使用箭头函数来监视对象中的具体的一个值,比如
但是大括号和return是可以省略的于是就可以直接
但是如果监视的对象数据的属性是对象类型,可以直接写,也可以写成函数,但是建议写成函数,如果要监视对象类型的属性的细枝末节内部变化,需要手动开启深度模式deep:true
。
5.监视上述的多个数据
监视多个数据,通过使用数据包裹起来即可,还是普通数据用函数,对象数据建议函数也可以直接使用。
watchEffect
- 官网:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数。
就是刚开始就会执行一次,你不需要明确给我提出具体要监控谁,你要用到谁我就自动监控谁。
watch
对比watchEffect
- 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
watch
:要明确指出监视的数据
watchEffect
:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。
标签的ref属性
作用:用于注册模板引用。
用在普通DOM
标签上,获取的是DOM
节点。
用在组件标签上,获取的是组件实例对象(函数)。
用在普通html标签上,是直接获取dom元素内容
用在组件标签上,需要通过引入defineExpose
来导出内容
TS中的接口、泛型、自定义
@
符号会直接跑到项目的根目录,从而方便引入。
接口
在TS使用接口来限制规范,新建一个
引用然后可以看到,如果我们设置的数据和接口设置的不一样,那么就会出错
如果是定义数组的话那就需要用到泛型
代码意思是,数组中的每一项都要符合这个数据规范,所以用到了Array,多个泛型在Person
后面直接加就行,比如:Array<Person,Student,Order>
还有一种方法就是用到自定义类型
可选
在定义接口的时候,如果有些属性是可选的可以后面+?
,比如年龄可选,此时加上?
以后,age
参数可以不写
如果是响应式数据reactive,可以这样
props
首先引入defineProps
然后就可以接收父组件传过来的内容,必须要用数组来接收。
只接收+限制类型
这样更加严谨,标明我需要收到一个list,并且这个list必须是符合之前写的Persons接口标准的list,当开启这种限制以后,如果父传过来一个别的数据那么是传不过来的,切父组件必须传list过来。
接收 + 限制类型 + 限制必要性 + 指定默认值
当在list后面加上了一个?
就可以让父组件可传可不传了。
但是如果不传数据,可以加一个默认值,在vue中引入withDefaults
这种做法通常用于在组件中定义默认的 props 值,以防止在使用组件时没有传入 props 导致错误。如果组件接收到了 list
prop,它将使用传入的值;如果没有传入 list
prop,则会使用默认提供的值 [{ id: '12', name: '张三', age: 18 }]
。
define开头的函数,其实可以不用引入,因为是宏函数可以直接拿来用。
生命周期
组件的生命周期(生命周期函数、生命周期钩子)
时刻 |
调用特定的函数 |
创建 |
created |
挂载 |
mounted |
更新 |
|
销毁 |
|
vue2的生命周期
beforeCreate(创建前):
- 你可以在这个阶段做些准备工作,但是此时还没有真正创建 Vue 实例。也就是说,Vue 实例还没有被初始化。
created(创建后):
- Vue 实例被创建好了,但是此时还没有开始创建真正的 DOM。你可以在这个时候做一些数据的初始化工作,或者请求一些初始数据。
beforeMount(挂载前):
- 这个阶段是在 Vue 开始把你的页面内容放到 DOM 中之前调用的。你可以在这里做一些DOM准备工作。
mounted(挂载后):
- 这时候 Vue 实例已经挂载到了页面上,你的页面已经显示了 Vue 组件的内容。通常你可以在这里做一些需要操作 DOM 的工作,比如初始化页面后的一些动画效果。
beforeUpdate(更新前):
- 在数据更新但页面尚未重新渲染时调用。你可以在这里做一些在更新前需要的操作,比如对比更新前后的数据。
updated(更新后):
- 数据已经更新,页面也已经重新渲染完毕。你可以在这个阶段做一些需要最新数据的操作,比如更新一些在页面上显示的内容。
beforeDestroy(销毁前):
- 当你要销毁 Vue 实例之前调用。你可以在这里做一些清理工作,比如取消定时器、移除绑定的事件等。
destroyed(销毁后):
- Vue 实例已经被销毁了,这时候你的 Vue 实例上的所有东西都被清理掉了。你可以在这里做一些最后的清理工作,确保你的组件在销毁时没有留下任何垃圾。
这些生命周期函数钩子的调用顺序是这样的:
- 创建阶段:
beforeCreate
-> created
- 更新阶段:
beforeUpdate
-> updated
- 销毁阶段:
beforeDestroy
-> destroyed
vue3生命周期
创建
vue3的创建阶段已经包含在了setup
函数中,所以可以直接在setup
进行打印输出查看。
挂载
首先引入,其实就是vue2中的beforeMount
前面加了个on,然后B大写了。
挂载完毕也同样是。
vue3在挂在会会调用onBeforeMount
函数所指定的函数,所以需要传入一个回调函数。
在父子组件中都有挂载函数时,子组件先挂载完毕。
更新
跟之前同样,也是前面+on
同样原理。
卸载
在vue2中叫做销毁,但是在vue3中就叫卸载
同样原理。
卸载的效果可以通过v-if
或v-show
来确定展示与否来测试。
自定义hooks
使用axios来请求一个数据
async和await解析
解析:这是一个名为 getData
的异步函数,主要用于从某个 API 地址获取数据。
async function getData() { ... }
这是一个异步函数声明,async
关键字表示这个函数会返回一个Promise
对象。这意味着函数内部可以使用await
关键字,它可以等待一个异步操作(如HTTP请求)完成,然后再继续执行。
try { ... } catch (error) { ... }
try
块用于捕获可能发生的异常。如果异步操作成功,代码将正常执行;如果发生错误,控制流将跳转到catch
块来执行代码。
let result = await axios.get("api地址")
这行代码是异步操作的核心,使用了 Axios 库发送一个 GET 请求到指定的 API 地址,并使用 await
暂停等待响应结果,直到HTTP请求完成。请求完成后,响应数据被赋值给变量result
。
List.push(result.data.message)
这一行将 API 响应数据中的 message
属性值添加到一个名为 List
的数组中。它将result
对象中的data
属性的message
字段添加到List
数组中。这通常用于将获取的数据存储起来,以便后续使用。
catch (error) { alert(error) }
如果在执行 axios.get
时发生了错误,就会执行 catch
代码块中的语句,这里是使用 alert
函数在浏览器中弹出一个警告框,显示错误信息。error
变量将包含错误信息。
hooks的功能就是,让一个功能数据、方法都贴在一起,从而方便阅读。
一般是以useXXX
来命名,比如useOrder
、useSum
。
具体就是新建一个hooks文件夹,然后把每一个函数的功能以及所用到的数据,都放在这个以useXXX
来命名的.ts
文件里,代码正常写,但是需要默认导出,以及最后return
一个值。
hooks/useOrder.ts
然后在原文件中进行调用
因为hooks的本质就是一个一个可以调用的函数,所以可以直接解构,因为useOrder
是有返回值的。
这样写的好处在于,方便维护,假如以后要维护这个订单相关的代码,那么只需要维护好这个订单hooks就好了。
因为有了hooks,那么compositionAPI才发挥出了真正的威力。
路由
首先说明,站在程序员的角度,路由是路由,路由器是路由器。
路由的英文名叫:route
,路由器的英文名叫:router
,带r
的是路由器,不带的是路由,这两个有很大的区别。
路由就是一组key-value的对应关系,多个路由需要经过路由器的管理,一般在SPA应用上比较多。
SPA应用简单来说就类似于后台管理系统的那种单页面切换。
路由有一个核心的东西就是路径在变localhost:8080/xxx
。
学习路由使用步骤
- 导航区、展示区
- 请来路由器
安装路由
涉及到路由,就必须有router
这个文件夹,因为我们使用标准文件划分,并且指定制定路由事要想好路由器的工作模式。
Vue Router 提供了两种路由模式:history
和 hash
。这两种模式主要区别在于 URL 的显示方式和浏览器历史记录的管理方式。
**Hash 模式(哈希模式)**:在这种模式下,URL 中会包含一个 #
符号,例如 http://example.com/#/home
。这个 #
后面的部分(hash)不会发送到服务器,它只是客户端用来跟踪应用状态的一种方式。当用户点击浏览器的后退按钮时,应用会根据 URL 中的 hash 值来显示相应的视图。createWebHashHistory
History 模式:这种模式提供了一种更自然的 URL 体验,没有 #
符号,例如 http://example.com/home
。在这种模式下,Vue Router 会利用 HTML5 History API 来实现干净的 URL。这意味着 URL 的变化会被浏览器记录在历史记录中,用户可以使用浏览器的前进和后退按钮来导航应用的不同视图。createWebHistory
router/index.ts
然后回到main.ts
中去引用路由
src/main.ts
此时配置完毕以后,就已经用拥有了路由环境。
展示文件,RouterView
用于展示文件,RouterLink
用于切换文件。
RouterView
会自动把切换到的.vue
文件进行展示。
RouterLink
用于切换,其中
to="/xxx"
用于写切换路径
active-class="css样式"
表示被激活时候的类名,加上一个css样式,然后点击别的时候会自动去掉。
两个注意点
路由组件通常存放在pages
或 views
文件夹,一般组件通常存放在components
文件夹。
路由组件:靠路由规则渲染出来的,比如以下这是三个,一般是放在pages
或 views
文件夹里(视图)。
一般组件:自己动手写出来的组件例如:<Demo/>
- 通过点击导航,视觉效果上“消失” 了的路由组件,默认是被卸载掉的,需要的时候再去挂载。
components组合成为view,一个页面(page)由多个view组合(例如导航栏、显示区等)而成。
路由器工作模式
history
模式
优点:URL
更加美观,不带有#
,更接近传统的网站URL
。
缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有404
错误。
history:createWebHistory()
hash
模式
优点:兼容性更好,因为不需要服务器端处理路径。
缺点:URL
带有#
不太美观,且在SEO
优化方面相对较差。
history:createWebHashHistory()
to的两种写法
命名路由
作用:可以简化路由跳转及传参。
给路由规则命名:
跳转路由:
嵌套路由
增加一个children
,注意里面的path路径是不需要加/
的。
在使用RouterLink
跳转时,to
的路径要写完整。
一般组件用大驼峰,参数等命名用小驼峰。
路由传参
如果数据在父组件上,但是子组件需要展示,通过父组件向子组件传递参数方式有两种。
1.query参数
前情提要:News.vue
是父组件,Detail.vue
是子组件,父组件通过v-for
来渲染新闻标题。
步骤一:
父组件News.vue
在to
地址的同时传入参数,写法如下
此时传递了三个参数a,b,c
,多个参数使用&
隔开,这传入的是死数据,而且只是一个字符串。
所以要写成模板字符串,模版字符串则是在双引号""
里加上两个撇号,模版字符串里面嵌入JS
可以通过${}
来实现,并且要把to
变成:to
。
上述写法,一大长串儿,显得很不美观有点冗余,所以我们可以美化一下代码。
这样就看起来比较美观,毕竟要做一个格式化工程师,如果在路由中定义了name
参数,那么path
路径可以换成name
名称,更方便。
步骤二:
子组件Detail.vue
接收参数,需要引入useRouter
,可以看出是一个hooks
。
通过定义一个数据来接收useRouter
如果你不知道route
里面包含了什么可以通过打印来查看,返回的里面包含了一个query
参数,这个参数里面包含了父组件传过来的值。
引用
如果解构赋值。
重点:如果从响应式数据中直接解构属性,那么这个属性就会就此失去响应式,如果不想失去响应式,那么可以使用toRefs
就不会失去了。调用也更加方便即:
2.params参数
首先是要在路由中配置占位符,使用/:xxx
来进行占位符,占位符所对应的名称要和传入的参数名称相对应,在后面加上?
该参数可有可无。
- 传递参数
这里的跳转只可以使用name
参数,不可以使用path
,如果是query
的话两个都可以
- 接收参数:
备注1:传递params
参数时,若使用to
的对象写法,必须使用name
配置项,不能用path
。
备注2:传递params
参数时,需要提前在规则中占位。
路由的props配置
简单点就是可以获取值的时候不那么麻烦,响应式数据只写{{ xxx }}
这种语法是最棒的,所以我们就要用这种方法。
props
的功能可以这里么理解,当路由已经找到了并开始渲染<Detail />
组件时,如果我们开启了props
。
那么我们使用params
传递的三个参数就会进行转化为类似于组件传递值的方法,于是就变成了<Detail id=?? title=?? content=?? />
。
于是乎就特别简单了,这些props
参数可以直接使用defineProps
函数接收。
第一种props
第一种使用的是params
来传递参数
这就叫优雅的代码!!!(但是这个只能和params
参数进行配合,如果是query
就不行了),所以我们可以使用第二种props
写法,函数写法。
第二种props
使用query
传递参数
如果使用的是query
来传值,那么只能用这种办法,当然params
也同样适用,但是params
可以直接使用第一种更加方便。
常用的就这两种,第三种是对象写法,就是传递固定的值,几乎不用。
这些都是路由组件的传递方式,如果是一般组件(一般组件就是自己可以动手写在代码里的那么可以直接传递props
,比如我们可以直接<Deatil a=1 b=2 c=3/>
)。
路由的replace属性
路由的每次跳转都会形成历史记录,可以在浏览器的左上角的左右箭头进行前进后退,这是因为路由跳转默认是push
模式,如果不想这样,那么可以改成replace
模式,即不能前进也不能后退。
要改为replace
,只需要在RouterLink
标签中,加入replace
属性即可。
如果想看到某一个具体的就不让他回去的话,那么就可以单独加上replace
。
编程式导航(重要)
我们之前的跳转,全部使用了<RouterLink />
标签进行实现,那么<RouterLink />
标签在最后会被转化为<a/>
标签,这就导致了,如果全部都使用,那么就会出现一堆<a/>
元素。
所谓编程式导航,其实就是脱离<RouterLink />
来实现路由跳转。
打开页面3秒后跳转到新闻页面。
编程式路由导航的使用频率要远大于RouterLink
式。
router.push()
和to
的语法是一样的,都有两个模式字符串和对象,而且语法一模一样,直接拿过来用就行,但是参数需要传递过来。
通过按钮点击来查看新内容,那么就可以直接把之前的to
来拿用。
当然了,如果不想使用push
留下痕迹,可以直接router.replace()
进行使用。
路由重定向
作用:将特定的路径,重新定向到已有路由。
我们想一打开页面就直接显示主页等想一开始就显示的内容,顺序不分先后。
连续解构赋值+重命名写法
↑返回的数据名叫data
,需要从data
中拿到content
值,又把content
名改为title
Pinia
官网:Pinia | The intuitive store for Vue.js (vuejs.org)
官方描述为:符合直觉的Vue.js状态管理哭,说白了就是用着很舒服,没有vuex那么臃肿。
集中式状态管理也叫集中式数据管理,常见的有:react的redux,vue2的vuex,vue3的pinia。
hooks的作用只是把代码集中在一起,而pinia是各个组件之间共享数据。
搭建Pinia环境
引入Pinia
存储数据+读取数据
首先src
目录下必须有store
文件夹,pinia具体就体现在这里。
里面的文件命名一般是和组件相对应的,比如我们有一个count.vue
组件,那我们可以创建一个store/count.ts
文件,可以把这个文件看做是一个小仓库。
Pinia
官方鼓励我们使用类似hooks
的方式命名,所以我们可以命名为useCountStore
,引入defineStore
,它的第一个参数建议和组件名相同count
,然后在第二个参数就是开始配置。
state
要求要写成一个函数,并且要return
一个对象,可以是字符串也可以是对象,我们这里把sum
进行共享,并默认值设为6
。
count.vue
中不需要定义sum
,我们直接引入提到的小仓库,只要看到引入的目录中带store
,那就是引用了pinia
。
这里有值得注意的一点,如果是自己动手写定义的ref
属性,那么在使用的时候是必要.value
的。
但是如果是在reactive
里面定义的ref
,会自动拆包,也就是不需要.value
了,
我们现在已经拿到了sum
的值,就可以直接正常使用了,并且数据也是共享的。
修改数据三种方式
Pinia是支持直接把数据拿过来就修改的,符合直觉。
第一种修改方式,直接修改,比如我们要修改sum
。
对,就是这么简单暴力,可以直接拿来用,这个在vuex
中是不可以的。
第二种修改方式,使用$patch
函数修改。
$patch
是一个实用函数,用于直接修改状态对象(state)的一个或多个属性,属于批量变更,它的好处是执行一次就可以修改多个参数。
如果是多个值需要同时发生变化,就推荐使用$patch
,可以达到占用最少的资源。
第三种修改方式 actions
借助actions
实现(actions
中可以编写一些业务逻辑)。
组件中调用actions
这里的actions
感觉看起来好像就是正常的操作,还不如第一种来的实在,因为actions
可以完成的在第一种都可以完成,但是actions
中的业务逻辑,是可以进行复用的,所以也很有意义。
storeToRefs
这里想让你在读数据的时候更加优雅一些。
因为我们以上的操作在读取响应式数据的时候都是countStore.sum
等等前面都带一个countStore
,一点儿也不优雅,所以我们可以让他更优雅一些。
可能会想到,直接解构数据就好咯,但是这样不行,这样会导致数据就此丢失响应式,那么又想一下,引入一个toRefs
来包裹一下不就好了,确实可以,而且也可以正常运营,但是代价很大。
因为他会把你的store
中的所有数据,包括函数什么的,全部都变成ref
响应式数据,我们其实只需要数据罢了,所以不要用toRefs
,这样不好。
但是要解决这个问题也很简单,Pinia
也考虑到了这个问题,所以提供了storeToRefs
,大白话直说:这个只会把store
中的数据变成响应式数据给你。
geeters
geeters
和state
、actions
都是 兄弟,所以他们都是同级,geeters
里面是写函数的,而且必须返回一个值,就相当于计算属性。
在geeters
里面定义的函数,都有一个参数叫state
,所以在getters
中可以直接通过这个参数来调用state
中的数据,也可以直接使用this
,不用this
可以直接写成箭头函数。
这里的计算属性,同样是和响应式数据一样,可以拿来直接使用,并且也是可以直接解构出来进行使用。
$subscribe 订阅的使用
每个store
中都有$subscribe
函数,类似于watch
监视。可以直接调用,并且要传入一个函数,如果你不需要用this
那么你可以使用箭头函数,官方文档使用的是箭头函数。
那么$subscribe
的主要作用是,当store
中的数据发生了变化以后,就会调用。
它收到两个参数,mutate
修改前的数据, state
修改后的数据,state
使用频率是最多的。
我们可以把state中的修改后的数据存入浏览器中
JSON.stringify
:用于把内容转化为字符串。
JSON.parse
:用于把字符串转回来。
此时我们的数据可以这样定义
store的组合式写法
我们之前写的,属于是选项式写法,比如actions
和state
都是写在了一个对象里,我们把defineStore
的第二个参数写成箭头函数,就可以使用组合式store
了,但是记住箭头函数中不可以使用this
。
组合式store
例子,你就把他当做一个setup
函数就可以了。
组件通信
组件通信就是组件之间互相传递数据,这个玩的6很重要。
props
props
是使用频率最高的一种通信方式,常用与 :父 ↔ 子。
父组件:
子组件
父传子,比较容易理解,直接就可以传到子组件中去。
但子传父,就需要父亲先给子传入一个函数,子收到这个函数,在调用这个函数的时候以传参的形式把值传递过去。
自定义事件 custom_event
当你定义函数的时候,有一个默认参数,这个参数就是事件对象,里边包含了很多事件,比如鼠标移动之类的坐标。
概述:自定义事件常用于:子 传 父。
注意区分好:原生事件、自定义事件。
在定义@click
点击事件的时候,传入$event
占位符,即表示这是一个事件对象,那么test
函数的c
参数,就是一个事件对象了。
原生事件:
- 事件名是特定的(
click
、mosueenter
等等)
- 事件对象
$event
: 是包含事件相关信息的对象(pageX
、pageY
、target
、keyCode
)
自定义事件:
- 事件名是任意名称
- 事件对象
$event
: 是调用emit
时所提供的数据,可以是任意类型!!!
父组件在调用子组件的时候,通过@xxx = "xxx"
来定义一个自定义事件
子组件,需要调用defineEmits
,并且传入一个数组来进行接收
子组件emit('haha',666)
中的这个666在处罚的时候会以参数的形式被传递到父组件中的xyz
函数的value
参数中去,同理666可以替换为ref定义的toy
数据。
另外自定义组件的命名推荐使用肉串命名kebab-case
,比如my-event
mitt
可以实现任意组件通信,有种一拍即合的感觉,学到了就会了,mitt
就像是组件中的中间人。
首先就是要安装mitt
一般是放在utils
或者tools
文件夹中写一个emitter.ts
文件,然后在项目文件中需要的时候进行引用,和自定义事件挺像的。
mitt
提供了四个主要方法:
- on(event, handler)
- off(event, handler)
- emit(event, …args)
- all.clear()
接收数据的组件中:绑定事件、同时在销毁前解绑事件:
提供数据的组件,在合适的时候触发事件
注意这个重要的内置关系,总线依赖着这个内置关系
v-model通信传值
ui组件库的底层,大量使用v-model进行通信,这里是v-model的底层原理。
v-model是双向绑定,它的底层原理是一个动态的value值:value='xxx'
,加上@input='xxx = $event.target.value'
$event:事件对象,target,发生事件的本体,value就拿到了他的值。
v-model用在html标签上
v-model用在组件标签上
我们正常使用的是上面的写法,但是最终会被转化成为下面的写法。
组件接收使用
$event就是dom事件对象,一般在组件标签上使用 $event,而且HTML原生标签中使用 $event.target用来获取DOM对象。
$event到底是啥?啥时候能.target?
对于原生事件,$event就是事件对象 => 能.target
对于自定义事件,$event就是触发事件时,所传递的数据 不能.target
v-model既能父传子也能子传父。
v-model多个写法
即可以在一个标签上可以写多个v-model
,写法如下,即可以传入多个值。
$attrs
概述:$attrs
用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。
可以理解父组件通过子组件来传递给孙组件。
具体说明:$attrs
是一个对象,包含所有父组件传入的标签属性。
注意:$attrs
会自动排除props
中声明的属性(可以认为声明过的 props
被子组件自己“消费”了)
简单来说,就是你传了a、b、c三个值,但是只有a被props
接收了,那么剩下的b、c就都存在attrs
里面。
可以直接在模板上通过{{ $attrs }}
来查看
单项传递数据
父组件
v-bind="{x:100,y:200}"
== :x=100 :y=200
这两个写法是一样的,如果子组件不进行使用这些数据,那么可以直接在孙组件标签上添加v-bind="$attrs"
,来直接传递给孙组件。
然后孙组件就可以通过defineProps
来直接接收父组件的东西了。
如果要孙组件传递给父组件,那么在传入的时候加入一个回调函数,当然这个函数也会被包含在$attrs
中,孙组件直接接收进行使用就可以了。
$refs 和 $parent
概述:
$refs
用于 :父→子。
$parent
用于:子→父。
原理如下:
属性 |
说明 |
$refs |
值为对象,包含所有被ref 属性标识的DOM 元素或组件实例。 |
$parent |
值为对象,当前组件的父组件实例对象。 |
通过父组件来改子组件中的内容,可以在父组件中给子组件标签打上ref='xxx'
记号,然后在子组件通过宏函数把数据导出,导出后此时的ref
中的xxx
,就可以拿到子组件导出来的数据了,然后就可以进行操作了。
$refs
可以直接调用,里面包含了所有通过ref
打上标签的子组件。
假如父组件中引入了两个子组件,那么通过一个普通的按钮来创建一个点击方法,这个方法里面的参数传入$refs
,然后在就可以打印出来这两个子组件的实例对象
两个子组件中有相同的一个数据,想让他们每次点击都+2个。
反过来$parent
就是拿到父亲的数据,用法都一样,当然了父组件也要加上defineExpose({数据名})
进行向外部提供数据。
provide、inject 很好用
- 概述:实现祖孙组件直接通信,不打扰子组件。
- 具体使用:
- 在祖先组件中通过
provide
配置向后代组件提供数据
- 在后代组件中通过
inject
配置来声明接收数据
- 这两个都不带响应式。
具体编码:
【第一步】父组件中,使用provide
提供数据。
引入provide
函数,然后进行调用。
函数用于孙传父
注意:子组件中不用编写任何东西,是不受到任何打扰的
【第二步】孙组件中使用inject
配置项接受数据。
inject
第一个参数是接收的名称,和父组件提供的名称一样;第二个参数是默认值,即如果没有接收到就是用默认的替代;
slot 插槽
一般是同时呈现多个组件,但是组件里面的内容只有些许的不一样,这样就不用写多个组件,只需要用一个组件来完成。
默认插槽
在父组件中使用子组件,并且使用了双标签,可以在双标签的中间夹一些html标签,并且这些标签会在子组件中的<slot></slot>
标签的位置进行渲染。
假如你什么数据也没传过来,可以通过<slot>默认内容</slot>
这样当你没夹杂标签就会显示默认的内容。
这样当我们实现最上面图片的需求时,我们只需要写三个子组件标签,并夹杂着不同的内容带过去,这些不同的内容都被放在了子组件中的<slot></slot>
标签中。
注意slot
标签做的是呈现,比如样式什么的还是在父组件中进行修改比较好,并且只需要写一个slot
标签就行,写多个就会呈现多次。
简单理解:你在子组件中挖了一个坑,这个坑等着你来填土;这三个东西都可以填进来。
具名插槽
就是给坑取名字,然后让对应的名字到对应的坑中。
子组件的slot
使用name
来取一个名字,默认名为:default
父组件把标签都放到template
中,然后通过v-slot:x
来命名,也可以通过#x
来命名,可以写多个;注意template
是写在组件标签内的。
在子组件中通过名字来引用,在子组件中s1
名称对应的标题会在上方,而s
对应的会在下方。样式之类的只需要在父组件中进行设置就可以了。
作用域插槽
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(新闻数据在News
组件中,但使用数据所遍历出来的结构由App
组件决定)
在父组件中的子组件标签中v-slot="params"
表明params
作为一个变量名接收了子组件slot标签<slot :games="games" a="哈哈" x="hello"></slot>
里面传递过来的games
、a
、x
三个参数、即为所有参数。
对于命名还是和之前一样,比如:v-slot:s="params"
,子组件:name='s'
。
数据的维护什么都在子组件中,但是根据这些数据所生成的结构是父组件决定的。
简单理解:压岁钱在孩子那,但是根据压岁钱买的东西,却由父亲来决定。
其他API
shallowRef 与 shallowReactive
需要从vue
中引用
shallowRef
- 作用:创建一个响应式数据,但只对顶层属性进行响应式处理。
- 用法:
- 特点:只跟踪引用值的变化,不关心值内部的属性变化。
简单理解:我可以对person.value
进行修改值,并且有响应式处理,但是我对person.value.name
就没有响应式处理了,就是只可以.
一次。
如果你关注的是数据的整体修改,那么就使用shallowRef
。
shallowReactive
同理。
作用:创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性则不会变成响应式的。
通过使用 shallowRef()
和 shallowReactive()
来绕开深度响应。浅层式 API
创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可提升性能。
readonly 与 shallowReadonly
都是需要通过vue来引用的,然后直接使用。
readonly
- 作用:用于创建一个对象的深只读副本,限制所有层次。
- 用法:
当你在修改original
的时候,那么readOnlyCopy
的值也会跟着响应式进行变化,是有关联关系的,但是如果直接修改readOnlyCopy
则无法修改,会提示警告.
相当于复制了一份只能用来使用但是不能修改的数据。
- 特点:
- 对象的所有嵌套属性都将变为只读,并且readonly的参数必须是响应式数据(ref,reactive)。
- 任何尝试修改这个对象的操作都会被阻止(在开发模式下,还会在控制台中发出警告)。
- 应用场景:
- 创建不可变的状态快照。
- 保护全局状态或配置不被修改。
shallowReadonly
与 readonly
类似,但只作用于对象的顶层属性,就是只限制第一层,更深层次的就不限制了。
toRaw 与 markRaw
需要从vue
中引用。
toRaw
函数用于获取由 ref
、reactive
、readonly
等创建的响应式对象的原始未代理的对象。当你需要访问到对象的原始数据,而不是其响应式代理时,就可以使用 toRaw
; toRaw
返回的对象不再是响应式的,不会触发视图更新。
语法:const raw = toRaw(proxy)
官网描述:这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。
何时使用? —— 在需要将响应式对象传递给非 Vue
的库或外部系统时,使用 toRaw
可以确保它们收到的是普通对象
markRaw
作用:标记一个对象,使其永远不会变成响应式的。
例如使用mockjs
时,为了防止误把mockjs
变为响应式对象,可以使用 markRaw
去标记mockjs
customRef
需要从vue
中引用。
自定义Ref作用:创建一个自定义的ref
,并对其依赖项跟踪和更新触发进行逻辑控制。
普通的ref定义的数据,只要数据一变化,页面立马就更新,假如想要间隔一秒变化,那就办不到。
customRef实现修改数据一秒后页面变化:
读数据之前要调用track
,改数据改完了之后要调用trigger
,必须同时存在,这两个都加上才会实现响应式效果,可以理解为在自己实现ref
。
一般会把自定义ref封装成hooks
。
巩固一下hooks:一般以useXxx
命名比如useMsgRef.ts
,然后把所有代码都给拿过来,hooks是一个函数,所以要包裹在函数里.
此时就可以在代码中进行引用useMsgRef
总结:所谓customRef(自定义ref)就是在原来ref的基础上,加上一些自己的逻辑,这个主要聊的地方就是track
(跟踪)和trigger
(触发)。
Vue3新组件