0%

认识Vue3

Vue

在vue中

编程语言:JS、TS 主要是TS

代码风格:组合式API 、 选项式API 官方推荐组合式

生态系统逐渐向Vue3倾斜,主流库和擦火箭都在向Vue3迁移

这边建议直接下载一个 Hbuilder X ,可以直接创建vue项目,不用准备脚手架,和输入命令

默认使用Vite,是直接准备完成,看哪部分渲染哪部分,更加快捷

image-20240520204027444

image-20240520204544002

这里的creaApp相当于一个花盆,APP相当于根,第四行相当于把根放进花盆里

index.html内

image-20240520210332091

image-20240520210551468

总结

  • Vite项目中,index.html 是项目入口文件,在项目最外层
  • 加载 index.html 后,vite解析 <script type="module" src="xxx"> 指向 JavaScript
  • Vue3中是通过 createApp 函数创建一个应用实例

引入组件

如果想要封装组件,可以在components目录下新建一个Vue文件,然后引入使用

image-20240521152100322

三步走

一、import引入

引入组件的路径

二、component引入

将组件名称放入export default中

三、使用方法

如下图:组件名称为 Person 使用是则为 person

如组件名称为 MyComponents 使用时写作 my-components

image-20240521152203856

Vue3核心

  • vue2 的 API 设计是 Options(配置)风格的

  • vue3 的 API 设计是 Composition(组合)风格的

这些框住的都是配置,选项式

image-20240521153927852

Options API 的弊端

Options 类型的 API ,数据、方法、计算属性等,是分散在 data methods computed 中的,若想更改需求,需要分别修改 data methods computed ,不便于维护和复用

查看效果动画

https://www.bilibili.com/read/cv10685553/?spm_id_from=333.999.0.0

image-20240521160504513

image-20240521160547233

Composition API 的优势

用函数的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。将数据、方法、监视等组合在一起

image-20240521160616293

image-20240521160637568

拉开序幕的setup

setup

setup中的this是undefined,不要尝试在setup中写this.xxx,Vue3在尝试弱化this

image-20240521162756473

注意看注释

image-20240521162917455

setup返回值

上图的return就是一个返回值

setup的返回值也可以是一个渲染函数

1
return () => 是一个箭头函数,百度一下即可

通过这个渲染函数,将哈哈渲染到页面中去了

image-20240521163256642

setup与Opyions API 的关系

setup可以和data、methods共存,注意一下细节,setup是先执行的,也就是说,data是可以读取到setup的数据的

原写法可以读取到新写法的数据,新写法读取不到旧写法(data)的数据

image-20240521165113768

setup语法糖

image-20240521171120014

这两个script同时存在很正常,第一个script仍然有存在的必要,因为需要export名称,如果文件名和组件名相同的话也可以不写

用以下方法可以只写一个script但是仍然可以定义组件名

终端下载插件

1
npm i vite-plugin-vue-setup-extend -D

vite.config.js中引入

image-20240521172728045

在文件中的script中输入name

image-20240521172651664

image-20240521172430585

响应式数据

Vue2的数据在data中就是响应式数据,但是Vue3没有data,他的响应式需要通过其他的方式

image-20240521200546270

ref创建基本类型的响应式

image-20240521201837488

这个实例对象是可以改变的,内的数据只看无下划线部分的

image-20240521201324842

这里需要更改一下方法为name.value但是前端仍为不需要更改,其自动选定value

image-20240521201438804

reactive创建对象类型的响应式

打印出来的数据,数据在target中

image-20240521203519086

image-20240521212701383

ref创建对象类型响应式数据

1
2
ref >>>  可以定义基本类型、对象类型的响应式数据
reactive >>> 只能定义对象类型的响应式数据

ref定义响应式数据,只需要在调用对象的时候加一个value即可正常调用

image-20240521213448544

image-20240521213820263

ref对比reactive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
////从宏观角度来看
1、ref用来定义:基本类型数据、对象类型数据
2、reactive用来定义:对象类型数据


////区别
1、ref创建变量必须使用 .value (可以使用 volar 插件自动添加 .value )
2、reactive重新分配一个新对象,会失去响应式 (可以使用 Object.assign 去整体替换)


////使用原则
1、若需要一个基本类型的响应式数据,必须使用ref
2、若需要一个响应式对象,层级不深,ref、reactive 都可以
3、若需要一个响应式对象,且层级很深,推荐使用reactive

volar插件在Hbuilder X中没有找到,VSCode中有

reactive有一个局限性,无法被整体替代

解决方法

image-20240522152618838

1
2
3
car = {brand:'雅迪',price:'1'}
car = reactive({brand:'雅迪',price:'1'})
//这么写页面是不会更新的

如果是定义的ref对象数据,就可以直接整体替换

1
2
car.value = {brand:'雅迪',price:'1'}
//页面会更新渲染

我的理解在于,ref定义的一个对象,数据在value中,更改整个value,仍然是在响应式数据内更改,而reactive定义的对象,如果要整体替换的话就不再是响应式的数据,虽然数据成功更改了,但是无法在页面显示,使用Object.assgin()算是巧妙的保留了reactive的响应式,在更新数据之后更新到页面。

简单来说捏,ref整体替换相当于换了个女朋友,reactive的替换相当于女朋友换了身衣服

