vue3vue2

  1. 性能的提升
    • 打包大小减少41%
    • 初次渲染快55%, 更新渲染快133%
    • 内存减少54%
  2. 源码的升级
    • 使用Proxy代替defineProperty实现响应式
    • 重写DOM的实现和Tree-Shaking
  3. 支持typescript
  4. 新的特性
    • Composition API (组合式api)

      setup refreactive computedwatch

    • 新的内置组件

      Teleport Suspense Fragment

    • 其他改变

      新的生命周期钩子:setup beforeCreate created onBeforeMount onMounted onBeforeUpdate onUpdated onBeforeUnmount onUnmounted
      data选项应始终被声明为一个函数

创建vue3工程

vue-cli

查看 vue-cli版本号, 确保@vue/cli版本在4.5.0以上

1
vue --version

安装升级你的vue-cli

1
2
3
4
5
6
7
8
9
# 安装
npm install -g @vue/cli

# 升级
npm update -g @vue/cli

# 卸载后在安装
npm uninstall -g @vue/cli
npm install -g @vue/cli

使用vue-cli创建vue3工程

1
2
3
4
5
vue create vue_test

# 然后选择版本
# `> 3.x`
# `2.x`

vite(推荐)

创建vue3项目

1
npm create vue@latest

按照命令提示进行选择

1
2
3
4
5
6
7
8
9
10
11
12
✔ Project name: … <your-project-name>
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add an End-to-End Testing Solution? … No / Cypress / Playwright
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes

Scaffolding project in ./<your-project-name>...
Done.
进入项目
1
2
3
cd <your-project-name>
npm install
npm run dev

启动时,如果遇到以下错误!

1
2
3
4
5
(node:43520) UnhandledPromiseRejectionWarning: SyntaxError: Unexpected token '??='
at Loader.moduleStrategy (internal/modules/esm/translators.js:149:18)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:43520) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:43520) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

原因是逻辑空赋值(??=)ES2021的语法,node v15.0.0以上才支持逻辑空赋值(??=)的语法。之前为了兼容旧代码使用的node版本是14使用nvm切换16的node,成功解决

如果使用`node v14`已经安装了`node_modules`,先删除`node_modules`,然后在切换版本`v16`

1
2
3
4
rm -r node_modules
# 切换版本
nvm use v16
nvm alias default v16

vue3工程目录结构

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
├── public
│   └── favicon.ico
├── src
│   ├── App.vue
│   ├── assets
│   │   ├── base.css
│   │   ├── logo.svg
│   │   └── main.css
│   ├── components
│   │   ├── HelloWorld.vue
│   │   ├── TheWelcome.vue
│   │   ├── WelcomeItem.vue
│   │   └── icons
│   │   ├── IconCommunity.vue
│   │   ├── IconDocumentation.vue
│   │   ├── IconEcosystem.vue
│   │   ├── IconSupport.vue
│   │   └── IconTooling.vue
│   └── main.ts
├── env.d.ts
├── index.html
├── package-lock.json
├── package.json
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── README.md
└── vite.config.ts

index.html

  • vite项目中, index.html是项目中的入口文件,在项目最外层!
  • 加载index.html后,vite解析<script type="module" src="/src/main.ts"></script>指向的javaScript!
  • vue3项目中通过createApp方法创建实例!

env.d.ts

env.d.ts 用来识别文件类型的.txt .ts

如果main.ts文件引入App.vue时出现以下错误:

找不到模块“./App.vue”或其相应的类型声明。

env.d.ts文件中 按住commnd 或者 ctrl 点击 vite/client 进入 文件类型(client.d.ts)配置!加入以下配置代码!

1
2
3
4
5
6
7
8
// vue3 报错提示 找不到模块“./XXX.vue”或其相应的类型声明
// 报错原因:typescript 只能理解 .ts 文件,无法理解 .vue文件
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

tsconfig

typescript相关配置文件

OptionsApi & ComponsitionApi

  • vue2 的设计风格是 Options(配置)风格!
  • vue3 的设计风格是 ComponsitionApi(组合)风格!

选项式->optionsApi

optionsApi 选项式 api 可配置 api!
data methods watch computed

Options类型的 API, 数据 方法 计算属性等,是分散在:data methods watch computed 中的,若想新增或者修改,就需要分别改动data methods watch computed地方!

Options gif图片

图片地址来源: https://blog.csdn.net/m0_45911911/article/details/126023757

组合式->componsitionsApi

composition gif图片

图片地址来源: https://blog.csdn.net/m0_45911911/article/details/126023757

setup 函数

setup函数是vue3组合式api的入口函数!将options选项式中的data methods等,全部整合到setup函数中!

options & setup 写法对比

Options Api写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="options-page">
<h2> vue2 OptionsPage </h2>

<div class="name">姓名: {{name}}</div>

<div class="age">年龄: {{age}}</div>

<div class="emial">邮箱: {{email}}</div>

<div class="tel">电话: {{tel}}</div>

<div class="msg"> {{getMsg()}} </div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
export default {
name: "OptionsPage",
data(){
return {
msg: 'Welcome to Your HelloWord App Components',
name: "李四",
age: 16,
email: "mjw1046665443@163.com",
tel: "15121235122"
}
},
methods: {
getMsg(){
return this.msg;
}
}
}
</script>

Composition Api写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="setup-page">
<h2> vue3 setupPage </h2>

<div class="name">姓名: {{name}}</div>

<div class="age">年龄: {{age}}</div>

<div class="emial">邮箱: {{email}}</div>

<div class="tel">电话: {{tel}}</div>

<div class="msg"> {{getMsg()}} </div>
</div>
</template>
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
<script>
export default {
name: "SetupPage",
setup () {
// 这里的数据 仅是提供 template 展示,无法响应式修改!
let name = "张三"
let age = 18
let email = "1046665443@qq.com"
let tel = "15121235122"

let msg = "Welcome to Your SetupPage App Components"

function getMsg(){
return msg;
}

// 交付数据 提供 template 访问 data 以及 method
return {
name,
age,
email,
tel
}
// return () => "哈哈!" // 页面会被替换 哈哈! 俩字
}
}
</script>

