Skip to main content

Vue3

学习 vue3 用于提升前端竞争力和拓展技能

组合式 API 的好处

image-20250116132206806

V3 对比 V2

image-20250116132234491

基础

setup

  • 创造时间在beforeCreate
  • 没有 this
  • 有语法糖的写法 开发上效率提升 底层还是会编译成正常写法

语法糖

<script setup></script>

reactive 与 ref

我们希望有响应式的数据 就需要使用到他们

reactive 它的作用就是接收对象类型为参数传入返回一个响应式对象

<script setup>
// Reactive: 接受一个对象类型的数据 返回一个响应式对象
import {reactive} from "vue";

const state = reactive({count: 1000})

const setCount = () => {
state.count++
}
</script>

<template>
<div>
<div>{{ state.count }}</div>
<button @click="setCount">+1</button>
</div>
</template>

<style scoped>

</style>

ref使用来处理简单类型的数据响应式的

  • 本质: 是在原有传入数据的基础上 外包了一层对象
  • 底层: 包成复杂类型之后借助 Reactive 实现响应式
  • 访问数据, js 区域操作需要 .value 模版中直接使用就好
<script setup>
// ref 接受简单类型 或复杂类型, 返回一个响应式对象
// 本质: 是在原有传入数据的基础上 外包了一层对象
// 底层: 包成复杂类型之后借助Reactive实现响应式
import {ref} from "vue";

const count = ref(0);
const setCount = () => {
count.value++;
}
</script>

<template>
<div>
<div>{{ count }}</div>
<button @click="setCount">+1</button>
</div>
</template>

<style scoped>

</style>

推荐声明的时候都是用 ref即可

计算属性

基本思想和 vue2 完全一样 只是修改了代码的写法

  • 导入函数 conputed
  • 执行函数 在回调参数中 return 基于响应式数据做计算的值

小案例

<script setup>

import {computed, ref} from "vue";

const list = ref([1, 2, 3, 4, 5, 6, 7, 8])

// 基于 list 计算属性 需要过滤出 >2的数字
const computedList = computed(() => {
return list.value.filter(item => item > 2)
})
</script>

<template>
<div>{{ list }}</div>
<div>计算新数据{{ computedList }}</div>
</template>

<style scoped>

</style>

image-20250116152404952

需要注意的地方:

  1. 计算属性不应该关心其他操作 就只做对数据的计算和操作
  2. 避免直接修改计算属性的值

watch

和 vue2 也是基本一致只是修改了代码的写法

作用: 监听数据的变化 并且执行设置好的回调函数

额外参数:

  1. immediate 立即执行
  2. deep 深度监听

监听单个值

<script setup>
import {ref, watch} from "vue";

const count = ref(0);
const nickname = ref('张三');

const changeCount = () => {
count.value++
}

const changeNickname = () => {
nickname.value = '李四'
}

// 监视单个数据的变化
watch(count, (newVal, oldVal) => {
console.log('旧的' + oldVal, '新的' + newVal);
});
</script>

<template>
<div>{{ count }}</div>
<button @click="changeCount">改数字</button>
<div>{{ nickname }}</div>
<button @click="changeNickname">改名字</button>
</template>

<style scoped>

</style>

image-20250116170757977

监听多个

// 监视多个数据
watch([count, nickname], (newArr, oldArr) => {
console.log("旧的" + oldArr, "新的" + newArr);
});

image-20250116171006962

可选项

immediate立刻执行一次 , 刷新页面就可以看到打印

// 监视多个数据
watch(
[count, nickname],
(newArr, oldArr) => {
console.log("旧的" + oldArr, "新的" + newArr);
},
{
immediate: true,
}
);

deep 深度监视 默认的 watch 是浅层监视

实际上默认的监视 是无法检测到复杂类型内部的变化的 所以需要开始 deep 深度监视

const userInfo = ref({
name: '王五',
age: 18,
})

const setUserInfo = () => {
userInfo.value.age++
}
// 监视单个数据的变化
watch(userInfo, (newVal, oldVal) => {
console.log('旧的' + oldVal, '新的' + newVal);
});


<template>
<div>{{ userInfo }}</div>
<button @click="setUserInfo">plus</button>
</template>

按照这个数据形式 我们点击按钮 watch 是没有打印的 这个时候就需要深度监视了

// 监视单个数据的变化
watch(
userInfo,
(newVal, oldVal) => {
console.log("旧的" + oldVal, "新的" + newVal);
},
{
deep: true,
}
);

但是还是有个问题 就是我们这个方式只能监听到对象全部, 我们需要精确到属性的时候就需要按照以下的写法 实现监听具体的属性

watch(
() => userInfo.value.age,
(newVal, oldVal) => {
console.log("旧的" + oldVal, "新的" + newVal);
}
);

image-20250116172021467

生命周期函数

Vue2 对比 Vue3 钩子函数

image-20250116172159653

组件父子通信

和 vue2 的父子通信是完全一致的,主要还是语法的变化

  • 由于写了 setup 无法直接配置 props 这里需要借助宏函数
const props = defineProps({
car: String,
});

在模版中 可以直接使用属性 而不需要 props.car

defineProps原理:

本质上就是编译阶段的一个标识, 实际的编辑结果会转换成配置 props

父传子

父组件