toRefs和toRef

举个例子

image-20240522162105083

image-20240522162339468

通过toRefs可以将reactive解构的数据变为响应式数据,通过输出可以发现,更改数据的时候,content.name的内容也发生了改变

说明这里解构出来的name是和content.name相关联的,所以前端渲染写name或者conten.name都是可以的。非常nb。

toRefs接收一个有reactive定义的响应式对象,将响应式对象的每一组key value都拿出来,形成一个新的对象,对象中有值,但是这个值是和原响应式相关联的,非常有作用

toRef看名字知道和toRefs的关系,toRef是将响应式数据内的一个变量拿出来

1
let name =toRef(content,'name')

作用和toRefs相同

computed计算属性

写一个输入框

image-20240522164255754

使用v-model:value可以实现双向绑定,value可以省略

image-20240522164545945

image-20240522164457116

虽然,可以直接把数据拿过来用,但是如果有别的需求的话,代码将变得复杂

image-20240522164839272

如:姓名为英文,我需要首字母大写

image-20240522165326461

很不优雅,把他移到后端

这个时候需要一个computer计算属性,computer有一个属性,只要计算所依赖的数据发生变化就重新计算

Vue2中使用computer是这样的

1
2
3
4
5
6
7
8
9
<script lang="ts">
export default {
computer : {

}
}
</script>

//computer在Vue2中是一个配置项,在Vue3则发生改变

Vue3

image-20240522170439226

image-20240522171227394

上图定义的fullName是一个计算属性,且是只读

image-20240522171917700

修改一下试试

可以看到这是一个响应式数据

image-20240522172322486

点击后页面也没有什么变化,这里有个bug,懒得换截图了,fullName.value 点击后数据不会更改

image-20240522172550613

写一个可以改变的computer,get() get 到的是自己输入的只读,set() set是通过点击事件出发更改,然后将数据解构,再去 get 计算的

image-20240522173705139

如何应用到微信小程序

我的思考:

我认为上面的set方法严格来说也不算是改变了计算结果,而是通过改变计算的两个数据,引起computed重新计算而已,这个功能在微信小程序中也可以复现

简单说一下原理:微信小程序中并没有直接的API写法,需要npm一个组件,然后再自己封装一个component页面组件,然后调用

首先:引入组件
1
2
npm install mobx-miniprogram mobx-miniprogram-bindings
//使用npm引入mobx-miniprogram和mobx-miniprogram-bindings两个组件到微信小程序,然后构建npm(我成功的时候是引入到了page的同级目录下)

简单介绍一下组件

  • mobx-miniprogram 的作用:创建 Store 对象,用于存储应用的数据
  • mobx-miniprogram-bindings 的作用:将状态和组件、页面进行绑定关联,从而在组件和页面中操作数据
其次:封装组件

这一步需要在page的同级目录下创建一个component文件夹,因为是.js文件内不是 page({}) 而是 Compontent({})

image-20240523212900937

注意检查一下json文件中是否有 "component": true,

然后

numstore/numstore.js 中创建一个 Store 对象,来储存数据,函数

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
/* compontents/numstore/numstore.js */

import { observable, action, set } from 'mobx-miniprogram'

// 使用 observable 创建一个被监测的对象
export const numStore = observable({

// 创建应用状态
numA: 'zhang',
numB: 'san',

// 使用 action 更新 numA 以及 numB
update: action(function () {
this.numA+=1
this.numB+=1
}),

changesum: action(function () {
this.sum = 'li-si'
}),

// 计算属性,使用 get 修饰符,
get sum() {
return this.numA + this.numB;
},
set sum(VaL) {
const [val1,val2] = VaL.split('-')
console.log(set,val2,val1)
this.numA = val1
this.numB = val2
}
})

image-20240523214058666

注意蓝字是问号,我并没有真的去尝试,因为感觉很麻烦,封装着用呗,蛮好的

然后:使用组件

在要引用组件的页面引用 mobx-miniprogram-bindings

如果需要 Page 或者 Component 中对共享的数据进行读取、更新操作,需要使用 mobx-miniprogram-bindings

mobx-miniprogram-bindings 的作用就是将 Store 和 页面或组件进行绑定关联

如果需要在组件中使用状态,需要 mobx-miniprogram-bindings 库中导入 ComponentWithstore 方法

在使用时:需要将 ==component 方法替换成 Componentwithstore 方法==,原本组件配置项也需要写到该方法中,在替换以后,就会新增一个==storeBindings== 配置项,配置项常用的属性有以下三个:

  • store:指定要绑定的store对象,这里是绑定的封装组件的位置
  • fields:指定要绑定的data字段,这里的字段,是封装组件内的字段,而且绑定后,不需要在index中的data再进行定义,直接再前端就可以使用拿到数据
  • actions:指定需要映射的 action 方法,之前封装组件时,引用了action方法,而且在定义函数时也写到了action,这里只需要写需要用到的方法名,即可从封装的组件内获取到对应方法,直接在前端使用即可

以为大部分组件被封装起来了,在index页面也就没什么好说的了,只需要注意一下storeBindings这个配置项获取方法即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* pages/index/index.js */