setup注意事项

  1. setup中的this问题

    setup函数中无法使用this,函数中访问this时,此时为undefind!

  2. setup函数中return

    setup函数中需要 return 返回值,否则在页面中无法使用setup函数中的data methods等!`return值可以不是Object,也可以是return () => "哈哈",这样以来,界面只会显示哈哈,哪怕是template中定义的元素,都会被return覆盖掉!

setup函数和options api混合写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div class="setup-options-blend-page">
<h2> vue3 setupAndOptionsBlend </h2>
<div class="name">姓名: {{name}}</div>

<div class="age">年龄: {{age}}</div>

<div class="emial">邮箱: {{email}}</div>

<div class="tel">电话: {{tel}}</div>

<div class="msg"> {{getMsg()}} </div>

<div class="test"> setup & options 混合写法: {{test}} </div>

<div class="setup-name">试图在 options 中 访问 setup 中的 name 属性 {{ setupName }} </div>
</div>
</template>
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
<script>
export default {
name: "SetupAndOptionsBlend",
data(){
return {
test: "setup & options 混合写法!",
setupName: this.name
}
},
methods: {
test(){}
},
setup () {
let name = "张三"
let age = 18
let email = "1046665443@qq.com"
let tel = "15121235122"

let msg = "Welcome to Your SetupPage App Components"
function getMsg(){
return msg;
}
return {
name,
age,
email,
tel,
getMsg
}
}
}
</script>

注意事项:

  1. setup函数中无法使用this,函数中访问this时,此时为undefind,且无法访问data methods中定义的属性方法!
  2. 反而data methods可以通过this来访问setup中定义的属性方法!
  3. setup函数回调优先beforeCreate回调函数执行!

setup简写方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<!-- setup 简写 -->
<div class="setup-abbreviation">
<h2> vue3 简写方式 </h2>
<div class="name">姓名: {{name}}</div>

<div class="age">年龄: {{age}}</div>

<div class="emial">邮箱: {{email}}</div>

<div class="tel">电话: {{tel}}</div>

<div class="msg"> {{getMsg()}} </div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
<script setup>
let name = "张三"
let age = 18
let email = "1046665443@qq.com"
let tel = "15121235122"

let msg = "Welcome to Your setupAbbreviation App Components"
function getMsg(){
return msg;
}
</script>

简写方式可以省略return值, 以及export default写法, 但是无法定义组件名 name!

定义组件名的方式需要借助vite插件 vite-plugin-vue-setup-extend! 使用此插件可以帮我们实现定义组件名name的功能!

安装vite-plugin-vue-setup-extend

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

引入插件vite.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VitPluginVueSetupExtend from 'vite-plugin-vue-setup-extend'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), VitPluginVueSetupExtend()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});

配置组件name

1
<script setup name="component-name"></script>

ref reactive 响应式

[ref]基本数据类型 or 对象类型

注意: ref 支持 基本类型 和 对象类型!

  • 作用: 定义响应式数据
  • 语法: let xxx = ref('测试')
  • 返回值: RefImpl实例对象,简称ref对象ref,ref对象的value的属性是响应式的!
  • 注意点:
    1. JS中操作数据需要xxx.value,template模版中不需要xxx.value
    2. 对于let name = ref('张三') name不是响应式 name.value是响应式
    3. 如果ref类型数据存在reactive里,那么不需要通过.value去获取相应的值! 例如: reactive({a:1, b: ref(0)})
1
2
3
let obj = reactive({a:1, b: ref(0)})

console.log("obj.b", obj.b) // 0 不需要 obj.b.value

[reactive]对象类型

注意: reactive 仅支持 对象类型!

  • 语法:
    • 对象: let xxx = reactive({brand: "xiaoke", color: "red"})
    • 数组: let xxx = reactive(['red','blue', 'green'])

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<!-- 响应式 ref reactive -->
<div class="ref-reactive-demo">
<h2>ref reactive 响应式</h2>
<p>name: {{ nameRef }}</p>
<p>age: {{ ageRef }}</p>
<p>email: {{ emailRef }}</p>
<p>car: {{ car }}</p>
<p>carList: {{ carList }}</p>
<button @click="modify('李四', 20, 'mjw1046665443@qq.com')">修改</button>
<button @click="modifyCar">修改car的颜色</button>
<button @click="removeCar(1)">删除car其中一项</button>
</div>
</template>
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
<script setup name="RefAndReactive">
import { ref, reactive } from "vue"
let nameRef = ref("张三")
let ageRef = ref(18)
let emailRef = ref("1046665443@qq.com")

// 响应式 object
let car = reactive({ brand: "xiaoke", color: "blue", type: "suv" })

// 响应式数组
let carList = reactive(["ford", "bmw", "audi"])

function modify(name, age, email) {
console.log("modify", name, age, email)
nameRef.value = name
ageRef.value = age
emailRef.value = email
}
function modifyCar(){
car.color = "green"
}
// 删除响应式数组中第二个数据
function removeCar(index = 0){
carList.splice(index, 1)
}
</script>

ref & reactive

ref 可以定义基本数据类型响应式 也可以定义对象类型响应式, 但是reactive只能定义对象类型响应式!

1
2
3
4
5
6
7
8
9
10
11
12
13
import { ref, reactive } from "vue"
let name = ref("张三")
let obj = ref({ brand: "xiaoke", color: "blue" })
let arr = ref(['red', 'green', 'yellow'])

obj.value.color = "red";
arr.value[2] = "red";

let obj2 = reactive({ brand: "xiaoke", color: "blue" });
let arr = reactive(['red', 'green', 'yellow'];

obj2.color = "red"
arr[2] = "red"

ref: 返回值为 RefImpl实例,需要通过.value形式去访问!

reactive: 返回值为 Proxy代理对象,不需要通过.value形式去访问!

注意事项

  1. ref-> 如果值为基本类型 或者浅层次应用类型 可以使用,反之深层次引用类型不适合使用 / ref
  2. ref-> 自动添加.value插件(vscode搜 Volor)然后设置找到Auto Insert: Dot Value 打勾即可,当敲打变量时会区分 ref自动.value
  3. reactive -> 重新分配对象的时候会失去响应式,通过Object.assgin()处理即可!
    1
    2
    3
    4
    5
    6
    let obj = reactive({ brand: "xiaoke", color: "blue" });
    // obj reactive({ brand: "aaa", color: "bbbb" }) 这样是不行的 不响应
    obj = Object.assign(obj, { color: "red" }); // 响应式

    let obj = ref({ brand: "xiaoke", color: "blue" });
    obj.value = { color: "red" }; // 响应式

toRefs & toRef

toRefs

object 对象结构时, 将结构的属性进行响应式! 把reactive定义的对象 转换为 ref对象!

1
2
3
let obj = reactive({ brand: "xiaoke", color: "blue" });
let { brand } = obj;
brand = "aaa"; // 不是响应式

通过 toRefs 处理非响应式数据,

1
2
3
4
import { reactive, toRefs } from 'vue'
let obj = reactive({ brand: "xiaoke", color: "blue" });
let { brand } = toRefs(obj);
brand.value = "aaa"; // 响应式数据

let { brand } = obj; 等价于 let brand = obj.brand; obj.brand为响应式,当赋值与另一个变量时, brand变量不响应式!

let { brand } = toRefs(obj); 相当于将object中每个属性加了ref(obj.attr),因此brand.value即可响应式!

toRef

toRef将单个object中的某个属性改为响应式!

1
2
3
let obj = reactive({ brand: "xiaoke", color: "blue" });
let n1 = toRef(obj, 'brand');
n1.value;

computed 计算属性

1
2
3
4
5
6
7
8
9
10
11
<template>
<div class="computed-page">
<h2>计算属性</h2>
姓: <input type="text" v-model="firstName"/> <br/>
名: <input type="text" v-model="lastName"/> <br/>

{{ fullName }}

<button @click="modifyFullName">修改名称</button>
</div>
</template>
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
<script setup name="Computed">
import { ref, computed } from "vue";
let firstName = ref("张")
let lastName = ref("三")

// 计算属性
let fullName = computed(() => {
return firstName.value + "-" + lastName.value
})
// or
// get and set
let fullName = computed({
get(){
return firstName.value + "-" + lastName.value
},
set(val){
firstName.value = val.split('-')[0]
lastName.value = val.split('-')[1]
}
})

function modifyFullName(){
fullName.value = "李-时"
}

</script>

watch 监听函数

watch 只能监视以下几种情况!

  • refreactive定义的数据!
  • 函数返回的某个值
  • 一个包含上述内容的数组

监听ref基本类型 和 对象类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="watch-page">
<h2> watch 监听 </h2>
<div class="name">姓名: {{name}}</div>

<div class="age">年龄: {{age}}</div>

<div class="emial">邮箱: {{email}}</div>

<div class="tel">电话: {{tel}}</div>

<div class="car">refCar: {{refCar}}</div>

<button @click="changeName">修改名称</button>
<button @click="changRefCar">修改汽车-ref</button>
</div>
</template>
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
<script setup name="WatchPage">
import { reactive, ref, watch } from "vue"
let name = ref("张三")
let refCar = ref({type: "xiaoke", color: "blue"})

// 监听基本类型
let stopWatch = watch(name,(newValue, oldValue) => {
console.log("基本类型监听",newValue, oldValue)
if( newValue == ??) {
// 停止监听
stopWatch()
}
})
// 监听ref对象
let stopWatch2 = watch(refCar, (newValue, oldValue) => {
console.log("ref对象类型监听",newValue, oldValue)
})
function changeName(){
name.value += "~"
}
function changeCar(){
// 修改对象本身 产生一个新的对象
refCar.value = {type: "benchi", color: "white"}
}
</script>

注意:

ref监听对象类型是监听的对象的引用地址! 若要监听对象内部属性需呀开启深度监视_deep!

若要修改ref对象类型中的属性,newValueoldValue都是新值,因为都是同一个对象地址!
若要修改ref 整个定义的对象, newValue 为新值,oldValue 为旧值,因为不是同一个对象地址了!

1
2
3
4
5
6
7
import { watch } from "vue"
watch(obj, ( newValue, oldValue ) =>{
console.log(newValue, oldValue)
}, {
deep: true,
immediate: true
})

deep: 深度监听, immediate: 立即执行

具体配置项请查看官方:

vue3官方文档 watch

监听reactive对象类型

reactive 只能为对象引用类型,且监听后,deep监听默认开启,无法取消默认深度监听!
reactive 无法修改对象本身, 仅能修改对象属性!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="watch-page">
<h2> watch 监听 </h2>
<div class="name">姓名: {{name}}</div>

<div class="age">年龄: {{age}}</div>

<div class="emial">邮箱: {{email}}</div>

<div class="tel">电话: {{tel}}</div>

<div class="car">reactiveCar: {{refCar}}</div>

<button @click="changeCarColor">修改color</button>
<button @click="changRefCar">修改汽车-ref</button>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup name="WatchPage">
import { reactive, ref, watch } from "vue"
let refCar = reactive({type: "xiaoke", color: "blue"})

// 监听ref对象
let stopWatch2 = watch(refCar, (newValue, oldValue) => {
console.log("ref对象类型监听",newValue, oldValue)
})
// 修改某个属性
function changeCarColor(){
refCar.color = "green"
}
// 修改对象的属性
function changeCar(){
// 无法直接修改对象本身
// refCar = {type: "benchi", color: "white"}
// refCar = reactive({type: "benchi", color: "white"})

// 以下方式 只能替换 原有对象已存在的属性值 对象还是那个对象,只是改了属性的值!
Object.assign(refCar, {type: "benchi", color: "white"})
}
</script>

监视对象某个属性

监视refreactive定义的对象类型中的某个属性:

  1. 若属性值不是对象类型,需要写成函数形势!
  2. 若属性值是对象类型,也可以写成对象形式,也可以写成(推荐)函数形势!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { reactive, ref, watch } from "vue"

let car = reactive({type: "benchi", color: "white", characteristic: { size: "big" }})

// 属性为基本类型时
// no xxx car.type
// watch(car.type, (newValue, oldValue) => {})

// yes
watch(() => car.type, (newValue, oldValue) => {})

// 属性为对象时
// yes 推荐
watch(() => car.characteristic, (newValue, oldValue) => {})
// yes
watch(car.characteristic, (newValue, oldValue) => {})

监视对象多个属性

监听对象多个属性时, 可以写成数组形式! 基本类型函数形式, 对象类型函数,可对象!

1
2
3
4
5
6
import { reactive, ref, watch } from "vue"

let car = reactive({type: "benchi", color: "white", characteristic: { size: "big" }})

// 监听[car.type, car.color]基本类型, [car.characteristic]对象类型
watch([() => car.type, () => car.color, car.characteristic ], (newValue, oldValue) => {})

watchEffect监视内部使用的响应数据

watchEffect 监视回调函数内部使用的refreactive响应式数据!
watchEffect 会立即执行一次!

1
2
3
4
5
6
7
8
9
10
<template>
<div class="watch-effect-page">
<h2> watchEffect 监听 </h2>
<div>{{ zhangsan }}</div>
<div>{{ lisi }}</div>
<div>{{ person }}}</div>
<button @click="changeName">修改张三</button>
<button @click="addNewGames">修改john</button>
</div>
</template>
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
<script setup name="WatchEffectPage">
import { reactive, ref, watchEffect } from "vue";
let zhangsan = ref("张三")
let lisi = ref("lisi")

let person = reactive({
name: "John",
age: 20,
hobby: {
sport: "football",
music: "pop",
games: ['永结物件','帕鲁']
}
})
// watchEffect 监视 回调函数内部 所使用的 ref reactive响应式数据
watchEffect(()=> {
// zhangsan.value, person.hobby.games 这些变量和属性将会被监视
console.log("watchEffect", zhangsan.value, person.hobby.games)
})

function changeName(){
zhangsan.value += "~"
}
function addNewGames(){
person.hobby.games.push('飞车')
}
</script>

标签ref属性

标签ref属性, 可以在html标签中使用, 也可以在component自定义组件中使用!
被标识了html元素的ref属性, 可以通过ref()获取到,返回结果为DOM对象!
被标识了component组件的ref属性, 可以通过ref()获取到,返回结果为component组件实例对象!

dom元素标记ref属性

template

1
2
3
<template>
<h2 ref="titleRef"> watchEffect 监听 </h2>
</template>

script
1
2
let titleRef = ref()
console.log("titleRef", titleRef.value) // dom 元素

component元素标记ref属性

template

1
2
3
<template>
<Person ref="personRef" />
</template>

script
1
2
let personRef = ref()
console.log("personRef", personRef.value) // component 实例

获取ref标记的组件的数据

ref所标记的component组件, 无法直接通过组件实例获取子组件的数据,需要通过子组件的暴露 defineExpose后,才可访问!

父组件
template

1
2
3
<template>
<Person ref="personRef" />
</template>

script

1
2
let personRef = ref()
console.log("personRef", personRef.value) // component 实例

子组件
template

1
2
3
<template>
<Person ref="personRef" />
</template>

script
1
2
3
4
5
6
let a = ref(0)
let b = ref("111")
let c = ref("dddd")

// 向外暴露只组件数据
defineExpose({a,b,c})

注意: ref="nameRef"let nameRef = ref() 的变量名nameRef名称要对上!

属性传递

定义属性传递需要使用defineProps defineEmits方法实现!

props向下传递

defineProps

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
defineProps({
lisi: [String, Number],
zhangsan: {
type: String,
default: "default",
required: false,
validate: () => {}
},
obj: {
type: Object,
default: () => { retrun {}}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个
// 工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="watch-effect-page">
<h2 ref="titleRef"> watchEffect 监听 </h2>
<div>{{ zhangsan }}</div>
<div>{{ lisi }}</div>
<div>{{ person }}}</div>
<button @click="changeName">修改张三</button>
<button @click="addNewGames">修改john</button>

<h2> 子组件 </h2>
<props-page :lisi="lisi" :person="person" />
</div>
</template>
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
<script setup name="WatchEffectPage">
import PropsPage from "./PropsPage.vue";
import { reactive, ref, watchEffect } from "vue";
let zhangsan = ref("张三")
let lisi = ref("lisi")
let titleRef = ref()
let person = reactive({
name: "John",
age: 20,
hobby: {
sport: "football",
music: "pop",
games: ['永结物件','帕鲁']
}
})
// watchEffect 监视 回调函数内部 所使用的 ref reactive响应式数据
watchEffect(()=> {
// console.log("watchEffect", zhangsan.value, lisi.value, person)
console.log("titleRef", titleRef.value)
console.log("watchEffect", zhangsan.value, person.hobby.games)
})

function changeName(){
zhangsan.value += "~"
}
function addNewGames(){
person.hobby.games.push('飞车')
}
</script>

子组件

1
2
3
4
5
6
7
8
<template>
<div class="props-page">
<div>{{ zhangsan }}</div>
<div>{{ lisi }}</div>
<div>{{ person.name }}</div>
<div>{{ list }}</div>
</div>
</template>
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
<script setup>
import { onMounted, withDefaults } from "vue"
// let props = defineProps(['zhangsan','lisi', 'person','list'])

// withDefaults 设置默认值
/* let props = withDefaults(defineProps(), {
zhangsan: "zs默认值",
list: () => [{a: "default"}],
person: () => { return {}}
}) */

let props = defineProps({
zhangsan: {
type: String,
default: "default"
},
list: {
type: Array,
default: () => [{a: "default"}]
},
person: {
type: Object,
default: () => { return {}}
},
lisi: {
type: String,
default: "lisi默认值"
}
})
// let props2 = defineProps(['list'])
onMounted(() => {
console.log("onMounted", props)
})
</script>

emits向上传递

defineEmits

1
2
3
let emit = defineEmits(["on-close","on-show"])

emit('on-close', '要传递的参数')

子组件

1
2
3
4
5
6
7
8
9
10
<template>
<div class="props-page">
<div>{{ zhangsan }}</div>
<div>{{ lisi }}</div>
<div>{{ person.name }}</div>
<div>{{ list }}</div>
<button @click="show">向上传递 show</button>
<button @click="close">向上传递 close</button>
</div>
</template>
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
41
42
<script setup>
import { onMounted, withDefaults } from "vue"
// let props = defineProps(['zhangsan','lisi', 'person','list'])

// withDefaults 设置默认值
/* let props = withDefaults(defineProps(), {
zhangsan: "zs默认值",
list: () => [{a: "default"}],
person: () => { return {}}
}) */
let emit = defineEmits(["on-close","on-show"])
let props = defineProps({
zhangsan: {
type: String,
default: "default"
},
list: {
type: Array,
default: () => [{a: "default"}]
},
person: {
type: Object,
default: () => { return {}}
},
lisi: {
type: String,
default: "lisi默认值"
}
})
function show(){
console.log("show emit")
emit("on-show", "我是子组件传递的数据on-show~~~~")
}
function close(){
console.log("close emit")
emit("on-close", "我是子组件传递的数据on-close~~~~")
}
// let props2 = defineProps(['list'])
onMounted(() => {
console.log("onMounted", props)
})
</script>

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="watch-effect-page">
<h2 ref="titleRef"> watchEffect 监听 </h2>
<div>{{ zhangsan }}</div>
<div>{{ lisi }}</div>
<div>{{ person }}}</div>
<button @click="changeName">修改张三</button>
<button @click="addNewGames">修改john</button>
<div>子组件传递的数据: {{ subMsg }}</div>
<h2> 子组件 </h2>
<props-page :lisi="lisi" :person="person" @on-show="subComponentShow" @on-close="subComponentClose" />
</div>
</template>
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
<script setup name="WatchEffectPage">
import PropsPage from "./PropsPage.vue";
import { reactive, ref, watchEffect } from "vue";
let zhangsan = ref("张三")
let lisi = ref("lisi")
let subMsg = ref("")
let titleRef = ref()
let person = reactive({
name: "John",
age: 20,
hobby: {
sport: "football",
music: "pop",
games: ['永结物件','帕鲁']
}
})
// watchEffect 监视 回调函数内部 所使用的 ref reactive响应式数据
watchEffect(()=> {
// console.log("watchEffect", zhangsan.value, lisi.value, person)
console.log("titleRef", titleRef.value)
console.log("watchEffect", zhangsan.value, person.hobby.games)
})
// 子组件 emit 传值
function subComponentShow(msg) {
console.log("subComponentShow", msg)
subMsg.value = msg
}
function subComponentClose(msg) {
console.log("subComponentClose", msg)
subMsg.value = msg
}

function changeName(){
zhangsan.value += "~"
}
function addNewGames(){
person.hobby.games.push('飞车')
}
</script>

生命周期

vue2

创建 (创建前 beforeCreate ) 创建后创建 (created)
挂载 (挂载前 beforeMount) 挂载后 mounted
更新 (更新前 beforeUpdate) 更新后 updated
销毁 (销毁前 beforeDestroy) 销毁后 destroyed

vue3

创建 (创建前 setup ) 创建后创建 (setup)
挂载 (挂载前 beforeMount) 挂载后 mounted
更新 (更新前 beforeUpdate) 更新后 updated
销毁 (销毁前 beforeDestroy) 销毁后 destroyed

vue2生命周期

钩子函数描述
beforeCreate在实例创建之前调用。在实例初始化过程中创建组件的datacomputedwatchmethods属性之前调用。在beforeCreate钩子中,实例的属性还不存在,因此在this中不能访问实例属性。
created在实例创建之后调用。在实例初始化之后,数据观测(data observer) 和事件监听器(event listeners)已经绑定,$el 已经挂载完成,但是$nextTick 回调中访问的 DOM 节点还不存在。
beforeMount实例挂载之前调用。在 mounted 钩子中,实例的 $el 已经存在,但子组件可能还没有挂载。在 beforeMount 钩子中,实例的 $el 还不存在。
mounted实例挂在之后调用。 这时可以访问实例的 $el
beforeUpdate状态更新之前执行函数,此时data中的状态值是最新的但是界面上显示的数据还是旧的,因为还没有开始重新渲染DOM节点
updated此时data中的状态值和界面上显示的数据都已经完成了更新,界面已经被重新渲染好了!
beforeDestroy组件实例销毁之前调用!
destroyed实例销毁后调用,Vue实例指示的所有东西都会解绑,所有的事件监听器都会被移除,所有的子实例也都会被销毁。组件已经被完全销毁,此时组建中所有data、methods、以及过滤器,指令等,都已经不可用了。
vue2生命周期

vue3生命周期

钩子函数描述
setup()开始创建组件之前,在beforeCreatecreated之前执行。创建的是datamethod
onBeforeMount()组件挂载到节点上之前执行的函数。
onMounted()组件挂载完成后执行的函数。
onBeforeUpdate()组件更新之前执行的函数。
onUpdated()组件更新完成之后执行的函数。
onBeforeUnmount()组件卸载之前执行的函数。
onUnmounted()组件卸载完成后执行的函数
onActivated()被包含在keep-alive中的组件,会多出两个生命周期钩子函数。被激活时执行。
onDeactivated()比如从 A 组件,切换到 B 组件,A 组件消失时执行。
onErrorCaptured()捕获一个来自子孙组件的异常时激活钩子函数

对比

vue2vue3
beforeCreatesetup
createdsetup
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
activatedonActivated
deactivatedonActivated
errorCapturedonErrorCaptured

Hooks

Hooks单个vue组件中的功能进行分类提取!
module 当一个组件业务繁多时将业务的每个功能点单独抽离成一个文件js or ts 类似于mixis混入效果!
hooks文件中必须export一个function,其命名必须为useXXX

看一段没有使用 hooks的代码

no 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<template>
<div class="hooks-page">
<h2>Hooks Page</h2>
<div class="sum">{{ sum }}</div>
<div class="show-img">
<img v-for="dog in dogs" :key="dog" :src="dog" />
</div>
<button @click="add">点我累加</button>
<button @click="addDogs">点我添加 dog</button>
</div>
</template>

<script setup>
import { reactive, ref } from 'vue';
import axios from 'axios';

let sum = ref(0)

let dogs = reactive([
"https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg"
])


function add(){
sum.value += 1;
}

async function addDogs() {
let { data } = await axios.get("https://dog.ceo/api/breed/pembroke/images/random")
dogs.push(data.message)
}

</script>

<style scoped>
.hooks-page {
padding: 15px;
background-color: #ddd;
margin-top: 10px;
}
.hooks-page .show-img img{
height: 100px;
margin-right: 12px;
}
</style>

以上代码有两个功能点sum求和显示 dogs 列表,两个互不相干的功能混合在一起,代码可读性差,可维护性差!

使用 hooks

新建文件夹src/hooks/ 分别新建文件useSum.js useDogs.js!

useSum.js

useSum.js
1
2
3
4
5
6
7
8
import { reactive, ref } from "vue";
export const useSum = () => {
const sum = ref(0);
function add() {
sum.value += 1;
}
return { sum, add };
}

useDogs.js

useDogs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { reactive, ref } from "vue";
import axios from "axios";

export const useDogs = () => {
let dogs = reactive([
"https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg",
]);
async function addDogs() {
let { data } = await axios.get(
"https://dog.ceo/api/breed/pembroke/images/random"
);
dogs.push(data.message);
}
return { dogs, addDogs };
};

HooksPage.vue

HooksPage.vue
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
41
42
43
44
45
46
47
48
49
50
51
<template>
<div class="hooks-page">
<h2>Hooks Page</h2>
<div class="sum">{{ sum }}</div>
<div class="show-img">
<img v-for="dog in dogs" :key="dog" :src="dog" />
</div>
<button @click="add">点我累加</button>
<button @click="addDogs">点我添加 dog</button>
</div>
</template>

<script setup>
import { useSum } from '@/hooks/useSum';
import { useDogs } from '@/hooks/useDogs';

let { add, sum } = useSum();
let { addDogs, dogs } = useDogs();

/* import { reactive, ref } from 'vue';
import axios from 'axios';

let sum = ref(0)

let dogs = reactive([
"https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg"
])


function add(){
sum.value += 1;
}

async function addDogs() {
let { data } = await axios.get("https://dog.ceo/api/breed/pembroke/images/random")
dogs.push(data.message)
} */

</script>

<style scoped>
.hooks-page {
padding: 15px;
background-color: #ddd;
margin-top: 10px;
}
.hooks-page .show-img img{
height: 100px;
margin-right: 12px;
}
</style>

vue-router

vue-router 路由器 导航, 通过routes配置路由规则,进行path路径匹配,进而在当前页面显示对应组件!

vue-router中的组件:
<router-view> 显示当前路由匹配的组件
<router-link :to='' class='active-class'> 跳转到指定路由

  1. 路由组件通常放在pagesviews, 一般组件通常放在components中!
  2. 通过点击导航视觉上的消失, 默认是被卸载的需要的时候在去挂载!

引入vue-router

安装vue-router

1
npm install vue-router --save

创建@/router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { createRouter, createWebHistory } from "vue-router";

// 创建路由
const router = createRouter({
// 路由模式
history: createWebHistory(),
// 路由列表
routes: [
{
path: "/",
name: "Home",
component: () => import("@/views/base-func-demo/index.vue"),
},
{
path: "/router-page",
name: "RouterPage",
component: () => import("@/views/router-page-demo/index.vue"),
},
],
});

export default router;

其中 createRouter 为创建router实例,配置路由规则! createWebHistory 为创建router 模式[hash 模式 or history 模式]!

to 字符串写法

1
<router-link active-class="active" to="/home">主页</router-link>

to 对象写法

1
2
3
<router-link active-class="active" :to="{path: '/home'}">主页</router-link>

<router-link active-class="active" :to="{name: 'Home'}">主页</router-link>

路由工作模式

vue-router 路由工作模式分为hash 模式和 history 模式!

history模式

http://127.0.0.1:5173/router-page

优点: URL更加美观,不带有#号!
缺点: 后期项目上线, 需要服务端配合处理路径问题,否则刷新会有404错误!

1
2
3
4
const router = createRouter({
// 路由模式
history: createWebHistory()
})

后端处理: 例如 nginx 在配置文件中需要:
1
2
3
4
5
location / {
root /root/gshop;
index index.html;
try_files $uri $uri/ /index.html;
}

hash模式

http://127.0.0.1:5173/#/router-page

优点: 兼容性好,不需要服务端处理路径!
缺点: URL不美观,带有#号,且在SEO优化方面相对较差!

1
2
3
4
const router = createRouter({
// 路由模式
history: createWebHashHistory()
})

命名路由

name: “Home” 给路由起名字!

1
2
3
4
5
{
path: "/",
name: "Home",
component: () => import("@/views/base-func-demo/index.vue"),
},

嵌套路由

嵌套路由是通过children 属性来配置的,它是一个数组[]!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
path: "/routerPage",
name: "RouterPage",
component: () => import("@/views/router-page-demo/index.vue"),
// redirect: "/routerPage/details",
children: [
{
path: "details",
name: "Details",
component: () =>
import("@/views/router-page-demo/sub-router/Details.vue"),
},
{
path: "about",
name: "About",
component: () =>
import("@/views/router-page-demo/sub-router/About.vue"),
},
],
},

注意: childrenpath属性无需加/!
访问路径: /routerPage/details or /routerPage/about!

路由传参

query

query 通过?号来传递参数, 例如: ?id=1!
例如: http://127.0.0.1:5173/routerPage/about?content=内容2

demo
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<template>
<div class="router-page-demo">
<center>
<h2>router-page-demo</h2>
</center>
<div class="navbar">
<router-link class="nav-item" v-for="item in navList" :to="`${item.to}?content=${item.content}`" :key="item.id">{{ item.nav }}</router-link>
</div>
<div class="content">
<div class="content-title">子级路由显示位置</div>
<router-view></router-view>
</div>
</div>
</template>

<script setup>
import { reactive } from 'vue';

let navList = reactive([
{id: "851783781278371", nav: "子路由1", content: "内容1", to: "/routerPage/details" },
{id: "675178378127837", nav: "子路由2", content: "内容2", to: "/routerPage/about" },
// {id: "751783781278371", nav: "子路由3", content: "内容3" },
// {id: "751783781278372", nav: "子路由4", content: "内容4" },

])
</script>

<style scoped>
.navbar{
height: 125px;
display: flex;
justify-content: space-around;
align-items: center;
}
.navbar .nav-item{
width: 95px;
line-height: 45px;
border-radius: 15px;
text-decoration: none;
display: inline-block;
background-color:#ddd ;
color: #000;
text-align: center;
}
.content{
width: 1675px;
height: 400px;
border: 1px solid #ddd;
margin: 0 auto;
border-radius:10px ;
padding: 15px;
}
.content .content-title{
text-align: center;
font-size: 18px;
font-weight: bold;
}
</style>

怎么获取 query 参数呢?

1
2
3
4
5
import { useRoute } from 'vue-router';

let route = useRoute();
let queryParams = route.query;
console.log("about", queryParams);

还有一种写法

{ path: item.to, query: {content: item.content}}

1
2
3
<router-link class="nav-item" v-for="item in navList" :to="{ path: item.to, query: {content: item.content}}" :key="item.id">
{{ item.nav }}
</router-link>

params

params 通过:/号来传递参数, 例如: /id/1!

1
2
3
4
5
6
{
path: "details/:content/:title",
name: "Details",
component: () =>
import("@/views/router-page-demo/sub-router/Details.vue"),
},

1
2
3
4
5
6
7
let navList = reactive([
{id: "851783781278371", nav: "子路由1", content: "内容1", to: "/routerPage/details/内容传参/15123213", name: "Details" },
{id: "675178378127837", nav: "子路由2", content: "内容2", to: "/routerPage/about", name: "About" },
// {id: "751783781278371", nav: "子路由3", content: "内容3" },
// {id: "751783781278372", nav: "子路由4", content: "内容4" },

])

怎么获取 params 参数呢?

1
2
3
4
5
import { useRoute } from 'vue-router';

let route = useRoute();
let params = route.params;
console.log("details", params);

还有一种写法, path 替换成 name

{ name: item.name, params: {content: item.content,title: "15123213"}}

1
2
3
4
5
6
7
8
<router-link 
class="nav-item"
v-for="item in navList"
:to="{ name: item.name, params: {content: item.content, title: "15123213"}}"
:key="item.id"
>
{{ item.nav }}
</router-link>

path: "details/:content/:title" 假如占位符title属性不想要了,可以这样做:
path: "details/:content/:title?" 加问号即可!

路由规则props配置

在路由规则中通过props: true 来达到组件之间传递数据的目的!
该属性[props]组件 DetailsdefineProps(['content','title'])
收集所有params参数,通过props透传到组件props中!

路由规则

1
2
3
4
5
6
7
{
path: "details/:content/:title",
name: "Details",
component: () =>
import("@/views/router-page-demo/sub-router/Details.vue"),
props: true // 该属性[props] 在组件 Details 中`defineProps(['content','title'])` 中会自动解析`
},