<script setup>
//父组件
import SonComponent from "@/son-component.vue";
import {ref} from "vue";

// 传递响应式数据使用 v-bind 简写 :
const money = ref(100);
</script>

<template>
<div>我是父组件</div>
<SonComponent :money="money" car="宝马"></SonComponent>
</template>

<style scoped>

</style>

子组件

<script setup>
//由于 setup 这里我们需要用宏函数接受子组件
const props = defineProps({
car: String,
money: Number
});
</script>

<template>
<div class="son">我是子组件</div>
<div>我爸爸有一辆 {{ car }} 他还给我了 {{ money }}的零花钱</div>
</template>

<style scoped>
.son {
color: aqua;
}
</style>

效果

image-20250118150317701

子传父

  • 父组件给子组件绑定事件
  • 子组件通过 emit 方法触发事件传递

子组件

<script setup>
//由于 setup 这里我们需要用宏函数接受子组件
const props = defineProps({
car: String,
money: Number
});
const emits = defineEmits('changeMoney');

const buy = () => {
emits('changeMoney', 5)
}
</script>

<template>
<div class="son">我是子组件</div>
<div>我爸爸有一辆 {{ car }} 他还给我了 {{ money }}的零花钱</div>
<button @click="buy">花钱</button>
</template>

<style scoped>
.son {
color: aqua;
}
</style>

父组件

<script setup>
//父组件
import SonComponent from "@/son-component.vue";
import {ref} from "vue";

// 子传父
// 传递响应式数据使用 v-bind 简写 :
const money = ref(100);
const changeMoneyFn = (decMoney) => {
money.value = money.value - decMoney
};
</script>

<template>
<div>我是父组件</div>
<SonComponent
:money="money"
car="宝马" @changeMoney="changeMoneyFn"></SonComponent>
</template>

<style scoped>

</style>

模版引用

  • 调用 ref 函数生成一个 ref 对象
  • 在标签中用 ref 绑定对象
  • 通过 ref.value 访问到绑定的元素(必须是渲染完成的)
<script setup>
import {onMounted, ref} from "vue";
import TextCom from "@/模版引用/text-com.vue";

const inp = ref(null)
onMounted(() => {
console.log(inp.value)
})
const cliFn = () => {
inp.value.focus()
}
</script>

<template>
<input ref="inp" type="text">
<button @click="cliFn">点击让输入框聚焦</button>
<text-com/>
</template>

<style scoped>

</style>

defineExpose 默认 setup 情况下组件内部的属性和方法是不对外暴露的

可以通过这个宏函数指定暴露

const message = ref("this is msg");
defineExpose({
message,
});

跨层传递

顶层组件可以向任意的组件传递数据和方法 实现跨层组件通信

  • 顶层函数通过 PROVIDE函数提供数据
  • 底层组件 通过INJECT函数获取数据
  • 我们可能有很多层传递 对于数据 我们主张谁的数据谁维护 所以会传递对应数据的修改方法一并到其他层

顶层

<script setup>

import CenterCom from "@/跨层传递/center-com.vue";
import BottomCom from "@/跨层传递/bottom-com.vue";
import {provide, ref} from "vue";

provide('theme-color', 'pink')
const count = ref(100);
provide('count', count)
provide('changeCount', (newCount) => {
count.value = newCount;
})
</script>

<template>
<h1>我是顶层组件</h1>
<center-com></center-com>
<bottom-com></bottom-com>
</template>

<style scoped>

</style>

中层

<script setup>
import {inject} from "vue";

const themeColor = inject('theme-color')
const changeCount = inject('changeCount')
</script>

<template>
<h2>我是中间层组件 顶层给我的 {{ themeColor }}</h2>
<button @click="changeCount(10)">修改count</button>
</template>

<style scoped>

</style>

底层

<script setup>
import {inject} from "vue";

const count = inject('count');
const changeCount = inject('changeCount')
</script>

<template>
<h3>我是底层组件 顶层给我的 {{ count }}</h3>
<button @click="changeCount(10111)">修改count</button>
</template>

<style scoped>

</style>

效果

image-20250118153204994

defineOptions

我们使用了 setup 之后 与其平级的属性都无法添加了 比如 props emit 等等

如果我们要定义组件的 name 或者其他属性 又得重新添加一个 普通的标签

这里 vue3.3 更新了defineOptions来让我们可以继续设置这些属性

defineModel

vue3.3 用于解决 v3 的 v-model 和 v2 不同的地方

在 v3 中在自定义组件上使用 v-model 相当于 传递一个 modelvalue 属性同事触发一个UPDATE:MODELVALUE事件 如果想要子组件同步数据的话就需要

  1. 定义 props
  2. 使用 emit 拿到UPDATE:MODELVALUE事件
  3. 然后手动调这个函数

很麻烦

Pinia

简介

是 vue 最新的状态管理工具 是 vuex 的替代品 是 redux 的竞争对手哈哈哈

他对比 vuex:

  1. 更简单的 api
  2. 符合组合式的编写风格
  3. 去掉了 modules 的概念 每个 store 都是独立模块
  4. 配合 TS 更加友好

入门

添加 pinia 到项目中

导入依赖: npm i pinia

需要在 MAIN.JS中使用 pinia

import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia";

const pinia = createPinia();
createApp(App).use(pinia).mount("#app");