import { ComponentWithStore } from 'mobx-miniprogram-bindings'
import { numStore } from '../../components/store/numstore'
//两行import分别引入mobx-miniprogram-bindings组件和刚刚封装的numstore组件

ComponentWithStore({
data: {
someData: '...'
},
storeBindings: {
store: numStore,
fields: ['numA', 'numB', 'sum'],
actions: ['update','changesum']
},
methods: {
}
})

如果原页面有需要新定义的方法,只需要在methods中定义,参考vue2

最后:前端渲染

写个丑陋的前端渲染一下

image-20240523215936991

掌握了这些东西,可以尝试一下弄计算属性了

引入组件

1
npm install miniprogram-computed

开始使用组件复现计算属性

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
// component.js

// 引入 miniprogram-computed
import { ComponentWithComputed } from 'miniprogram-computed'

ComponentWithComputed({
data: {
a: 1,
b: 1
},

computed: {
total(data) {
// 注意:
// computed 函数中不能访问 this ,只有 data 对象可供访问
// 这个函数的返回值会被设置到 this.data.sum 字段中
// 计算属性具有缓存,计算属性使用多次,但是计算属性方法只会执行一次
console.log('~~~~~')
return data.a + data.b
}
},
methods: {
change:function(){
this.setData({
numA : this.data.numA + 1
})
}
}
})

前端吓唬写一下,达到计算属性目的

watch

监视,谁谁谁变了,我要怎样怎样

1
2
3
4
5
6
Vue3中的watch只能监视以下四种数据:

1、ref 定义的数据
2、reactive 定义的数据
3、函数返回一个值( getter函数 )
4、一个包含上述内容的数组

情况一

监听 ref 定义的数据

image-20240522210655285

在Vue3中,watch函数返回一个停止监听器的函数,因此定义的stopWatch实际上是一个函数,当呢调用他是,他会停止对sum响应式数据的监听

情况二

监听ref定义的【对象类型】数据:直接写数据名,监听的是对象的【地址值】,若想监视对象内部的数据,要开启深度监视。

image-20240524105415300

deep开启深度监视

image-20240524111542504

为了帮助理解可以当个比方

更改地址值相当于搬家,可以找到newValue和oldValue,更改属性相当于家里换家具,console时已经更换完了,获取到的地址值是同一个,看到的数据也是一样的

情况三

监视reactive定义的【对象类型】数据,且默认开启了深度监视

image-20240524143721719

情况四

监视ref或者reactive定义的【对象类型】数据中的某个属性

注意点如下

  • 若该属性不是【对象类型】,需要写成函数形式
  • 若该属性仍然是【对象类型】,可以直接编,也可以写成函数,不过建议写成函数

监视对象类型中的某个基本类型数据,写成函数式

image-20240524153323683

蓝字错误,写为

1
()=>person.name

监视对象类型中的某个对象类型数据,可以直接写(但是有问题),建议写成函数式

image-20240524154850969

函数式

image-20240524155233716

总结,如果要侦听对象内的某个属性,无脑函数,对象类型数据在加上一个deep

情况五

监视上述的多个数据

image-20240524160732396

如何应用到微信小程序

自己的话:

使用和computed同一个组件引入

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
//js
import { ComponentWithComputed } from 'miniprogram-computed'

ComponentWithComputed({
data: {
a:1,
b:1
},
computed: {
total(data) {
return data.a + data.b
}
},
watch: {
'a,b':function (a,b) {
this.setData({
total: a + b
})
}
},
methods: {
updateDate() {
this.setData({
a:this.data.a + 1,
b:this.data.b + 1
})
}
}
})

watchEffect

官网描述:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更新时重新执行该函数

watch 对比 watchEffect

  • 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
  • watch :要明确指出监视的数据
  • watchEffect :不用明确指出监视的数据(函数中用到哪些属性,就监视哪些属性)

image-20240525102510968

标签的ref属性

image-20240525105645472

这种查询id的方法不建议使用,因为id是不可重复的,id取名难,一旦重复了不好找错

image-20240525114656049

image-20240525115912353

上方这些东西是在HTML的标签上写的ref,如果在组件标签上写ref就有事了

==二编==:这里的截图有点含糊,在App.vue中给person逐渐添加了一个ref标签,然后点击打印标签的value,但是发现可以打印出来,但是看不到数据,如果想要看到数据,需要在person组件中添加defineExpose,指定可以获取到的变量名称。而且这个defineExpose可以不引入,因为其是宏函数,宏函数在Vue3内不引用也可以使用

image-20240525121116370

总结:ref用在普通的DOM标签上,获取的是DOM节点;用在组件标签上,获取的是实例对象

尝试在小程序中实现,发现小程序中虽然有这种写法的提示词,但是并不支持这种查询,因为这是web的API

1
2
3
wx.createSelectorQuery().select('.title').boundingClientRect(function(rect) {
console.log(rect)
}).exec()

TS中的接口、泛型、自定义类型

先看一下TS文件

image-20240525195139100

结合看一下

image-20240525194601451

image-20240525200151911

定义接口时 表示可选

image-20240525200436120

props的使用

接受父页面的传参

image-20240525201909744

简单介绍一下Vue中的属性问题

image-20240525203322432

简单看一下for循环吧,和小程序有点不一样

image-20240525204531540

限制类型+限制必要性

