vue3学习笔记
Vue3学习笔记
创建Vue3工程
基于vite进行工程的创建。官方链接
1 | npm create vue@latest |
Vue3核心语法
选项式API和组合式API
vue2:选项式API
vue3:组合式API
选项式API中,数据、方法、计算属性等等是分散开的,若想添加需求,需要到不同的选项中修改内容,不便于维护和复用。
setup
setup函数中的this
是undefined
,
在setup中直接声明变量不是响应式的。
setup的执行在beforeCreate
之前。
setup的返回值也可以是一个渲染函数。
1 | setup(){ |
面试题:setup和传统的配置项(data,methods)能不能同时写?
可以同时写,data可以读到setup中的数据。
1 | data(){ |
语法糖:
1 | <script setup> |
如何在只想写一个setup的情况下还实现对组件的命名呢?
可以使用插件来实现。
- 安装
1 | npm i vite-plugin-vue-setup-extend -D |
- 配置
vite.config.js
1 | import VueSetupExtend from 'vite-plugin-vue-setup-extend' // 添加 |
- 使用
1 | <script setup name='test'> |
ref和reactive
ref
用来定义基本类型数据、对象类型数据。
reactive
用来定义对象类型数据。
- 区别
ref
创建的对象必须试用.value
(可以使用volar插件自动添加)
reactive
在重新分配对象的时候会失去响应式。(可以使用Object.assign
进行整体替换)
1 | const student = reactive({ name: '小玉', age: 20 }) |
- 使用原则
对于基本类型的响应式数据,使用ref
层级不深的响应式数据,两者都可以
层级较深的响应式数据,推荐使用reactive
torefs和toref
1 | // torefs 和 toref |
计算属性
计算属性有缓存,多次使用只会调用一次。(数据未改变时)
1 | import { ref, computed } from 'vue' |
watch
watch只能监视以下四种数据:
ref
定义的数据;
reactive
定义的数据;函数返回一个值;
一个包含上述内容的数组。
- 情况1,监视
ref
类型的基本类型数据:
1 | import { ref, watch } from 'vue' |
- 情况2,监视
ref
定义的对象类型的数据。
若修改的是对象中的属性,
newValue
和oldValue
都是旧值,因为他们都是同一个对象。若修改的是整个
ref
定义的对象,newValue
是新值,oldValue
是旧值,不是同一个对象。
1 | // 情况2 监视ref定义的对象类型数据 |
- 情况3,监视
reactive
定义的对象类型。
1 | // 情况3 监视reactive定义的对象类型数据 |
- 情况4,监视
ref
或者reactive
定义的数据类型数据中的某个属性,注意:
如果该属性不是对象类型,需要写成函数形式。getter
如果该属性依然是对象类型,可以直接编写,但建议写成函数。
1 | <script setup> |
- 监视多个值。
1 | // 监视多个属性 |
watchEffect
1 | import { ref, watchEffect } from 'vue' |
标签的ref属性
用于绑定DOM元素,如果绑定的是组件,则获得的是组件实例。父组件可以获取子组件通过defineExpose
导出的内容。
1 | <template> |
TS接口和泛型
types/index.ts
1 | export interface Person { |
demo.vue
1 | import { type Person,type PersonList } from "@/types"; |
Props
父组件
1 | <template> |
子组件
1 | import { defineProps, withDefaults } from 'vue' |
生命周期
vue2
生命周期 hook 创建前;创建完毕 beforeCreate();created() 挂载前;挂载完毕 beforeMount();Mounted() 更新前;更新完毕 beforeUpdate();updated() 销毁前;销毁完毕 beforeDestory();destroyed() vue3
setup的执行在
beforeCreate
之前。生命周期 hook 挂载前;挂载完毕 onBeforeMount(), onMounted() 更新前;更新完毕 onBeforeUpdate(),onUpdated() 卸载前;卸载完毕 onBeforeUnmount(),onUnmounted()
父子组件中,子挂载在父挂载之前完成。因此App组件是最后挂载的。
自定义hooks
useSum.ts
1 | import { ref, type Ref } from 'vue' |
demo
1 | <template> |
路由
基本使用
src/router/index.ts
1 | import { createRouter, createWebHistory } from 'vue-router' |
main.ts
1 | import { createApp } from 'vue' |
在需要切换的区域:active-class="activate-nav"
指定激活时的css。
1 | <template> |
组件切换时,视觉上消失的组件实际上是被卸载掉了。
工作模式
- history
- 优点:URL美观,路径不带
#
,接近传统的网站URL。 - 缺点:项目上线需要后端配合处理路径问题。否则主页面之外的其他页面刷新会报
404
。
- 优点:URL美观,路径不带
- hash
- 优点:兼容性好,不需要服务器处理路径。
- 缺点:URL不美观,SEO优化方面较差。
To C常用history模式,后台管理常用hash模式。
to
的两种写法
- 写法一
<RouterLink to="/">首页</RouterLink>
- 写法二
<RouterLink :to="{path:'/home'}">首页</RouterLink>
命名路由
顾名思义就是给路由取一个名字。
1 | const router = createRouter({ |
配置名字以后,在进行路由跳转的时候可以通过名字进行。
<RouterLink :to="{name:'zhuye'}">首页</RouterLink>
嵌套路由
使用children
配置项配置子路由。
1 | const router = createRouter({ |
Home页面响应位置使用<RouterView />
标签。
参数传递
query
参数在URL里面,形如:
/home/news?id=10
1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<h1>首页页面</h1>
<h3>二级导航</h3>
<nav>
<span v-for="item in news" :key="item.id">
<RouterLink
:to="`/home/content?content=${item.content}`">
{{ item.name }}
</RouterLink>
</span>
</nav>
<RouterView />
</template>参数项太多会很恶心,可以换一个方法。
1
2
3
4
5
6
7
8
9
10
11
12
13<span v-for="item in news" :key="item.id">
<RouterLink
:to="{
path:'/home/content',
query:{
id:item.id,
content:item.content,
name:item.name
}
}">
{{ item.name }}
</RouterLink>
</span>读取参数。
1
2
3
4
5
6
7<template>
<h3>资讯内容:{{ route.query.content }}</h3>
</template>
<script setup>
import { useRoute } from "vue-router"
const route = useRoute()
</script>params
配置路由。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const router = createRouter({
history: createWebHistory(),
routes: [
{
name: "zhuye",
path: '/home',
component: Home,
children: [
{
path: 'content/:id/:content',
component: content
}
]
}
]
})传递参数。
1
2
3
4
5<RouterLink
class="navItem"
:to="`/home/content/${item.id}/${item.content}`">
{{ item.name }}
</RouterLink>另一种写法,此处必须使用
name
参数。1
2
3
4
5
6
7
8
9
10
11<RouterLink class="navItem"
:to="{
name: 'xiangqing',
params:{
id:item.id,
content:item.content
}
}">
{{ item.name }}
</RouterLink>获取参数。
1
2
3
4
5
6
7
8
9<template>
<h3>资讯内容:{{ params.id + "," + params.content }}</h3>
</template>
<script setup>
import { useRoute } from "vue-router"
import { toRefs, toRef } from "vue"
const route = useRoute()
const { params } = toRefs(route)
</script>路由规则props配置
当
props
设置为true
时,route.params
将被设置为组件的props
。
1 | const router = createRouter({ |
当props
写成一般对象时,等同于给组件设置props。
1 | const router = createRouter({ |
当props
写成函数时,函数的参数为route
,可以返回props的函数。
1 | const router = createRouter({ |
组件内声明props即可。
1 | <template> |
replace属性
在history模式下,浏览器的访问记录会以栈的形式维护。导航到新的页面后,可以通过后退返回到上个记录。
使用replace
会在当前栈进行原地替换。
1 | <RouterLink replace to="/home">首页</RouterLink> |
编程式路由导航
1 | <script setup> |
重定向
1 | const router = createRouter({ |
状态管理
组件间共享数据。
pinia的安装和配置。
1 | yarn add pinia |
1 | import { createApp } from 'vue' |
定义。
1 | import { defineStore } from 'pinia' |
使用。
1 | <template> |
修改。
第一种方法:直接修改。
1 | function add() { |
第二种方法:patch。适合一次修改多个值。
1 | function add() { |
第三种方法:action。在store中定义,可以在修改值之前添加约束。
1 | const useCounterStore = defineStore('count', { |
storeToRefs
直接结构出来store的值,会失去响应性,如下。
1 | <template> |
理论上可以通过toRefs
进行包裹,但会解构出来许多不需要的属性,因此推荐使用storeToRefs
。
1 | <template> |
getter函数
相当于共享的computed
。两种写法如下。
1 | const useCounterStore = defineStore('count', { |
subscribe
类似于watch
,数据发生改变的时候被调用。
1 | <script setup lang="ts"> |
组合式Store
1 | import { defineStore } from 'pinia' |
组件通信
父子组件通信
父组件->子组件。
父组件。
1 | <template> |
子组件。
1 | <template> |
子组件->父组件。
父组件。
1 | <template> |
子组件。
1 | <template> |
自定义事件
父组件。
1 | <template> |
子组件。
1 | <script setup lang="ts"> |
mitt
https://github.com/developit/mitt
实现任意组件间的通信,以下使用兄弟组件进行演示。com1传递数据给com2
安装mitt
1 | npm install --save mitt |
新建utils/mitt.ts
1 | import mitt from 'mitt' |
com2
1 | import emitter from '@/utils/mitt'; |
com1 点击按钮后会将6
作为参数传递到com2中。
1 | <template> |
V-model
https://cn.vuejs.org/guide/components/v-model.html
实现父子组件的双向绑定。
父组件。
1 | <template> |
子组件。
1 | <template> |
attrs
父组件通过v-bind传给子组件的值或方法,子组件若未defineProps
,可以通过$attrs
进行引用。
可以利用attrs完成祖组件和孙组件之间的数据传递。
如:grandfather.vue
1 | <father v-bind="{x:100,y:200}" /> |
father.vue
1 | <son v-bind="$attrs" /> |
son.vue
1 | defineProps(['x','y']) |
$refs
https://cn.vuejs.org/guide/essentials/template-refs.html
通过在子组件上定义ref可以获取子组件的实例对象,进而得到子组件通过defineExpose
导出的数据。
1 | <child ref="child" /> |
$refs
可以获取所有子组件的实例。
1 | <button @click="getAllChild($refs)">获取所有子组件</button> |
$parent
当前组件可能存在的父组件实例,如果当前组件是顶层组件,则为null
。
1 | <button @click="getFather($parent)">获取所有子组件</button> |
额外的解包细节
https://cn.vuejs.org/guide/essentials/reactivity-fundamentals.html#additional-ref-unwrapping-details
provide & inject
provide 和 inject 通常成对一起使用,使一个祖先组件作为其后代组件的依赖注入方,无论这个组件的层级有多深都可以注入成功,只要他们处于同一条组件链上。
祖先组件。
1 | <script setup> |
子孙组件。
1 | import { inject } from 'vue' |
pinia
略
slot
- 默认插槽
子组件。
1 | <template> |
父组件。
1 | <comp2> |
- 具名插槽
顾名思义是具有名字的插槽。
子组件。
1 | <template> |
父组件。
1 | <comp2> |
- 作用域插槽。
场景:数据由子组件来维护,但子组件内部的结构是由父组件来决定的。
例如:子组件内有一组数据,展示方式由父组件来决定。
子组件。
1 | <template> |
父组件。
1 | <template> |
具名 + 作用域 插槽。
子组件。
1 | <template> |
父组件。
1 | <template> |
其他API
shallowRef & shallowReactive
- shallowRef:
ref
的浅层作用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<script setup lang="ts">
import {shallowRef} from 'vue'
const data1 = shallowRef(0)
function changeData1(){
data1.value = 1 // 有响应性
}
const data2 = shallowRef({
name:'小红花',
age:18
})
function changeData2Name(){
data2.value.name = '小绿植' // 没有响应性
}
function changeData2(){ // 有响应性
data2.value = {
name:'小绿植',
age:22
}
}
</script> - shallowReactive:
reactive
的浅层作用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import { shallowReactive } from 'vue'
const data3 = shallowReactive({
name: '落叶',
age: 18,
student:{
name:'bobo',
age:20
}
})
function changeData3() {
data3.name = '芒果' // 有响应性
data3.student.name = 'yuan' // 这种情况下还是会更新
// 原因:https://github.com/vuejs/core/issues/5740
}
function changeData3name() {
data3.student.name = 'yuan' // 没有响应性
}readonly && shallowReadonly
- readonly:深层次的只读。
- shallowReadonly:浅层次的只读。
1 | import { ref,readonly,shallowReadonly } from 'vue' |
toRaw && markRaw
- toRaw:根据一个Vue创建的代理返回其原始对象。
使用场景:将响应式数据传递给外部系统使用的时候,可以确保外部系统收到的是原始值。
1 | import {reactive,toRaw} from 'vue' |
- markRaw:将一个对象标记为不可被转为代理。返回该对象本身。
1
2
3
4
5
6
7import {reactive,markRaw} from 'vue'
const data = markRaw({
name:'小红花',
age:18
})
const data2 = reactive(data) // 没有响应性customRef
why? 自带的ref的功能就是:数据发生改变,然后更新视图。
如果我想在数据发生改变后,做一些操作呢?比如延迟1s后执行更新。
使用自定义ref实现。
1 | import { customRef } from 'vue' |
使用。
1 | <template> |
Teleport
<Teleport>
是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。
例如:
子组件是一个模态框,在父组件中使用。
子组件的CSSposition: fixed
能够相对于浏览器窗口放置有一个条件,那就是不能有任何祖先元素设置了transform
、perspective
或者filter
样式属性。
如果父组件使用了以上样式属性,模态框的位置会出问题,因此,处理方式是将子组件转移到其他DOM结构,可以使用
1 | <template> |
Suspense
https://cn.vuejs.org/guide/built-ins/suspense.html
试验性内容。
全局API转移到应用对象
https://cn.vuejs.org/api/application.html
app.component()
app.config.globalProperties
app.directive()
…