子组件中使用defineProps
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div class="details">
<h3>我是子级路由_details</h3>
</div>
</template>

<script setup name="Details">
/* import { toRefs } from 'vue';
import { useRoute } from 'vue-router';

let route = useRoute();
let { params } = toRefs(route) */;

let props = defineProps(['content','title'])
// console.log("params.value",params.value)
console.log("content, title",props.content, props.title)
</script>

<style scoped>

</style>

自定义props函数

props可以为函数通过回调参数来特定返回props属性值!

1
2
3
4
5
6
7
8
9
{
path: "details/:content/:title",
name: "Details",
component: () =>
import("@/views/router-page-demo/sub-router/Details.vue"),
props( route ){
return route.query
} // 该属性[props] 在组件 Details 中`defineProps(['content','title'])` 中会自动解析`
},

自定义props对象

1
2
3
4
5
6
7
8
9
10
{
path: "details/:content/:title",
name: "Details",
component: () =>
import("@/views/router-page-demo/sub-router/Details.vue"),
props: {
a: 123123,
b: 13513451
}// 该属性[props] 在组件 Details 中`defineProps(['content','title'])` 中会自动解析`
},

replace属性

replace 加上该属性后,无法通过浏览器历史浏览功能返回上一页!

1
2
3
4
5
6
7
8
9
<router-link
replace
class="nav-item"
v-for="item in navList"
:to="`${item.to}?content=${item.content}`"
:key="item.id"
>
{{ item.nav }}
</router-link>