image-20240525210047384

指定默认值

image-20240525211104015

生命周期

Vue组件实例在创建时要经历一系列的初始化步骤,在此过程中Vue在合适时机,调用特定的函数,从而让开发者有机会在特定的时期运行自己的代码。

生命周期、生命周期函数、生命周期钩子

组件的一生:==创建、挂载、更新、销毁==

  • 创建————调用特定的函数(created)
  • 挂载————调用特定的函数(mounted)
  • 更新
  • 销毁

Vue2的生命周期

不包括路由的话是有八个生命周期钩子,但是真实情况是大于八个的

  • 创建——(创建前 beforeCreate,创建完毕 Created)
  • 挂载——(挂载前 beforeMount,挂载完毕 Mounted)
  • 更新——(更新前 beforeUpdate,更新完毕 Updated)
  • 销毁——(销毁前 beforeUnmount,销毁完毕 Unmounted)

因为我的Hbuilder X没有成功运行Vue2,这里不做图片演示,只有文字说明,创建和挂载组件是只能执行一次的,但是可以更新很多次,如果想要看销毁的话,可以在App.vue中使用

这里解释了一下v-if和v-show的区别

image-20240525222800323

Vue3的生命周期

  • 创建——(创建 setup)
  • 挂载——(挂载前 onBeforeMount,挂载完毕 onMounted)
  • 更新——(更新前 onBeforeUpdate,更新完毕 onUpdated)
  • 销毁——(销毁前 onBeforeUnmount,销毁完毕 onUnmounted)

Vue3常用的钩子挂在完毕、更新完毕、卸载之前

image-20240526093208019

image-20240526093444459

image-20240526094106548

App.vue的挂载要在person组件的创建之前,很好理解吧

补课微信小程序生命周期

小程序从启动到销毁的过程

一个小程序完整的生命周期由 ==应用生命周期==、==页面生命周期== 和 ==组件生命周期== 三部分组成的

应用生命周期

应用生命周期函数需要在app.js文件的App()方法中进行定义,App()方法必须在app.js中进行调用,主要用来注册小程序

应用生命周期函数由 onLaunch onShow onHide 三个函数组成

image-20240526102014503

页面生命周期

访问页面——onLoad(监听页面加载)——onShow(监听初次渲染完成)——onUnload(监听页面卸载)

注意跳转到其他页面是是否关闭本页面,不同的路由,再次返会后,触发的生命周期函数不同,自己get一下

补充一个细节

  • tabber页面之间的跳转并不会销毁页面
  • 如果是左上角的返回键,返回上一个页面,会销毁当前页面

之所以补课微信小程序的生命周期是因为,当时看课没有这个,其实也不是很难,平时开发的时候多少也都了解了一下

组件的生命周期

组件的生命周期函数需要在 lifetimes 字段内进行声明

组件的生命周期函数有五个:created attached ready moved detached

  • 组件创建完毕——created
  • 组件解析完成,挂载到页面后——attached
  • 组件被销毁——detached

组件的生命周期主要用于component自定义组件,因为component自定义组件不太了解,先按下不表

但是一下原生组件,如view是没有生命周期的,如果view在组件内,其生命周期会受到外在组件的影响

自定义Hooks

hooks的命名规范 useXxxx

先写一个获取狗图片的功能,这个网站,每次打开获取到的狗都不同,利用这个来写

https://dog.ceo/api/breed/pembroke/images/random

image-20240526153051669

现在解决蓝字问题,真正扣题了

image-20240526154703753

路由(route)

  • 路由就是一组 ==key、value== 的对应关系
  • 多个路由,需要经过 ==路由器== 的管理

前端路由可以实现 SPA(单页面) 应用,从始至终只有一个HTML,如果这个HTML还想实现炫酷的切换,这个时候就要用到路由了

SPA一般有一个左边的导航区,和右边的展示区,点击左边导航区的内容展示区发生相应的变化,且页面不抖动

原理:

点击某个导航项时,上方路径发生变化,然后被路由器监视到变化,然会路由器进行规则匹配,如果匹配成功,组件展示在展示区,重新匹配后,将原组件卸载,挂载新匹配的组件

==路径至关重要,如果不能引起路径的变化,路由器无法变化==

写一个路由

写路由要做的事

  1. 导航区、展示区
  2. 请来路由器
  3. 制定路由的具体规则(什么路径对应什么组件)
  4. 形成一个一个的【???.vue】

一、导航区、展示区

image-20240526165939915

二、请来路由器

安装路由器

1
npm i vue-router

在Vue3的环境中,不用加router的版本号,用最新的即可

创建,使用路由器

image-20240526171219169

写完这个这里多了应该routes

image-20240526171740303

三、

三四一起写

image-20240526173847185

两个注意点

  1. 路由组件通常放在 pages 或者 views 文件夹,一般组件通常放在 components 文件夹
  2. 通过点击导航,视觉效果上消失的路由组件,是被卸载了的,需要的时候再去挂载

区分路由组件和一般组件:如果有一个 Demo.vue

一般组件:亲手写标签出来 <demo></demo>

路由组件:靠路由的规则渲染出来的

1
2
3
routes:[
{path:'/demo' , components:Demo}
]

路由器的工作模式

一般来说给用户使用的要考虑美观需要使用history模式,后台管理使用hash

history模式

  • 优点:URL更加美观,不带有 # ,更接近传统网站的URL
  • 缺点:后期项目上线,==需要服务器配合处理路径问题==,否则刷新会有404错误
1
2
3
4
5
6
7
8
//写法
Vue2:mode:'history'
Vue3:
const router = createRouter({
history:createWebHistory()
router:[]
})
React:BrowserRouter

hash模式

  • 优点:兼容性更好,因为不需要服务器端处理路径
  • 缺点:URL带有 # 不太美观,且在 SEO 优化方面相对较差
1
2
3
4
5
//别忘了引用
const router = createRouter({
history:createWebHashHistory()
/****/
})

to的两种写法

1
2
<router-link to="/news" active-class="active">新闻</router-link>
<router-link :to="{path:'/about'}" active-class="active">关于</router-link>

image-20240526203416484

命名路由

rt

image-20240526204529640

嵌套路由

准备路由

image-20240526212435860

前端搞上

image-20240526212829763

如果直接写 detail 或者 /detail 的话,会匹配不到路由,匹配失败

路由传参

query参数

image-20240527104116519

image-20240527110436396

image-20240527112057014

params参数

个人感觉params参数很一般,建议query一般梭

  • 传递params参数时,若使用to对象写法,请务必使用 name 配置项,不能使用 path
  • 传递params参数时,需要提前在规则中占位

image-20240527114235375

image-20240527115140815

路由的props配置

用来解决之前说过的很长,很不美观的问题

props有三种写法

一、

image-20240527121219010

红字打错了,是==占位的三个params参数==,

这种写法有局限性只是params参数,如果是query参数这个写法是不可以的

二、函数写法

image-20240527122501118

query和params传参都行

三、对象写法

没意义

image-20240527122814484

这里主要是路由的props,为什么不说组件的props呢,因为没必要,路由组件是没有机会写标签的 <Detail/> 这个标签不会出现,而组件可以,爱怎么写怎么写,组件的props就是 <Person a="100" /> 的那个a,一般组件直接写在标签上就行了

replace属性

路由跳转的时候会操作浏览器的历史记录,路由操作浏览器的历史记录有两个动作,一个是push,另一个是replace

image-20240527135958523

设置路由的replace也很简单,在导航栏加一个 replace 即可

image-20240527140206218

编程式导航

编程式导航:脱离 <route-link> 进行路由跳转, <route-link> 本质是一个 <a> 标签

需求:写一个按钮点击也可以跳转到对应路由

image-20240527150003235

image-20240527151158558

在最后说一下编程式路由导航的作用吧

如果我想实现登录成功后自动跳转、打开三秒后跳转、鼠标滑动跳转等等功能,这显然是一个a标签无法做到的,这个时候就需要编程式导航了

重定向

重定向,让指定的路径重新定位到另一个路径

之前一直没说,一个很恶心的地方,就是页面打开是没有默认项的,现在就需要重定向解决

image-20240527151549602

image-20240527152221423

这里是为了方便观察层级放到上面来到,一般写在路由下面

小程序一些思考

如果是只看样式的话,路由有点像微信小程序的tabBer,但是又有区别tabBer切换页面的时候不会卸载掉,而路由切换组件的时候是要卸载掉的,于是我有想到了另一个东西,也是我经常写的,其视觉效果是相同的。单纯的view是没有生命周期的。但是这里是wx:if来控制显示的,并不是hidden隐藏,if来控制显示是进行销毁的,而hidden不销毁。

image-20240526200730897

先定义一个这么个导航区,然后在后端定义一个 status ,通过点击事件来改变 status 的值,利用三元判断来定义选中后的样式,达到视觉上的选中效果,然后利用 <block wx:if="{{status==1}}"></block> 这个小判断来控制元素的显示

码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//wxml
<view class="nav">
<view class="slike1"></view>
<view class="slike2"></view>
<view class="nav-view{{status==1 ? 'status':''}}" bind:tap="cells1">宣传文章</view>
<view class="nav-view{{status==2 ? 'status':''}}" bind:tap="cells2">公安文件</view>
<view class="nav-view{{status==3 ? 'status':''}}" bind:tap="cells3">线上讲座</view>
<view class="nav-view{{status==4 ? 'status':''}}" bind:tap="cells4">知识竞赛</view>
</view>
<block wx:if="{{status==1}}">
<view>1</view>
</block>
<block wx:if="{{status==2}}">
<view>2</view>
</block>
<block wx:if="{{status==3}}">
<view>3</view>
</block>
<block wx:if="{{status==4}}">
<view>4</view>
</block>
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
//js
page({
data: {
status: 1
},
cells1() {
this.setData({
status: 1
})
},

cells2() {
this.setData({
status: 2
})
},

cells3() {
this.setData({
status: 3
})
},

cells4() {
this.setData({
status: 4
})
},
})

随着继续的学习,我感觉嵌套的路由在小程序中实现类似效果似乎也不是很难,但是,到传参这里,没有了上面头绪,因为我这种方式虽然也是不同组件的切换,但是不需要去找组件的路径,那么就没有url来提供给我传参的机会,那么对于实现前端的变化我的想法是这样的,取消 <block> 标签,只写一个 view 标签,定义一个demolist来显示数据,通过不同点击事件来改变demolist的值,来达到相同的效果,但是我思考了一下这似乎并不算传参,只是效果与其类似而已。也可以参考vue的编程式导航的内容,给点击事件传一个参,然后那这个参去拿写好的数据,但是这样感觉还不是很像。