编程式路由

就是以代码的形式来控制路由!

1
2
3
4
5
6
import { useRouter } from 'vue-router';

let router = useRouter();
router.push("/about");
router.push({path: "/about", query: {}, params: {}});
// router.replace();

push 跳转是有历史记录的,可以返回上一次路由,而replace 跳转是没有历史记录的,无法返回上一次路由!

路由重定向

重定向就是当访问/时,自动跳转到/about!
/routerPage 访问此路由时跳转到/routerPage/details!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
path: "/routerPage",
name: "RouterPage",
component: () => import("@/views/router-page-demo/index.vue"),
redirect: "/routerPage/details",
children: [
{
path: "details/:content/:title",
name: "Details",
component: () =>
import("@/views/router-page-demo/sub-router/Details.vue"),
props: true
},
{
path: "about",
name: "About",
component: () =>
import("@/views/router-page-demo/sub-router/About.vue"),
},
],
},

pinia状态管理工具

piniavue3状态管理工具,可以轻松的在vue3中使用vuex!

引入pinia

安装

1
npm i pinia --save

在main.js中引入pinia

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createApp } from "vue";
import App from "./App.vue";
import router from "@/router";
import { createPinia } from "pinia";

const pinia = createPinia();
const app = createApp(App);

app.use(router);
app.use(pinia);