至于编程式导航,一开始就是了,通过各种事件来实现效果,重定向也是有的,在data中默认了status为1

Pinia

集中式状态(数据)管理 redux vuex pinia

集中式状态管理:当写App的时候,会有各种各样的组件,还会有组件嵌套啊什么的,这个时候如果我在抽奖页面想用到登录页面的登录信息,这个时候就需要集中式状态管理了。但是不要是一个数据都交给数据管理,要把那些共享的数据交给集中式状态管理,而不是组件自身的数据

准备一个效果

先敲一个加减效果吧

image-20240527163724136

再敲一个与之前生成随机狗类似的生成随机土味情话

网站地址:https://api.uomg.com/api/rand.qinghua?format=json

注意一下这里下载了一个随即库

1
npm i nanoid

image-20240527172117573

搭建pinia环境

下载pinia

1
npm i pinia

在main.js文件中引入

image-20240527173739270

成功后插件内多了一个菠萝

image-20240527173608462

存储、读取数据

  • Store是一个保存:状态、业务逻辑 的实体,每个组件都可以读取、写入它
  • 它有三个概念:state getter action ,相当于组件中的 data computed methods
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 引入defineStore用于创建store
import { defineStore } from "pinia"

// 定义并暴露一个store
export const useCountStore = defineStore('count',{
// 动作
actions: {},

// 状态
state(){
return {
// 数据
}
},

//计算
getters:{}
})

按照规范,要在src目录下创建一个store目录,store可以理解为pinia世界的老大

这里注意一下书写规范,和hooks很像,命名为use+组件名+Store

image-20240527175925942

在组件中引用,说一下关于ref的一个注意点

image-20240527175800749

store在插件中显示,但是有一点,只要使用了的才会显示,没使用过的不会显示,成功了还有小菠萝

image-20240527200157778

修改数据的三种方式

pinia官网的话——Pinia符合直觉的 Vue.js 状态管理库

其实在写存储的时候,我已经按照我的直觉更改了,这是一种简单的方式,可以看到后端改的插件已经认可了

image-20240527201144046

感觉方式和hooks类似啊

image-20240527203540093

storeToRefs

张的就像toRefs,解构使用的,toRefs解构store数据会把所以的方法、数据等等,所有的东西都变成ref响应式数据,很不合理。这个时候就需要storeToRefs了

值得注意的是storeToRefs这个API 是pinia提供的,因此引入需要

1
import { storeToRefs } from "pinia"

image-20240527210507661

getters的使用

image-20240527211537238

$subscribe的使用

subscribe 订阅 他的作用很像watch监视

这里写一个很重要的功能

image-20240527213856073

这里传的字符串严格来说叫密钥,可以在本地储存空间找到

image-20240527214017213

看一下ts中

image-20240527215225761

store组合式写法

有点类似hooks的形式,自己体会一下。

image-20240527220044203

1
return {talkList,getlove}

组件通信

组件之间互相传递数据

props

和路由的props类似

概述:props是使用频率最高的一种通信方式,常用于 父<—>子

  • 若父传子:属性值是非函数
  • 若子传父:属性值是函数

尽量不要在嵌套使用props。例如父要传孙,不要父传子、子传孙的

上面的说法多少有点抽象,看一下下面代码,父传子是定义的car,显然这不是一个函数,就可以把数据传到子组件。而子传父需要父组件内定义一个方法,将函数传给子,通过子组件的调用函数将数据传给父组件

image-20240528211804113

昂,还有一个小的点 h4 标签上有一个 v-show 把toy的值给他,没来的时候是空字符串,会被当作false算,来了是toy就相当于true了,一个小把戏

自定义事件

简单介绍一下 $event

懒得弄了,直接敲代码在这吧

==自定义事件专门用来子传父==

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
<template>
<div>
<h3></h3>
<button @click="test">点我</button>
</div>
</template>

<script>
function test(a) {
console.log(a)
}
//如果是这样不传递任何参数,但是接受了一个参数,就会出现一个事件对象event。
//如果说呢,我传递了参数a,b,c这个时候就接收不到事件对象了,需要手动在传参的位置添加 $event ,然后再把这个 $event 传递给某一个参数。
</script>



//说一个骚一点的赋值操作
<template>
<div>
<h3>{{ str }}</h3>
<button @click="str = 哈哈">点我</button>
</div>
</template>

<script>
import { ref } from 'vue'

let str = ref('你好')
// 这个时候也是实现了点击按钮进行更改赋值的操作

//如果呢,贱一点,把str变为 $event 还是可以成功的,但是前面数据很变得很奇怪,试一下就知道了
</script>

image-20240528220430557

这里说一下自定义事件的命名规范,HTML文件对大小写是不敏感的,因此驼峰式命名法 SendToy 将会变成 sendtoy 再到后端使用的时候会出问题,因此,自定义事件的命名推荐使用 kebab-case 羊肉串式命名法 send-toy

mitt

mitt可以实现==任意组件通讯==,大小只有200b

首先安装mitt

1
npm i mitt

一般按照规范会有tools或者utils目录

简单写一个emitter

image-20240528222059030

在main中引入一下,一行就行

image-20240528222305087

image-20240528223422399

感受一下任意组件通讯

父组件没啥用,截图也放不下了,代码贴这吧

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div class="father">
<h3>父组件</h3>
<Child1/>
<Child2/>
</div>
</template>

<script setup lang="ts" name="Father">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
</script>

image-20240528225437649

v-model

不冷不热的知识:开发有一个UI组件库,在UI组件库中大量使用了v-model进行组件通信

v-model==既能父传子,也能子传父==

写一个简单的双向绑定,这是HTML标签上的v-model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//使用v-model来实现绑定
<input type="text" v-model="username">

<script>
import { ref } from 'vue'
let username = ref(0)
</script>

//以上呢是一个简单的v-model的双向绑定,但是不够清楚明白,写一下他的逻辑

<input type="text" :value="username" @input="username = $event.target.value">

<script>
import { ref } from 'vue'
let username = ref(0)
</script>

//这两种方式是等价的

image-20240529140752820

1
2
@input="username = (<HTMLInputElement>$event.target).value"
//断言方法

v-model用于组件标签上

image-20240529142419824

注释的第一个是语法糖,第二个是原理,但是组件内的代码还是都要写的

注意这里的事件直接就username = $event 了,原因是,之前的HTML标签中 $event 是标准的DOM元素,需要.target 但是这个是在组件内的自定义事件,$event 只是数据,因此不需要.target

对于原生事件,$event 就是事件对象 ,所以能.target

对于自定义事件,$event 就是触发事件时,所传递的数据,不能.target

也可以更改value,既然value可以更改那么就意味着组件标签==可以使用多个v-model==

image-20240529144454933

这个飘红是以为使用了props传参,可以是软件不认可这种写法,懒得再给他接受定义一下了

$attrs

概述:$attrs 用于实现当前父组件,向当前子组件的通讯(祖——孙)

一个小的点:

父给子传数据,子是需要接受的对吧,父子之间一般为props通信,因此呢,如果接受了插件中可以看到一个props,如果没接受的话,数据会存放在attrs中

image-20240529145801542

如果说呢,父给子传递数据了,但是没有接收,还想使用应该怎么办呢,这个时候就要用到$attrs了,如果说在前端直接写

1
2
<h2>{{ $attrs }}</h2>
//这样就可以直接打印出来所有未接受的数据的key-value键值对

简单说一下v-bind 单向绑定,v-bind内也可以写对象

1
2
3
4
<Child :a="a" :b="b" v-bind="{x:100,y:200}"/>

//这里的v-bind内的对象相当于
<Child :a="a" :b="b" :x="100" :y="200"/>

祖传孙,传数据,孙传祖传方法,老套路了

image-20240529155221912

$refs 与 $parent

概述:

  • $refs 用于父——子
  • $parent 用于子——父

$refs

image-20240529163122158

$parent

image-20240529163403963

源代码父组件内有一个多行注释,说明的是ref数据生么时候.value的问题,这个问题之前有说过,不在赘述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    let obj = reactive({
a:1,
b:2,
c:ref(3)
})
let x = ref(4)

console.log(obj.a)
console.log(obj.b)
console.log(obj.c)
console.log(x.value)

// reactive响应式数据会自动解包,但是ref不会,所以需要.value,reactive内的ref数据已经被解包,不需要再进行.value解包。
// 注意点:当访问obj.c的时候,底层会自动读取value属性,因为c是在obj这个响应式对象中的

provide 与 inject

概述:实现祖孙之间的数据传输,虽然attrs也可以做到祖孙之间的数据传输,但是会打扰到父组件,尽管代码很少

provide和inject实现的隔代通信不会打扰到中间的组件

中间还有一个子组件,正常引用即可,用不着子组件

image-20240529165404011

image-20240529170315154

pinia

之前说过,不再赘述

slot插槽

默认插槽

image-20240529175423402

具名插槽

默认插槽是说了萝卜和坑的概念,但是如果我需要写多个标签的时候,那么就需要用到具名插槽了

默认插槽是没有name配置项的,所以无论什么标签到都可以放到默认插槽内,但是具名插槽不行

image-20240529180331882

这里v-slot还有一个小的语法糖,就是直接写 #名称 ,如上图

其实默认插槽也是有名字的叫做 default ,一般不会写

作用域插槽

作用域插槽的写法很多,所以感觉很乱,习惯就好

image-20240529191723787

总结梳理一下:

image-20240529192635795

其他API

API很多,说几个实用的

shallowRef 和 shallowReactive

shallowRef 即浅层次的ref 深层次就不是响应式了

image-20240529194707481

shallowReactive 基本同理

image-20240529195827949

readonly 与 shallowReadonly

readonly 创建一个只读副本。

特点:

  • 对象的所以属性都为只读
  • 任何尝试更改的操作都会报错

应用场景:

  • 创建不可变的状态快照
  • 保护全局状态或配置不可更改

shallowReadonly 只作用域顶层属性