// 配置 vue 无需 编译如下的标签选项
app.config.compilerOptions.isCustomElement = (tag) => {
return tag.startsWith("ion-") || ["center"].includes(tag);
};

// 挂载到页面
app.mount("#app");

pinia基本使用

下面以counter为例,先创建@/views/counter.vue

counter.vue
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
<template>
<div class="counter-page">
<h2>Counter</h2>
<p>当前计数: {{ countStore.count }}</p>

<select v-model="countStore.selected">
<option v-for="i in 4" :key="i" :value="i" :label="i"></option>
</select>

<button @click="counter">计数</button>
</div>
</template>

<script setup name="Counter">
import { useStoreCount } from "@/store/counter.js";
import { ref } from "vue";

let countStore = useStoreCount();
console.log(countStore.count);
console.log(countStore.$state.count);
// let count = ref(0)
// let selected = ref(1)

function counter(){
countStore.count+=countStore.selected || 1
}
</script>

<style scoped>
.counter-page {
padding: 15px;
background-color: #ddd;
margin-bottom: 10px;
}
</style>

创建一个@/store/counter.js

counter.js
1
2
3
4
5
6
7
8
9
10
import { defineStore } from "pinia";

export const useStoreCount = defineStore("counter", {
state(){
return {
count: 0,
selected: 1,
};
}
});

修改状态

piniastate响应式的,所以可以直接修改state!

直接修改

1
2
3
import { useStoreCount } from "@/store/counter.js";
let countStore = useStoreCount();
countStore.count+=1;

批量修改

1
2
3
4
5
6
7
import { useStoreCount } from "@/store/counter.js";
let countStore = useStoreCount();

countStore.$patch({
count: 123,
selected: 21231
})

使用 action

store 中 加入actions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { defineStore } from "pinia";

export const useStoreCount = defineStore("counter", {
actions: {
increment(){
// this 就是当前的 store[useStoreCount] 实例
this.count ++
}
},
state(){
return {
count: 0,
selected: 1,
};
},
});

counter.vue中调用actions定义都方法

1
2
3
4
5
6
7
import { useStoreCount } from "@/store/counter.js";
let countStore = useStoreCount();

function counter(){
// countStore.count+=countStore.selected || 1
countStore.increment();
}

storeToRefs

storeToRefs可以把store中的state转换为ref!
当在组件中需要结构 store数据时,需要storeToRefs将结构单数据,变为ref响应式!

1
2
3
4
5
6
7
8
import { useStoreCount, storeToRefs } from "@/store/counter.js";
let countStore = useStoreCount();
let { count } = storeToRefs(countStore);
console.log("count", count)
function counter(){
// countStore.count+=countStore.selected || 1
countStore.increment();
}

storeToRefs(countStore)toRefs(countStore.$state) 是等价的!
不要尝试toRefs(countStore) 去获取state! 这样会把store中的不需要的属性也会转为ref!

getters

gettersstore中的计算属性! 可以对数据进行计算和处理!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { defineStore } from "pinia";

export const useStoreCount = defineStore("counter", {
actions: {
increment(){
this.count ++
}
},
getters: {
// defaultSelected: state => tate.selected * 10;
defaultSelected( state ){
// return state.selected * 10;
return this.selected * 10;
}
},
state(){
return {
count: 0,
selected: 1,
};
},
});

使用的话getterscountStore结构中获取getters

1
2
3
4
5
6
7
8
import { useStoreCount, storeToRefs } from "@/store/counter.js";
let countStore = useStoreCount();
let { count, defaultSelected } = storeToRefs(countStore);
console.log("count", count)
function counter(){
// countStore.count+=countStore.selected || 1
countStore.increment();
}

订阅$subscribe监听数据变化

pinia可以使用$subscribe监听state的变化!

1
2
3
4
5
6
7
8
9
10
11
12
13
import { useStoreCount, storeToRefs } from "@/store/counter.js";
let countStore = useStoreCount();
let { count, defaultSelected } = storeToRefs(countStore);

countStore.$subscribe((mutate, state) => {
// mutate本次修改的数据, state真正的数据
console.log("变化了 state", state);
})

function counter(){
// countStore.count+=countStore.selected || 1
countStore.increment();
}

store组合是写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { reactive, ref } from "vue";
import { defineStore } from "pinia";

export const useStoreCount = defineStore("counter", ()=> {
let counter = ref(0)

function incrementCounter(){
counter.value++
}
return {
counter,
incrementCounter
}
});

防止pinia数据刷新丢失

安装插件

1
npm i pinia-plugin-persistedstate -D

在main.js中引入插件并挂载到pinia

1
2
3
4
5
6
7
8
9
import { createPinia } from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const app = createApp(App);
const pinia = createPinia();

// 防止pinia刷新数据丢失
pinia.use(piniaPluginPersistedstate);

app.use(pinia);

使用

1
2
3
4
5
6
7
8
9
10
11
12
import { defineStore } from "pinia";

export const useLoverTail = defineStore("lover-tail", {
state(){
return {
loverTails: []
};
},
// 需要持久的加入这个
persist: true
});

组件属性传递

组件传递方式传递方法
父传子父组件通过v-bind:attr形式 传递给子组件, 子组件通过 defineProps(['attr'])来接受传值! $parent可以在子组件中获取父组件的属性和方法!
子传父1. 子组件通过 defineEmits属性来发送父组件v-on:attr传递的属性! 2.父组件定义方法通过v-bind:fun传递给子组件进行传参并调用!3. 子组件通过ref子组件上打标记 并在子组件中借助 defineExpose()属性或方法暴露出去给父组件获取! 4. $refs可以获取所有子组件
祖孙夸传provide inject
任意组件miit 事件发布订阅 插件