特点:

  • 只将对象的顶层属性设置为可读,对象内部的嵌套属性仍然是可变的
  • 适用于只需保护对象顶层属性的场景

image-20240529202813553

使用谷歌插件也可以通过图标看到是否可以修改

image-20240529202427375

toRaw 与 markRaw

toRaw

获取一个响应式对象的原始对象,toRaw返回的对象不再是响应式的,不会触发视图更新

官网描述:这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更新的特殊方法,==不建议保存对原始对象的持久引用,请谨慎使用==。

何时使用?——在需要将响应式对象传递给非 Vue 库或外部系统时,使用 toRaw 可以确保它们收到的是普通对象

image-20240529205852348

toRaw 去除响应式

image-20240529204605021

markRaw

标记一个对象,使其永远不会变成响应式对象

1
2
3
4
import { markRaw,car } from 'vue'
let car = markRaw({brand:'奔驰',price:100})
let car2 = reactive(car)
// 如果car没有使用markRaw包裹,car2会把car数据拿走成为响应式数据,但是标记car为不可响应后,car2也会失去响应式。

例如呢使用一些第三方库时,为了避免把一些第三方库的数据变成响应式对象,可以使用markRaw标记一下

customRef

防抖

作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制。

image-20240529212806137

如果说呢,我实现新输入一个内容后1s之后再在页面中显示,显然这个需要自定义ref

image-20240529213236876

image-20240529214226758

一般这种代码会写为自定义hooks,小写一下

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
import { customRef } from "vue";

//不将数据写死,找前端要两个参数,一个是初始值,另一个个是事件
export default function (initValue:string,delay:number) {
let timer : any
// track含义是追踪 trigger含义是触发
let msg = customRef((track, trigger) => {
return {
// get何时调用:msg被读取时调用
get() {
track()
return initValue
},
// set何时调用:msg被修改时调用
set(value) {
clearTimeout(timer)
timer = setTimeout(() => {
initValue = value
trigger()
}, delay);
}
}
})
return {msg}
//返回方法msg
}

定义好hooks调用十分清爽

image-20240529215059500

这个API的难点就在于理解 tracktrigger 两个底层为自定义ref准备好的方法

Vue3 新组件

Teleport

翻译:传送,游戏中TP就是他的缩写

Teleport是一种能够将我们的组件HTML结构移动到指定位置的技术

举个例子吧:

这里有个问题,我想在页面上写一个弹窗,但是呢这个弹窗想要按照屏幕大小定位在中间,通过css调整位置

image-20240529222020479

这里是通过position:fixed; 将弹窗对于浏览器窗口进行定位,而不是相对于其父组件,但是呢,这个时候有一个有意思的问题,如果我在父组件内使用了 filter

1
filter: saturate(100%);

image-20240529222230109

这个时候呢,弹窗就又会变为根据父组件大小来定位

这样的可以影响fixed的css还有其他的可以查一下,但是呢这个问题可以使用teleport来解决

image-20240529222946838

这就是teleport的作用——将HTML标签传送到指定位置

只是将结构传送出去,逻辑没影响

image-20240529222714543

即,我将饱和度设置为0,弹窗的颜色依旧是鲜艳的,因为弹窗的父组件已经改变了

teleport不止在这里使用,只是举个例子,可以大胆发挥想象搞事情

Suspense

概述:等待异步组件时渲染一些额外的内容,让应用有更好的体验。

https://api.uomg.com/api/rand.qinghua?format=json

image-20240530142221122

image-20240530143555487

等数据请求完成后,再渲染子组件,替换掉h2标签

image-20240530143727780

全局API转移到对象

  • app.component
  • app.config
  • app.directive
  • app.mount
  • app.unmount
  • app.use

之前在Vue2 中可以vue.xxx 的内容已经变为了 app.xxx

app.component 全局组件

在main.js文件中注册,在任意页面都可以使用

image-20240530144644628

app.config 全局变量

image-20240530145954859

在main.js内输入注释内的内容会使页面无法正常加载,但是没有这个的话,前端 x 处会飘红

这个全局变量不推荐大量使用

app.directive 全局指令

注册全局指令

image-20240530152446862

app.mount

mount不用说,很重要,挂载整个应用的

app.unmount

unmount卸载mount

app.use

这个之前就使用过,作用是安装插件

image-20240530152740768

Vue3的非兼容性改变

这个是Vue3和Vue2改变的地方,可以直接上官网看非兼容性改变

  • 过渡类名 v-enter 修改为 v-enter-from 、过度类名 v-leave 更改为 v-leave-from
  • keyCode 作为 v-on 的修饰符支持
  • v-model 指令在组件上的使用已经被重新设计,替换掉了 v-bind.sync
  • v-if 和 v-for 在同一级元素身上使用的优先级发生了变化
  • 移除了 $on、$off 和 $once 的实例方法
  • 移除了过滤器 filter
  • 移除了 $children 实例 propert
  • ……

Vue2中v-for的优先级比v-if的优先级高,不能同时用在一个标签上,但是在Vue3中把v-if的优先级调整的比v-for的优先级高

完结撒花,中间也有不少关于微信小程序的思考,这里记录一下

土味情话网址:https://api.uomg.com/api/rand.qinghua?format=json

随机狗子网址:https://dog.ceo/api/breed/pembroke/images/random