父传子

子组件通过 defineProps属性来接受父组件v-bind:attr传递的属性!
子组件通过$parent属性来获取 父组件中的属性方法,需要父组件提供 defineExpose({})来暴露内部属性和方法!

1
2
3
4
5
6
7
8
9
10
<template>
<children ref="children" :attr="attr" />
</template>
<script setup name="index">
import { ref } from "vue"
import Children from "./children.vue";
let value = ref("父组件的值")
// 暴露属性 后 子组件可以通过 $parent 获取
defineExpose({value})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>父组件传递过来的属性 {{ attr }}</div>
<button @click="onClick($parent)">子组件点击事件</button>
</template>
<script setup name="children">

import { defineProps } from "vue";

let props = defineProps(['attr'])
// 获取 parent
function onClick(parent){
console.log("parent", parent)
}
</script>

子传父

子组件通过 defineEmits属性来发送父组件v-on:attr传递的属性!
也可以 父组件通过定义方法 传递给子组件,子组件调用父组件所定义的方法传参给父组件!
通过ref子组件上打标记 并在子组件中借助 defineExpose()属性或方法暴露出去给父组件获取!
也可以通过在父组件中使用 $refs 获取所有子组件的ref!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<children ref="childrenRef" :attr="attr" :setValue="getChildrenValue" @onClick="getChildrenValue" />
</template>
<script setup name="index">
import { ref, onMounted } from "vue"
import Children from "./children.vue";

let childrenRef = ref()

function getChildrenValue( value ){
console.log("子组件传递过来的值", value)
}

// 挂在完成后 获取 ref 子组件
onMounted(() => {
console.log(childrenRef.value.childValue)
})
</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
<template>
<div>父组件传递过来的属性 {{ attr }}</div>
<button @click="setEmitValue">通过 emit 传递值给父组件</button>
</template>
<script setup name="children">

import { defineProps, ref } from "vue";
let childValue = ref("子组件值")

// props 方式
let props = defineProps(['attr', "setValue"])

let emits = defineEmits(['on-click', 'on-chnage'])

// emit 方式
function setEmitValue(){
emits("on-click", childValue )
}

// 暴露子组件 属性 和 方法 提供给父组件
defineExpose({
childValue,
attr: props.attr
})
</script>

miit 任意组件通信

miit 是一个体积非常小的插件事件订阅 事件触发!

安装mitt

1
npm i mitt --save

创建文件@/utils/emitter.js

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
import mitt from "mitt";

const emitter = mitt();

/*
all 所有事件
on 绑定事件
emit 触发事件
off 解绑事件
*/

emitter.on("test1", (val)=>{
console.log("test1触发调用");
})


emitter.on("test2", (val) => {
console.log("test2触发调用");
});

setInterval(()=> {
emitter.emit("test1", "参数");
emitter.emit("test2", "参数");
}, 1000)

setTimeout(() => {
// 解绑谁 写 谁
// emitter.off("test2");
// 清空所有事件
emitter.all.clear();
}, 2000);

export default emitter;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<children ref="childrenRef"/>
</template>
<script setup name="index">
import { ref, onMounted } from "vue"
import emitter from "@/utils/emitter.js";
import Children from "./children.vue";

let childrenRef = ref()

// 挂在完成后 获取 ref 子组件
onMounted(() => {
// 给组件2 传值
emitter.emit("zujian2",childrenRef.value.childValue)
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>父组件传递过来的属性 {{ attr }}</div>
<button @click="setEmitValue">通过 emit 传递值给父组件</button>
</template>
<script setup name="children">

import { defineProps, ref } from "vue";

import emitter from "@/utils/emitter.js";
let childValue = ref("组件2值")

emitter.on("zujian2", (val)=> {
console.log("组件1 给 组件2 传值了", val)
})

</script>

v-model 组件通信

借助v-model双向绑定的特性,实现 父子组件之间的通信!

vue3

v-modelhtml标签上

1
2
3
<input v-model = "modelValue" />

<input :value = "modelValue" @input = "modelValue = $event.target.value" />

v-model自定义组件

1
2
3
<AtGuiGuInput v-model="modelValue" />

<AtGuiGuInput :modelValue="modelValue" @update:modelValue="modelValue = $event.target.value" />

AtGuiGuInput.vue

1
2
3
4
5
6
7
8
<template>
<input :value="modelValue" @input="emit('update:modelValue', $event.target.value)" />
</template>
<script setup>
import { defineProps, defineEmit } from "vue";
let props = defineProps(['modelValue'])
let emit = defineEmit(['update:modelValue'])
</script>

v-mode绑定多个

1
<AtGuiGuInput v-model:username="username" v-model:password ="password" />

AtGuiGuInput.vue

1
2
3
4
5
6
7
8
9
10
<template>
<input :value="modelValue" @input="emit('update:username', $event.target.value)" />

<input :value="modelValue" @input="emit('update:password', $event.target.value)" />
</template>
<script setup>
import { defineProps, defineEmit } from "vue";
let props = defineProps(['username', 'password'])
let emit = defineEmit(['username', 'password'])
</script>

vue2

vue2 AtGuiGuInput.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<input :value="modelValue" @input="$emit('getValue', $event.target.value)" />
</div>
</template>
<script>
export default{
name: "AtGuiGuInput",
model: {
prop: "modelValue"
event: 'getValue',
},
data(){
return {

}
}
}
</script>

$attrs

  1. $attrs用于当前父组件,向当前子组件传递数据(祖-孙)!
  2. $attrs是一个对象,包含所有父组件所传递的数据!

    注意: $attrs会自动排除props中声明的属性(可以认为声明过的props被子组件自己消费了)

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<children ref="childrenRef" :a="a" :b="b" :c="c" :d="d"/>
</template>
<script setup name="index">
import { ref, onMounted } from "vue"
import Children from "./children.vue";

let a = ref(0)
let b = ref(0)
let c = ref(0)
let d = ref(0)
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>父组件传递过来的属性 {{ attr }}</div>
<!-- 透传给孙子组件 -->
<grandchild v-bind="$attrs" />
</template>
<script setup name="children">

import { useAttrs } from "vue";

let childValue = ref("子组件值")

let attrs = useAttrs();

console.log("$attrs", attrs)
</script>

$refs$parent

$refs用于获取所有子组件ref属性值!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<!-- 透传给孙子组件 -->
<child1 ref="child1Ref" />
<child2 ref="child2Ref" />
<button @click="getAllComponent($refs)">点我获取所有子组件</button>
</template>
<script setup name="children">
import { useAttrs } from "vue";

// <child1 ref="child1Ref" />
// <child2 ref="child2Ref" />
function getAllComponent( refs ){
console.log("refs", refs)
for (const key in refs) {
console.log("refs", refs[key])
}
}
</script>

$parent
获取$parent父组件内部的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<children ref="childrenRef" :a="a" :b="b" :c="c" :d="d"/>
</template>
<script setup name="index">
import { ref, onMounted } from "vue"
import Children from "./children.vue";

let a = ref(0)
let b = ref(0)
let c = ref(0)
let d = ref(0)

// 向外暴露 内部组件的数据
defineExpose({a, b, c, d})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>父组件传递过来的属性 {{ attr }}</div>
<!-- 透传给孙子组件 -->
<grandchild v-bind="$attrs" />

<button @click="getAllComponent($parent)">点我获取父组件内部属性和方法</button>
</template>
<script setup name="children">

function getAllComponent( parent ){
console.log("parent", parent.a)
}
</script>

provideinject

依赖注入
provideinject用于父组件子组件传递数据,并且子组件可以向孙组件传递数据!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<children ref="childrenRef" :a="a" :b="b" :c="c" :d="d"/>
</template>
<script setup name="index">
import { ref, onMounted, provide } from "vue"
import Children from "./children.vue";

let money = ref(100)
let car = reactive({
brand: "奔驰",
price: "1200"
})

provide("money", money)
provide("car", car)
provide("car", {money, car})
</script>
1
2
3
4
5
6
7
8
<template>
<div>祖父组件传递过来的属性 {{ attr }}</div>
</template>
<script setup name="children">
import { inject } from "vue"
let money = inject("money", "这是默认值")
let car = inject("car")
</script>

provide提供的值 在inject中没有找到对应的模块, 那么inject可以通过第二个参数来设置默认值!

插槽

slot 插槽 就是在父组件中子组件标签内部添加动态标签内容!

默认插槽

父组件

1
2
3
<children-component>
<div>这里是插槽内容,会被渲染在子组件内部元素中!</div>
</children-component>

子组件

1
2
3
<templte>
<slot>父组件中的传递的内容 会在此处显示!</slot>
</template>

具名插槽

当在不同位置使用插槽时, 可以使用具名插槽!`v-slot:title 只能在templatecomponent中使用!
·v-slot:title·简写方式 #title

父组件

1
2
3
4
<children-component>
<template v-slot:title>这里是标题</template>
<template v-slot:body>这里是内容</template>
</children-component>

子组件

1
2
3
4
5
6
7
8
<templte>
<div>
<slot name="title">父组件中的传递的内容 会在此处显示!</slot>
<div class="body">
<slot name="body">父组件中的传递的内容 会在此处显示!</slot>
</div>
</div>
</template>

作用域插槽

就是在子组件中定义slot中,加入特定内部参数,提供父组件使用!

父组件

1
2
3
4
5
<children-component>
<template v-slot:title>这里是标题</template>
<!-- <template v-slot:body="games">这里是内容{{ games }}</template> -->
<template #body="{ games }">这里是内容{{ games }}</template>
</children-component>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<templte>
<div>
<slot name="title">父组件中的传递的内容 会在此处显示!</slot>
<div class="body">
<slot :games="games" name="body">父组件中的传递的内容 会在此处显示!</slot>
</div>
</div>
</template>
<script setup>
import { reactive } from "vue"
let games = reactive([
{id: 5781738213, name: "永结午间"},
{id: 4781738213, name: "明日"}
])
</script>

组件传递方式

组件关系传递方式
父传子props v-model $refs 默认插槽 具名插槽
子传父props emit v-model $parent 作用域插槽
祖传孙 孙传祖$attrs [provide - inject]
兄弟间 任意传mitt pinia

其他 API

shallowRefshallowReactive

shallowRef 只针对浅层面的数据进行响应式!
shallowReactive 只针对object 浅层面属性响应式 深层面属性不做响应!
场景: 当业务中遇到 深层次比较大的 对象时 一些不关注的属性不需要进行响应式,只需要关注浅层面的数据是否发生改变!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
import { ref, shallowRef } from "vue"

let num = shallowRef(0);
let person = shallowRef({
name: "张三"
});
// 响应式
function changeNum(){
num.value += 2;
}
// 不响应
function changeName(){
person.value.name = "李四";
}
// 改变整个对象 可以响应
function changePerson(){
person.value = {
name: "李四"
};
}
</script>
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>
import { ref, shallowReactive } from "vue"
let person = shallowReactive({
name: "zhangsan",
age: 15,
hobbys: {
games: ['永结物件','明日']
likeColor: "black"
}
})
// 响应式
function changePersonName(){
person.name = "lisi"
}
// 响应式
function changePersonAge(){
person.age = 16
}
// 不响应式
function changeLikeColor(){
person.hobbys.likeColor = "red"
}
</script>

readyonlyshallowReadyonly

readonly 用于创建一个对象的深层次只读副本!
readonly 只接受一个响应式属性 ref / reactive作为参数!
shallowReadyonly 浅层次 只读副本! 只限制 对象第一层只读!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { ref, reactive, readyonly, shallowReadyonly } from "vue"
let sum = ref(0) // 可响应式
let sum2 = readyonly(sum) // 不可响应式

let car = reactive({
brand: "奔驰",
options: {
price: "1200",
color: "黑色",
}

}) // 响应式
let car2 = readyonly(car) // 不可响应式

let car3 = shallowReadyonly(car) // 只限制 对象第一层只读!
car3.brand = "宝马" // 只读
car3.options.price = "1300" // 可响应式

car2car 之间是有关系的,当car可响应式属性发生改变时, car2 也会发生改变!

toRawmarkRaw

toRaw响应式对象 refreactive 数据 变为原始数据!不会触发视图的更新!
markRaw 被标记的对象不会被响应式处理`!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { ref, reactive, toRaw  } from "vue"
let car = reactive({
brand: "奔驰",
options: {
price: "1200",
color: "黑色",
}
}) // 响应式

// proxy 响应式代理对象


let car2 = toRaw(car) // 不可响应式 返回最初原始对象
/* {
brand: "奔驰",
options: {
price: "1200",
color: "黑色",
}
} */

markRaw

1
2
3
4
import { ref, reactive, toRaw, markRaw  } from "vue"
let car = markRaw({ brand: "奔驰" })
let car2 = reactive(car) // 无法使用 ref reactive 将 markRaw 标记为原始对象的改为响应式对象!
// car2 为不响应式

customRef自定义ref

customRef 可以自己定义ref响应式数据!
customRef 类似于computed计算属性,监控数据变化,触发视图更新的过程中可以进行一些处理!

customRef
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
<template>
<div class="custom-ref">
<div class="msg"> {{ msg }}</div>
<input type="text" v-model="msg"/>
</div>
</template>

<script setup>
import { ref, customRef } from "vue";
// let msg = ref("Hello Vue 3");
let initailValue = "Hello Vue 3"
let timer = null;
// 自定义ref
let msg = customRef((track, trigger)=> {
return {
get: () => {
track(); // 跟踪器 监视 msg 值的变化
return initailValue
},
set: (value) => {
clearTimeout(timer);
// 设置2000中更新值
timer = setTimeout(() => {
// console.log("set value", value)
initailValue = value;
trigger(); // 触发器 当值msg 发生变化的时候通知vue进行更改!
}, 2000)

}
}
})
</script>

<style scoped>
.custom-ref {
padding: 15px;
background-color: #ddd;
margin-top: 10px;
}
</style>

teleport传送门

teleport 可以将元素传送到指定的DOM节点!
teleport 即使你的 dom 元素 在某个节点位置,通过teleport to=""配置后,显示将在指定的节点中显示!
id选择器 class选择器 标签选择器

1
2
3
<teleport to="body">
<div>我是传送门,我会将以下内容挂在到body中!</div>
</teleport>

<Teleport> 挂载时,传送的 to 目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的,你需要确保在挂载 <Teleport>之前先挂载该元素。

suspense异步组件

suspense组件内部使用到异步加载数据时,可以使用suspense!防止 异步组件 在异步加载数据之前 丢失问题!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="container">
<h3>父组件</h3>
<Suspense>
<!-- 默认展示的样子 -->
<template v-slot:default>
<children :msg="msg"></children>
</template>
<!-- 异步加载数据后 最终展示的样子 -->
<template v-slot:fallback>
<children :msg="msg"></children>
</template>
</Suspense>
</div>
</template>
<script setup>
import children from "./children.vue"
import { Suspense } from "vue"
</script>
1
2
3
4
5
6
7
8
9
10
<template>
<div class="msg"{{ data }}></div>
</template>
<script setup >
import { ref } from "vue"
import axios from "axios"
let msg = ref("")
// await 这里之所以可以写 是因为 setup 函数底层 是异步函数!
let data = await axios.get("https://api.uomg.com/api/rand.qinghua?format=json")
</script>

vue3全局对象

  • app.component - 全局组件注册
  • app.config - 全局配置对象
  • app.directive - 全局指令
  • app.mount - ``
  • app.unmount
  • app.use