当前位置:   article > 正文

尚硅谷vue3学习笔记_尚硅谷vue3笔记

尚硅谷vue3笔记

01.setup 概述

`setup`是`Vue3`中一个新的配置项,值是一个函数,它是 `Composition API` ,组件中所用到的:数据、方法、计算属性、监视......等等,均配置在`setup`中。

特点如下:

- `setup`函数返回的对象中的内容,可直接在模板中使用。

- `setup`中访问`this`是`undefined`。

- `setup`函数会在`beforeCreate`之前调用,它是“领先”所有钩子执行的。

  1. <template>
  2. <div class="person">
  3. <h2>姓名:{{ name }}</h2>
  4. <h2>年龄:{{ age }}</h2>
  5. <button @click="changeName">修改名字</button>
  6. <button @click="changeAge">年龄+1</button>
  7. <button @click="showTel">点我查看联系方式</button>
  8. </div>
  9. </template>
  10. <script lang="ts">
  11. export default {
  12. name: "Person",
  13. setup() {
  14. // 数据,原来写在data中(注意:此时的name、age、tel数据都不是响应式数据)
  15. let name = "张三";
  16. let age = 18;
  17. let tel = "13888888888";
  18. // 方法,原来写在methods中
  19. function changeName() {
  20. name = "zhang-san"; //注意:此时这么修改name页面是不变化的
  21. console.log(name);
  22. }
  23. function changeAge() {
  24. age += 1; //注意:此时这么修改age页面是不变化的
  25. console.log(age);
  26. }
  27. function showTel() {
  28. alert(tel);
  29. }
  30. // 返回一个对象,对象中的内容,模板中可以直接使用
  31. return { name, age, tel, changeName, changeAge, showTel };
  32. },
  33. };
  34. </script>

下面的写法是setup语法糖

  1. <template>
  2. <div class="person">
  3. <h2>姓名:{{ name }}</h2>
  4. <h2>年龄:{{ age }}</h2>
  5. <button @click="changeName">修改名字</button>
  6. <button @click="changeAge">年龄+1</button>
  7. <button @click="showTel">点我查看联系方式</button>
  8. </div>
  9. </template>
  10. <script setup lang="ts">
  11. console.log(this); //undefined
  12. // 数据(注意:此时的name、age、tel都不是响应式数据)
  13. let name = "张三";
  14. let age = 18;
  15. let tel = "13888888888";
  16. // 方法
  17. function changName() {
  18. name = "李四"; //注意:此时这么修改name页面是不变化的
  19. }
  20. function changAge() {
  21. console.log(age);
  22. age += 1; //注意:此时这么修改age页面是不变化的
  23. }
  24. function showTel() {
  25. alert(tel);
  26. }
  27. </script>

02.ref 创建:基本类型的响应式数据

作用:定义响应式变量。

语法:let xxx = ref(初始值)`。

返回值:一个`RefImpl`的实例对象,简称`ref对象`或`ref`,`ref`对象的`value`属性是响应式的。

注意点:

JS中操作数据需要:`xxx.value`,但模板中不需要`.value`,直接使用即可。

对于`let name = ref('张三')`来说,`name`不是响应式的,`name.value`是响应式的。

  1. <template>
  2. <div class="person">
  3. <h2>姓名:{{ name }}</h2>
  4. <h2>年龄:{{ age }}</h2>
  5. <button @click="changeName">修改名字</button>
  6. <button @click="changeAge">年龄+1</button>
  7. <button @click="showTel">点我查看联系方式</button>
  8. </div>
  9. </template>
  10. <script setup lang="ts" name="Person">
  11. import { ref } from "vue";
  12. // name和age是一个RefImpl的实例对象,简称ref对象,它们的value属性是响应式的。
  13. let name = ref("张三");
  14. let age = ref(18);
  15. // tel就是一个普通的字符串,不是响应式的
  16. let tel = "13888888888";
  17. function changeName() {
  18. // JS中操作ref对象时候需要.value
  19. name.value = "李四";
  20. console.log(name.value);
  21. // 注意:name不是响应式的,name.value是响应式的,所以如下代码并不会引起页面的更新。
  22. // name = ref('zhang-san')
  23. }
  24. function changeAge() {
  25. // JS中操作ref对象时候需要.value
  26. age.value += 1;
  27. console.log(age.value);
  28. }
  29. function showTel() {
  30. alert(tel);
  31. }
  32. </script>

03. 【reactive 创建:对象类型的响应式数据】

作用:定义一个**响应式对象(基本类型不要用它,要用`ref`,否则报错)

语法:`let 响应式对象= reactive(源对象)`。

返回值:一个`Proxy`的实例对象,简称:响应式对象。

注意点:`reactive`定义的响应式数据是“深层次”的。

  1. <template>
  2. <div class="person">
  3. <h2>汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2>
  4. <h2>游戏列表:</h2>
  5. <ul>
  6. <li v-for="g in games" :key="g.id">{{ g.name }}</li>
  7. </ul>
  8. <h2>测试:{{ obj.a.b.c.d }}</h2>
  9. <button @click="changeCarPrice">修改汽车价格</button>
  10. <button @click="changeFirstGame">修改第一游戏</button>
  11. <button @click="test">测试</button>
  12. </div>
  13. </template>
  14. <script lang="ts" setup name="Person">
  15. import { reactive } from "vue";
  16. // 数据
  17. let car = reactive({ brand: "奔驰", price: 100 });
  18. let games = reactive([
  19. { id: "ahsgdyfa01", name: "英雄联盟" },
  20. { id: "ahsgdyfa02", name: "王者荣耀" },
  21. { id: "ahsgdyfa03", name: "原神" },
  22. ]);
  23. let obj = reactive({
  24. a: {
  25. b: {
  26. c: {
  27. d: 666,
  28. },
  29. },
  30. },
  31. });
  32. function changeCarPrice() {
  33. car.price += 10;
  34. }
  35. function changeFirstGame() {
  36. games[0].name = "流星蝴蝶剑";
  37. }
  38. function test() {
  39. obj.a.b.c.d = 999;
  40. }
  41. </script>

 04. 【ref 创建:对象类型的响应式数据】

其实`ref`接收的数据可以是:基本类型、对象类型。

若`ref`接收的是对象类型,内部其实也是调用了`reactive`函数。

  1. <template>
  2. <div class="person">
  3. <h2>汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2>
  4. <h2>游戏列表:</h2>
  5. <ul>
  6. <li v-for="g in games" :key="g.id">{{ g.name }}</li>
  7. </ul>
  8. <h2>测试:{{ obj.a.b.c.d }}</h2>
  9. <button @click="changeCarPrice">修改汽车价格</button>
  10. <button @click="changeFirstGame">修改第一游戏</button>
  11. <button @click="test">测试</button>
  12. </div>
  13. </template>
  14. <script lang="ts" setup name="Person">
  15. import { ref } from "vue";
  16. // 数据
  17. let car = ref({ brand: "奔驰", price: 100 });
  18. let games = ref([
  19. { id: "ahsgdyfa01", name: "英雄联盟" },
  20. { id: "ahsgdyfa02", name: "王者荣耀" },
  21. { id: "ahsgdyfa03", name: "原神" },
  22. ]);
  23. let obj = ref({
  24. a: {
  25. b: {
  26. c: {
  27. d: 666,
  28. },
  29. },
  30. },
  31. });
  32. console.log(car);
  33. function changeCarPrice() {
  34. car.value.price += 10;
  35. }
  36. function changeFirstGame() {
  37. games.value[0].name = "流星蝴蝶剑";
  38. }
  39. function test() {
  40. obj.value.a.b.c.d = 999;
  41. }
  42. </script>

 05. 【ref 对比 reactive】

宏观角度看:

`ref`用来定义:基本类型数据、对象类型数据;

 `reactive`用来定义:对象类型数据。

区别:

 1. `ref`创建的变量必须使用`.value`(可以使用`volar`插件自动添加`.value`)。

 2. `reactive`重新分配一个新对象,会**失去**响应式(可以使用`Object.assign`去整体替换)。

使用原则:

1. 若需要一个基本类型的响应式数据,必须使用`ref`。

2. 若需要一个响应式对象,层级不深,`ref`、`reactive`都可以。

3. 若需要一个响应式对象,且层级较深,推荐使用`reactive`。

 06. 【toRefs 与 toRef】

作用:将一个响应式对象中的每一个属性,转换为`ref`对象。

 备注:`toRefs`与`toRef`功能一致,但`toRefs`可以批量转换。

语法如下:

  1. <template>
  2. <div class="person">
  3. <h2>姓名:{{ person.name }}</h2>
  4. <h2>年龄:{{ person.age }}</h2>
  5. <h2>性别:{{ person.gender }}</h2>
  6. <button @click="changeName">修改名字</button>
  7. <button @click="changeAge">修改年龄</button>
  8. <button @click="changeGender">修改性别</button>
  9. </div>
  10. </template>
  11. <script lang="ts" setup name="Person">
  12. import { ref, reactive, toRefs, toRef } from "vue";
  13. // 数据
  14. let person = reactive({ name: "张三", age: 18, gender: "男" });
  15. // 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力
  16. let { name, gender } = toRefs(person);
  17. // 通过toRef将person对象中的gender属性取出,且依然保持响应式的能力
  18. let age = toRef(person, "age");
  19. // 方法
  20. function changeName() {
  21. name.value += "~";
  22. }
  23. function changeAge() {
  24. age.value += 1;
  25. }
  26. function changeGender() {
  27. gender.value = "女";
  28. }
  29. </script>

07. 【computed】计算属性

作用:根据已有数据计算出新数据(和`Vue2`中的`computed`作用一致)。

  1. <template>
  2. <div class="person">
  3. 姓:<input type="text" v-model="firstName" /> <br />
  4. 名:<input type="text" v-model="lastName" /> <br />
  5. 全名:<span>{{ fullName }}</span> <br />
  6. <button @click="changeFullName">全名改为:li-si</button>
  7. </div>
  8. </template>
  9. <script setup lang="ts" name="App">
  10. import { ref, computed } from "vue";
  11. let firstName = ref("zhang");
  12. let lastName = ref("san");
  13. // 计算属性——只读取,不修改
  14. /* let fullName = computed(()=>{
  15. return firstName.value + '-' + lastName.value
  16. }) */
  17. // 计算属性——既读取又修改
  18. let fullName = computed({
  19. // 读取
  20. get() {
  21. return firstName.value + "-" + lastName.value;
  22. },
  23. // 修改
  24. set(val) {
  25. console.log("有人修改了fullName", val);
  26. firstName.value = val.split("-")[0];
  27. lastName.value = val.split("-")[1];
  28. },
  29. });
  30. function changeFullName() {
  31. fullName.value = "li-si";
  32. }
  33. </script>

 08.【watch】监听属性

作用:监视数据的变化(和`Vue2`中的`watch`作用一致)

 特点:`Vue3`中的`watch`只能监视以下**四种数据**:

1. `ref`定义的数据。

2. `reactive`定义的数据。

3. 函数返回一个值(`getter`函数)。

4. 一个包含上述内容的数组。

我们在`Vue3`中使用`watch`的时候,通常会遇到以下几种情况:

8.1. 情况一

监视`ref`定义的【基本类型】数据:直接写数据名即可,监视的是其`value`值的改变。

  1. <template>
  2. <div class="person">
  3. <h1>情况一:监视【ref】定义的【基本类型】数据</h1>
  4. <h2>当前求和为:{{ sum }}</h2>
  5. <button @click="changeSum">点我sum+1</button>
  6. </div>
  7. </template>
  8. <script lang="ts" setup name="Person">
  9. import { ref, watch } from "vue";
  10. // 数据
  11. let sum = ref(0);
  12. // 方法
  13. function changeSum() {
  14. sum.value += 1;
  15. }
  16. // 监视,情况一:监视【ref】定义的【基本类型】数据
  17. const stopWatch = watch(sum, (newValue, oldValue) => {
  18. console.log("sum变化了", newValue, oldValue);
  19. if (newValue >= 10) {
  20. stopWatch();
  21. }
  22. });
  23. </script>

8.2.情况二

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

注意:

若修改的是`ref`定义的对象中的属性,`newValue` 和 `oldValue` 都是新值,因为它们是同一个对象。

若修改整个`ref`定义的对象,`newValue` 是新值, `oldValue` 是旧值,因为不是同一个对象了。

  1. <template>
  2. <div class="person">
  3. <h1>情况二:监视【ref】定义的【对象类型】数据</h1>
  4. <h2>姓名:{{ person.name }}</h2>
  5. <h2>年龄:{{ person.age }}</h2>
  6. <button @click="changeName">修改名字</button>
  7. <button @click="changeAge">修改年龄</button>
  8. <button @click="changePerson">修改整个人</button>
  9. </div>
  10. </template>
  11. <script lang="ts" setup name="Person">
  12. import { ref, watch } from "vue";
  13. // 数据
  14. let person = ref({
  15. name: "张三",
  16. age: 18,
  17. });
  18. // 方法
  19. function changeName() {
  20. person.value.name += "~";
  21. }
  22. function changeAge() {
  23. person.value.age += 1;
  24. }
  25. function changePerson() {
  26. person.value = { name: "李四", age: 90 };
  27. }
  28. /*
  29. 监视,情况一:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视
  30. watch的第一个参数是:被监视的数据
  31. watch的第二个参数是:监视的回调
  32. watch的第三个参数是:配置对象(deep、immediate等等.....)
  33. */
  34. watch(
  35. person,
  36. (newValue, oldValue) => {
  37. console.log("person变化了", newValue, oldValue);
  38. },
  39. { deep: true }
  40. );
  41. </script>

8.3.情况三

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

  1. <template>
  2. <div class="person">
  3. <h1>情况三:监视【reactive】定义的【对象类型】数据</h1>
  4. <h2>姓名:{{ person.name }}</h2>
  5. <h2>年龄:{{ person.age }}</h2>
  6. <button @click="changeName">修改名字</button>
  7. <button @click="changeAge">修改年龄</button>
  8. <button @click="changePerson">修改整个人</button>
  9. <hr />
  10. <h2>测试:{{ obj.a.b.c }}</h2>
  11. <button @click="test">修改obj.a.b.c</button>
  12. </div>
  13. </template>
  14. <script lang="ts" setup name="Person">
  15. import { reactive, watch } from "vue";
  16. // 数据
  17. let person = reactive({
  18. name: "张三",
  19. age: 18,
  20. });
  21. let obj = reactive({
  22. a: {
  23. b: {
  24. c: 666,
  25. },
  26. },
  27. });
  28. // 方法
  29. function changeName() {
  30. person.name += "~";
  31. }
  32. function changeAge() {
  33. person.age += 1;
  34. }
  35. function changePerson() {
  36. Object.assign(person, { name: "李四", age: 80 });
  37. }
  38. function test() {
  39. obj.a.b.c = 888;
  40. }
  41. // 监视,情况三:监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的
  42. watch(person, (newValue, oldValue) => {
  43. console.log("person变化了", newValue, oldValue);
  44. });
  45. watch(obj, (newValue, oldValue) => {
  46. console.log("Obj变化了", newValue, oldValue);
  47. });
  48. </script>

8.4.情况四

监视`ref`或`reactive`定义的【对象类型】数据中的**某个属性**,注意点如下:

若该属性值**不是**【对象类型】,需要写成函数形式。

若该属性值是**依然**是【对象类型】,可直接编,也可写成函数,建议写成函数。

结论:监视的要是对象里的属性,那么最好写函数式,注意点:若是对象监视的是地址值,需要关注对象内部,需要手动开启深度监视。

  1. <template>
  2. <div class="person">
  3. <h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</h1>
  4. <h2>姓名:{{ person.name }}</h2>
  5. <h2>年龄:{{ person.age }}</h2>
  6. <h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
  7. <button @click="changeName">修改名字</button>
  8. <button @click="changeAge">修改年龄</button>
  9. <button @click="changeC1">修改第一台车</button>
  10. <button @click="changeC2">修改第二台车</button>
  11. <button @click="changeCar">修改整个车</button>
  12. </div>
  13. </template>
  14. <script lang="ts" setup name="Person">
  15. import { reactive, watch } from "vue";
  16. // 数据
  17. let person = reactive({
  18. name: "张三",
  19. age: 18,
  20. car: {
  21. c1: "奔驰",
  22. c2: "宝马",
  23. },
  24. });
  25. // 方法
  26. function changeName() {
  27. person.name += "~";
  28. }
  29. function changeAge() {
  30. person.age += 1;
  31. }
  32. function changeC1() {
  33. person.car.c1 = "奥迪";
  34. }
  35. function changeC2() {
  36. person.car.c2 = "大众";
  37. }
  38. function changeCar() {
  39. person.car = { c1: "雅迪", c2: "爱玛" };
  40. }
  41. // 监视,情况四:监视响应式对象中的某个属性,且该属性是基本类型的,要写成函数式
  42. /* watch(()=> person.name,(newValue,oldValue)=>{
  43. console.log('person.name变化了',newValue,oldValue)
  44. }) */
  45. // 监视,情况四:监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
  46. watch(
  47. () => person.car,
  48. (newValue, oldValue) => {
  49. console.log("person.car变化了", newValue, oldValue);
  50. },
  51. { deep: true }
  52. );
  53. </script>

8.5.情况五

监视上述的多个数据

  1. <template>
  2. <div class="person">
  3. <h1>情况五:监视上述的多个数据</h1>
  4. <h2>姓名:{{ person.name }}</h2>
  5. <h2>年龄:{{ person.age }}</h2>
  6. <h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
  7. <button @click="changeName">修改名字</button>
  8. <button @click="changeAge">修改年龄</button>
  9. <button @click="changeC1">修改第一台车</button>
  10. <button @click="changeC2">修改第二台车</button>
  11. <button @click="changeCar">修改整个车</button>
  12. </div>
  13. </template>
  14. <script lang="ts" setup name="Person">
  15. import { reactive, watch } from "vue";
  16. // 数据
  17. let person = reactive({
  18. name: "张三",
  19. age: 18,
  20. car: {
  21. c1: "奔驰",
  22. c2: "宝马",
  23. },
  24. });
  25. // 方法
  26. function changeName() {
  27. person.name += "~";
  28. }
  29. function changeAge() {
  30. person.age += 1;
  31. }
  32. function changeC1() {
  33. person.car.c1 = "奥迪";
  34. }
  35. function changeC2() {
  36. person.car.c2 = "大众";
  37. }
  38. function changeCar() {
  39. person.car = { c1: "雅迪", c2: "爱玛" };
  40. }
  41. // 监视,情况五:监视上述的多个数据
  42. watch(
  43. [() => person.name, person.car],
  44. (newValue, oldValue) => {
  45. console.log("person.car变化了", newValue, oldValue);
  46. },
  47. { deep: true }
  48. );
  49. </script>

10.【watchEffect】

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

`watch`对比`watchEffect`

1. 都能监听响应式数据的变化,不同的是监听数据变化的方式不同

2. `watch`:要明确指出监视的数据

3. `watchEffect`:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。

示例代码:

  1. <template>
  2. <div class="person">
  3. <h1>需求:水温达到50℃,或水位达到20cm,则联系服务器</h1>
  4. <h2 id="demo">水温:{{ temp }}</h2>
  5. <h2>水位:{{ height }}</h2>
  6. <button @click="changePrice">水温+1</button>
  7. <button @click="changeSum">水位+10</button>
  8. </div>
  9. </template>
  10. <script lang="ts" setup name="Person">
  11. import { ref, watch, watchEffect } from "vue";
  12. // 数据
  13. let temp = ref(0);
  14. let height = ref(0);
  15. // 方法
  16. function changePrice() {
  17. temp.value += 10;
  18. }
  19. function changeSum() {
  20. height.value += 1;
  21. }
  22. // 用watch实现,需要明确的指出要监视:temp、height
  23. watch([temp, height], (value) => {
  24. //value中获取最新的temp值、height值
  25. const [newTemp, newHeight] = value;
  26. // 室温达到50℃,或水位达到20cm,立刻联系服务器
  27. if (newTemp >= 50 || newHeight >= 20) {
  28. console.log("联系服务器");
  29. }
  30. });
  31. // 用watchEffect实现,不用
  32. const stopWtach = watchEffect(() => {
  33. // 室温达到50℃,或水位达到20cm,立刻联系服务器
  34. if (temp.value >= 50 || height.value >= 20) {
  35. console.log(document.getElementById("demo")?.innerText);
  36. console.log("联系服务器");
  37. }
  38. // 水温达到100,或水位达到50,取消监视
  39. if (temp.value === 100 || height.value === 50) {
  40. console.log("清理了");
  41. stopWtach();
  42. }
  43. });
  44. </script>

11. 【标签的 ref 属性】

作用:用于注册模板引用。

用在普通`DOM`标签上,获取的是`DOM`节点。

用在组件标签上,获取的是组件实例对象。

用在普通`DOM`标签上:

  1. <template>
  2. <div class="person">
  3. <h1 ref="title1">尚硅谷</h1>
  4. <h2 ref="title2">前端</h2>
  5. <h3 ref="title3">Vue</h3>
  6. <input type="text" ref="inpt" /> <br /><br />
  7. <button @click="showLog">点我打印内容</button>
  8. </div>
  9. </template>
  10. <script lang="ts" setup name="Person">
  11. import { ref } from "vue";
  12. let title1 = ref();
  13. let title2 = ref();
  14. let title3 = ref();
  15. function showLog() {
  16. // 通过id获取元素
  17. const t1 = document.getElementById("title1");
  18. // 打印内容
  19. console.log((t1 as HTMLElement).innerText);
  20. console.log((<HTMLElement>t1).innerText);
  21. console.log(t1?.innerText);
  22. /************************************/
  23. // 通过ref获取元素
  24. console.log(title1.value);
  25. console.log(title2.value);
  26. console.log(title3.value);
  27. }
  28. </script>

用在组件标签上:

  1. <!-- 父组件App.vue -->
  2. <template>
  3. <Person ref="ren" />
  4. <button @click="test">测试</button>
  5. </template>
  6. <script lang="ts" setup name="App">
  7. import Person from "./components/Person.vue";
  8. import { ref } from "vue";
  9. let ren = ref();
  10. function test() {
  11. console.log(ren.value.name);
  12. console.log(ren.value.age);
  13. }
  14. </script>
  15. <!-- 子组件Person.vue中要使用defineExpose暴露内容 -->
  16. <script lang="ts" setup name="Person">
  17. import { ref, defineExpose } from "vue";
  18. // 数据
  19. let name = ref("张三");
  20. let age = ref(18);
  21. /****************************/
  22. /****************************/
  23. // 使用defineExpose将组件中的数据交给外部
  24. defineExpose({ name, age });
  25. </script>

12. 【props】

  1. `App.vue`中代码:
  2. >
  3. > ```vue
  4. > <template>
  5. > <Person :list="persons" />
  6. > </template>
  7. >
  8. > <script lang="ts" setup name="App">
  9. > import Person from "./components/Person.vue";
  10. > import { reactive } from "vue";
  11. > import { type Persons } from "./types";
  12. >
  13. > let persons = reactive<Persons>([
  14. > { id: "e98219e12", name: "张三", age: 18 },
  15. > { id: "e98219e13", name: "李四", age: 19 },
  16. > { id: "e98219e14", name: "王五", age: 20 },
  17. > ]);
  18. > </script>
  19. > ```
  20. >
  21. > `Person.vue`中代码:
  22. >
  23. > ```Vue
  24. > <template>
  25. > <div class="person">
  26. > <ul>
  27. > <li v-for="item in list" :key="item.id">
  28. > {{item.name}}--{{item.age}}
  29. > </li>
  30. > </ul>
  31. > </div>
  32. > </template>
  33. >
  34. > <script lang="ts" setup name="Person">
  35. > import {defineProps} from 'vue'
  36. > import {type PersonInter} from '@/types'
  37. >
  38. > // 第一种写法:仅接收
  39. > // const props = defineProps(['list'])
  40. >
  41. > // 第二种写法:接收+限制类型
  42. > // defineProps<{list:Persons}>()
  43. >
  44. > // 第三种写法:接收+限制类型+指定默认值+限制必要性
  45. > let props = withDefaults(defineProps<{list?:Persons}>(),{
  46. > list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}]
  47. > })
  48. > console.log(props)
  49. > </script>

13. 【生命周期】

概念:`Vue`组件实例在创建时要经历一系列的初始化步骤,在此过程中`Vue`会在合适的时机,调用特定的函数,从而让开发者有机会在特定阶段运行自己的代码,这些特定的函数统称为:生命周期钩子

规律:

生命周期整体分为四个阶段,分别是:**创建、挂载、更新、销毁**,每个阶段都有两个钩子,一前一后。

`Vue2`的生命周期

创建阶段:`beforeCreate`、`created`

挂载阶段:`beforeMount`、`mounted`

更新阶段:`beforeUpdate`、`updated`

销毁阶段:`beforeDestroy`、`destroyed`

`Vue3`的生命周期

创建阶段:`setup`

挂载阶段:`onBeforeMount`、`onMounted`

更新阶段:`onBeforeUpdate`、`onUpdated`

卸载阶段:`onBeforeUnmount`、`onUnmounted`

常用的钩子:`onMounted`(挂载完毕)、`onUpdated`(更新完毕)、`onBeforeUnmount`(卸载之前)

示例代码:

  1. <template>
  2. <div class="person">
  3. <h2>当前求和为:{{ sum }}</h2>
  4. <button @click="changeSum">点我sum+1</button>
  5. </div>
  6. </template>
  7. <!-- vue3写法 -->
  8. <script lang="ts" setup name="Person">
  9. import {
  10. ref,
  11. onBeforeMount,
  12. onMounted,
  13. onBeforeUpdate,
  14. onUpdated,
  15. onBeforeUnmount,
  16. onUnmounted,
  17. } from "vue";
  18. // 数据
  19. let sum = ref(0);
  20. // 方法
  21. function changeSum() {
  22. sum.value += 1;
  23. }
  24. console.log("setup");
  25. // 生命周期钩子
  26. onBeforeMount(() => {
  27. console.log("挂载之前");
  28. });
  29. onMounted(() => {
  30. console.log("挂载完毕");
  31. });
  32. onBeforeUpdate(() => {
  33. console.log("更新之前");
  34. });
  35. onUpdated(() => {
  36. console.log("更新完毕");
  37. });
  38. onBeforeUnmount(() => {
  39. console.log("卸载之前");
  40. });
  41. onUnmounted(() => {
  42. console.log("卸载完毕");
  43. });
  44. </script>

14. 【自定义 hook】

什么是`hook`?—— 本质是一个函数,把`setup`函数中使用的`Composition API`进行了封装,类似于`vue2.x`中的`mixin`。

自定义`hook`的优势:复用代码, 让`setup`中的逻辑更清楚易懂。

示例代码:

  1. `useSum.ts`中内容如下:
  2. ```js
  3. import { ref, onMounted } from "vue";
  4. export default function () {
  5. let sum = ref(0);
  6. const increment = () => {
  7. sum.value += 1;
  8. };
  9. const decrement = () => {
  10. sum.value -= 1;
  11. };
  12. onMounted(() => {
  13. increment();
  14. });
  15. //向外部暴露数据
  16. return { sum, increment, decrement };
  17. }
  18. ```
  19. - `useDog.ts`中内容如下:
  20. ```js
  21. import {reactive,onMounted} from 'vue'
  22. import axios,{AxiosError} from 'axios'
  23. export default function(){
  24. let dogList = reactive<string[]>([])
  25. // 方法
  26. async function getDog(){
  27. try {
  28. // 发请求
  29. let {data} = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
  30. // 维护数据
  31. dogList.push(data.message)
  32. } catch (error) {
  33. // 处理错误
  34. const err = <AxiosError>error
  35. console.log(err.message)
  36. }
  37. }
  38. // 挂载钩子
  39. onMounted(()=>{
  40. getDog()
  41. })
  42. //向外部暴露数据
  43. return {dogList,getDog}
  44. }
  45. ```
  46. - 组件中具体使用:
  47. ```vue
  48. <template>
  49. <h2>当前求和为:{{ sum }}</h2>
  50. <button @click="increment">点我+1</button>
  51. <button @click="decrement">点我-1</button>
  52. <hr />
  53. <img
  54. v-for="(u, index) in dogList.urlList"
  55. :key="index"
  56. :src="(u as string)"
  57. />
  58. <span v-show="dogList.isLoading">加载中......</span><br />
  59. <button @click="getDog">再来一只狗</button>
  60. </template>
  61. <script lang="ts">
  62. import { defineComponent } from "vue";
  63. export default defineComponent({
  64. name: "App",
  65. });
  66. </script>
  67. <script setup lang="ts">
  68. import useSum from "./hooks/useSum";
  69. import useDog from "./hooks/useDog";
  70. let { sum, increment, decrement } = useSum();
  71. let { dogList, getDog } = useDog();
  72. </script>

15. 路由

路由组件通常存放在`pages` 或 `views`文件夹,一般组件通常存放在`components`文件夹。

通过点击导航,视觉效果上“消失” 了的路由组件,默认是被卸载掉的,需要的时候再去挂载。

【基本切换效果】

- `Vue3`中要使用`vue-router`的最新版本,目前是`4`版本。

- 路由配置文件代码如下:

  1. import { createRouter, createWebHistory } from "vue-router";
  2. import Home from "@/pages/Home.vue";
  3. import News from "@/pages/News.vue";
  4. import About from "@/pages/About.vue";
  5. const router = createRouter({
  6. history: createWebHistory(),
  7. routes: [
  8. {
  9. path: "/home",
  10. component: Home,
  11. },
  12. {
  13. path: "/about",
  14. component: About,
  15. },
  16. ],
  17. });
  18. export default router;
  19. ```
  20. * `main.ts`代码如下:
  21. ```js
  22. import router from "./router/index";
  23. app.use(router);
  24. app.mount("#app");
  25. ```
  26. - `App.vue`代码如下
  27. ```vue
  28. <template>
  29. <div class="app">
  30. <h2 class="title">Vue路由测试</h2>
  31. <!-- 导航区 -->
  32. <div class="navigate">
  33. <RouterLink to="/home" active-class="active">首页</RouterLink>
  34. <RouterLink to="/news" active-class="active">新闻</RouterLink>
  35. <RouterLink to="/about" active-class="active">关于</RouterLink>
  36. </div>
  37. <!-- 展示区 -->
  38. <div class="main-content">
  39. <RouterView></RouterView>
  40. </div>
  41. </div>
  42. </template>
  43. <script lang="ts" setup name="App">
  44. import { RouterLink, RouterView } from "vue-router";
  45. </script>

15.1.【路由器工作模式】

`history`模式

优点:`URL`更加美观,不带有`#`,更接近传统的网站`URL`。

缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有`404`错误。

```js

 const router = createRouter({

  history: createWebHistory(), //history模式

 });

2. `hash`模式

优点:兼容性更好,因为不需要服务器端处理路径。

缺点:`URL`带有`#`不太美观,且在`SEO`优化方面相对较差。

```js

 const router = createRouter({

 history: createWebHashHistory(), //hash模式});

15.2.【to 的两种写法】

  1. <!-- 第一种:to的字符串写法 -->
  2. <router-link active-class="active" to="/home">主页</router-link>
  3. <!-- 第二种:to的对象写法 -->
  4. <router-link active-class="active" :to="{ path: '/home' }">Home</router-link>

15.3.【命名路由】

作用:可以简化路由跳转及传参

给路由规则命名:

  1. routes: [
  2. {
  3. name: "zhuye",
  4. path: "/home",
  5. component: Home,
  6. },
  7. {
  8. name: "xinwen",
  9. path: "/news",
  10. component: News,
  11. },
  12. {
  13. name: "guanyu",
  14. path: "/about",
  15. component: About,
  16. },
  17. ];
  18. 跳转路由:
  19. <!--简化前:需要写完整的路径(to的字符串写法) -->
  20. <router-link to="/news/detail">跳转</router-link>
  21. <!--简化后:直接通过名字跳转(to的对象写法配合name属性) -->
  22. <router-link :to="{ name: 'guanyu' }">跳转</router-link>

15.4【嵌套路由】

  1. 配置路由规则,使用`children`配置项:
  2. const router = createRouter({
  3. history: createWebHistory(),
  4. routes: [
  5. {
  6. name: "zhuye",
  7. path: "/home",
  8. component: Home,
  9. },
  10. {
  11. name: "xinwen",
  12. path: "/news",
  13. component: News,
  14. children: [
  15. {
  16. name: "xiang",
  17. path: "detail",
  18. component: Detail,
  19. },
  20. ],
  21. },
  22. {
  23. name: "guanyu",
  24. path: "/about",
  25. component: About,
  26. },
  27. ],
  28. });
  29. export default router;
  30. 跳转路由(记得要加完整路径):
  31. <router-link to="/news/detail">xxxx</router-link>
  32. <!-- 或 -->
  33. <router-link :to="{ path: '/news/detail' }">xxxx</router-link>

15.5.【路由传参】

  1. 1. 传递参数
  2. <!-- 跳转并携带query参数(to的字符串写法) -->
  3. <router-link to="/news/detail?a=1&b=2&content=欢迎你">
  4. 跳转
  5. </router-link>
  6. <!-- 跳转并携带query参数(to的对象写法) -->
  7. <RouterLink
  8. :to="{
  9. //name:'xiang', //用name也可以跳转
  10. path: '/news/detail',
  11. query: {
  12. id: news.id,
  13. title: news.title,
  14. content: news.content,
  15. },
  16. }"
  17. >
  18. {{news.title}}
  19. </RouterLink>
  20. 2. 接收参数:
  21. ```js
  22. import { useRoute } from "vue-router";
  23. const route = useRoute();
  24. // 打印query参数
  25. console.log(route.query);
  1. params 参数
  2. 1. 传递参数
  3. ```vue
  4. <!-- 跳转并携带params参数(to的字符串写法) -->
  5. <RouterLink
  6. :to="`/news/detail/001/新闻001/内容001`"
  7. >{{news.title}}</RouterLink>
  8. <!-- 跳转并携带params参数(to的对象写法) -->
  9. <RouterLink
  10. :to="{
  11. name: 'xiang', //用name跳转
  12. params: {
  13. id: news.id,
  14. title: news.title,
  15. content: news.title,
  16. },
  17. }"
  18. >
  19. {{news.title}}
  20. </RouterLink>
  21. ```
  22. 2. 接收参数:
  23. ```js
  24. import { useRoute } from "vue-router";
  25. const route = useRoute();
  26. // 打印params参数
  27. console.log(route.params);

备注 1:传递`params`参数时,若使用`to`的对象写法,必须使用`name`配置项,不能用`path`。

备注 2:传递`params`参数时,需要提前在规则中占位。

15.6.【路由的props 配置】

作用:让路由组件更方便的收到参数(可以将路由参数作为`props`传给组件)

  1. {
  2. name:'xiang',
  3. path:'detail/:id/:title/:content',
  4. component:Detail,
  5. // props的对象写法,作用:把对象中的每一组key-value作为props传给Detail组件
  6. // props:{a:1,b:2,c:3},
  7. // props的布尔值写法,作用:把收到了每一组params参数,作为props传给Detail组件
  8. // props:true
  9. // props的函数写法,作用:把返回的对象中每一组key-value作为props传给Detail组件
  10. props(route){
  11. return route.query
  12. }
  13. }

15.7.【 replace 属性】

  1. 1. 作用:控制路由跳转时操作浏览器历史记录的模式。
  2. 2. 浏览器的历史记录有两种写入方式:分别为`push`和`replace`:
  3. - `push`是追加历史记录(默认值)。
  4. - `replace`是替换当前记录。
  5. 3. 开启`replace`模式:
  6. ```vue
  7. <RouterLink replace .......>News</RouterLink>
  8. ```

15.8.【编程式导航】

  1. 路由组件的两个重要的属性:`$route`和`$router`变成了两个`hooks`
  2. <template>
  3. <div class="news">
  4. <!-- 导航区 -->
  5. <ul>
  6. <li v-for="news in newsList" :key="news.id">
  7. <button @click="showNewsDetail(news)">查看新闻</button>
  8. <RouterLink
  9. :to="{
  10. name:'xiang',
  11. query:{
  12. id:news.id,
  13. title:news.title,
  14. content:news.content
  15. }
  16. }"
  17. >
  18. {{news.title}}
  19. </RouterLink>
  20. </li>
  21. </ul>
  22. <!-- 展示区 -->
  23. <div class="news-content">
  24. <RouterView></RouterView>
  25. </div>
  26. </div>
  27. </template>
  28. <script setup lang="ts" name="News">
  29. import {reactive} from 'vue'
  30. import {RouterView,RouterLink,useRouter} from 'vue-router'
  31. const newsList = reactive([
  32. {id:'asfdtrfay01',title:'很好的抗癌食物',content:'西蓝花'},
  33. {id:'asfdtrfay02',title:'如何一夜暴富',content:'学IT'},
  34. {id:'asfdtrfay03',title:'震惊,万万没想到',content:'明天是周一'},
  35. {id:'asfdtrfay04',title:'好消息!好消息!',content:'快过年了'}
  36. ])
  37. const router = useRouter()
  38. interface NewsInter {
  39. id:string,
  40. title:string,
  41. content:string
  42. }
  43. function showNewsDetail(news:NewsInter){
  44. router.replace({
  45. name:'xiang',
  46. query:{
  47. id:news.id,
  48. title:news.title,
  49. content:news.content
  50. }
  51. })
  52. }
  53. </script>

15.9.【重定向】

  1. 1. 作用:将特定的路径,重新定向到已有路由。
  2. 2. 具体编码:
  3.    {
  4.        path:'/',
  5.        redirect:'/about'
  6.    }

16.pinia

  1. 【搭建 pinia 环境】
  2. 第一步:`npm install pinia`
  3. 第二步:操作`src/main.ts`
  4. import { createApp } from "vue";
  5. import App from "./App.vue";
  6. /* 引入createPinia,用于创建pinia */
  7. import { createPinia } from "pinia";
  8. /* 创建pinia */
  9. const pinia = createPinia();
  10. const app = createApp(App);
  11. /* 使用插件 */ {
  12. }
  13. app.use(pinia);
  14. app.mount("#app");
  15. 此时开发者工具中已经有了`pinia`选项

16.1.【存储+读取数据】

  1. 1. `Store`是一个保存:**状态****业务逻辑** 的实体,每个组件都可以**读取****写入**它。
  2. 2. 它有三个概念:`state`、`getter`、`action`,相当于组件中的: `data`、 `computed` 和 `methods`。
  3. 3. 具体编码:`src/store/count.ts`
  4. ```ts
  5. // 引入defineStore用于创建store
  6. import { defineStore } from "pinia";
  7. // 定义并暴露一个store
  8. export const useCountStore = defineStore("count", {
  9. // 动作
  10. actions: {},
  11. // 状态
  12. state() {
  13. return {
  14. sum: 6,
  15. };
  16. },
  17. // 计算
  18. getters: {},
  19. });
  1. 具体编码:`src/store/talk.ts`
  2. ```js
  3. // 引入defineStore用于创建store
  4. import { defineStore } from "pinia";
  5. // 定义并暴露一个store
  6. export const useTalkStore = defineStore("talk", {
  7. // 动作
  8. actions: {},
  9. // 状态
  10. state() {
  11. return {
  12. talkList: [
  13. { id: "yuysada01", content: "你今天有点怪,哪里怪?怪好看的!" },
  14. { id: "yuysada02", content: "草莓、蓝莓、蔓越莓,你想我了没?" },
  15. { id: "yuysada03", content: "心里给你留了一块地,我的死心塌地" },
  16. ],
  17. };
  18. },
  19. // 计算
  20. getters: {},
  21. });
  1. 组件中使用`state`中的数据
  2. ```vue
  3. <template>
  4. <h2>当前求和为:{{ sumStore.sum }}</h2>
  5. </template>
  6. <script setup lang="ts" name="Count">
  7. // 引入对应的useXxxxxStore
  8. import { useSumStore } from "@/store/sum";
  9. // 调用useXxxxxStore得到对应的store
  10. const sumStore = useSumStore();
  11. </script>
  12. ```
  13. ```vue
  14. <template>
  15. <ul>
  16. <li v-for="talk in talkStore.talkList" :key="talk.id">
  17. {{ talk.content }}
  18. </li>
  19. </ul>
  20. </template>
  21. <script setup lang="ts" name="Count">
  22. import axios from "axios";
  23. import { useTalkStore } from "@/store/talk";
  24. const talkStore = useTalkStore();
  25. </script>

16.2.修改数据】(三种方式)

  1. 1. 第一种修改方式,直接修改
  2. ```ts
  3. countStore.sum = 666;
  4. ```
  5. 2. 第二种修改方式:批量修改
  6. ```ts
  7. countStore.$patch({
  8. sum: 999,
  9. school: "atguigu",
  10. });
  11. ```
  12. 3. 第三种修改方式:借助`action`修改(`action`中可以编写一些业务逻辑)
  13. ```js
  14. import { defineStore } from "pinia";
  15. export const useCountStore = defineStore("count", {
  16. /*************/
  17. actions: {
  18. //
  19. increment(value: number) {
  20. if (this.sum < 10) {
  21. //操作countStore中的sum
  22. this.sum += value;
  23. }
  24. },
  25. //
  26. decrement(value: number) {
  27. if (this.sum > 1) {
  28. this.sum -= value;
  29. }
  30. },
  31. },
  32. /*************/
  33. });
  34. ```
  35. 4. 组件中调用`action`即可
  36. ```js
  37. // 使用countStore
  38. const countStore = useCountStore();
  39. // 调用对应action
  40. countStore.incrementOdd(n.value);

16.3.【storeToRefs】

  1. 借助`storeToRefs`将`store`中的数据转为`ref`对象,方便在模板中使用。
  2. - 注意:`pinia`提供的`storeToRefs`只会将数据做转换,而`Vue`的`toRefs`会转换`store`中数据。
  3. ```vue
  4. <template>
  5. <div class="count">
  6. <h2>当前求和为:{{ sum }}</h2>
  7. </div>
  8. </template>
  9. <script setup lang="ts" name="Count">
  10. import { useCountStore } from "@/store/count";
  11. /* 引入storeToRefs */
  12. import { storeToRefs } from "pinia";
  13. /* 得到countStore */
  14. const countStore = useCountStore();
  15. /* 使用storeToRefs转换countStore,随后解构 */
  16. const { sum } = storeToRefs(countStore);
  17. </script>

16.4【getters】

  1. 1. 概念:当`state`中的数据,需要经过处理后再使用时,可以使用`getters`配置。
  2. 2. 追加`getters`配置。
  3. ```js
  4. // 引入defineStore用于创建store
  5. import { defineStore } from "pinia";
  6. // 定义并暴露一个store
  7. export const useCountStore = defineStore("count", {
  8. // 动作
  9. actions: {
  10. /************/
  11. },
  12. // 状态
  13. state() {
  14. return {
  15. sum: 1,
  16. school: "atguigu",
  17. };
  18. },
  19. // 计算
  20. getters: {
  21. bigSum: (state): number => state.sum * 10,
  22. upperSchool(): string {
  23. return this.school.toUpperCase();
  24. },
  25. },
  26. });
  27. ```
  28. 3. 组件中读取数据:
  29. ```js
  30. const { increment, decrement } = countStore;
  31. let { sum, school, bigSum, upperSchool } = storeToRefs(countStore);
  32. ```

16.5.【$subscribe】

  1. 通过 store 的 `$subscribe()` 方法侦听 `state` 及其变化
  2. ```ts
  3. talkStore.$subscribe((mutate, state) => {
  4. console.log("LoveTalk", mutate, state);
  5. localStorage.setItem("talk", JSON.stringify(talkList.value));
  6. });

16.6.【store 组合式写法】

  1. ```ts
  2. import { defineStore } from "pinia";
  3. import axios from "axios";
  4. import { nanoid } from "nanoid";
  5. import { reactive } from "vue";
  6. export const useTalkStore = defineStore("talk", () => {
  7. // talkList就是state
  8. const talkList = reactive(
  9. JSON.parse(localStorage.getItem("talkList") as string) || []
  10. );
  11. // getATalk函数相当于action
  12. async function getATalk() {
  13. // 发请求,下面这行的写法是:连续解构赋值+重命名
  14. let {
  15. data: { content: title },
  16. } = await axios.get("https://api.uomg.com/api/rand.qinghua?format=json");
  17. // 把请求回来的字符串,包装成一个对象
  18. let obj = { id: nanoid(), title };
  19. // 放到数组中
  20. talkList.unshift(obj);
  21. }
  22. return { talkList, getATalk };
  23. });
  24. ```

17.组件通信

`Vue3`组件通信和`Vue2`的区别:

- 移出事件总线,使用`mitt`代替

`vuex`换成了`pinia`。

把`.sync`优化到了`v-model`里面了。

把`$listeners`所有的东西,合并到`$attrs`中了。

$children`被砍掉了。

17.1.【props】

  1. 概述:`props`是使用频率最高的一种通信方式,常用与 :**父 ↔ 子**
  2. - 若 **父传子**:属性值是**非函数**
  3. - 若 **子传父**:属性值是**函数**
  4. 父组件:
  5. ```vue
  6. <template>
  7. <div class="father">
  8. <h3>父组件,</h3>
  9. <h4>我的车:{{ car }}</h4>
  10. <h4>儿子给的玩具:{{ toy }}</h4>
  11. <Child :car="car" :getToy="getToy" />
  12. </div>
  13. </template>
  14. <script setup lang="ts" name="Father">
  15. import Child from "./Child.vue";
  16. import { ref } from "vue";
  17. // 数据
  18. const car = ref("奔驰");
  19. const toy = ref();
  20. // 方法
  21. function getToy(value: string) {
  22. toy.value = value;
  23. }
  24. </script>
  25. ```
  26. 子组件
  27. ```vue
  28. <template>
  29. <div class="child">
  30. <h3>子组件</h3>
  31. <h4>我的玩具:{{ toy }}</h4>
  32. <h4>父给我的车:{{ car }}</h4>
  33. <button @click="getToy(toy)">玩具给父亲</button>
  34. </div>
  35. </template>
  36. <script setup lang="ts" name="Child">
  37. import { ref } from "vue";
  38. const toy = ref("奥特曼");
  39. defineProps(["car", "getToy"]);
  40. </script>

17.2.【自定义事件】

  1. 1. 概述:自定义事件常用于:**=> 父。**
  2. 2. 注意区分好:原生事件、自定义事件。
  3. - 原生事件:
  4. - 事件名是特定的(`click`、`mosueenter`等等)
  5. - 事件对象`$event`: 是包含事件相关信息的对象(`pageX`、`pageY`、`target`、`keyCode`)
  6. - 自定义事件:
  7. - 事件名是任意名称
  8. - <strong style="color:red">事件对象`$event`: 是调用`emit`时所提供的数据,可以是任意类型!!!</strong >
  9. 3. 示例:
  10. ```html
  11. <!--在父组件中,给子组件绑定自定义事件:-->
  12. <Child @send-toy="toy = $event" />
  13. <!--注意区分原生事件与自定义事件中的$event-->
  14. <button @click="toy = $event">测试</button>
  15. ```
  16. ```js
  17. //子组件中,触发事件:
  18. this.$emit("send-toy", 具体数据);
  19. ```

17.3.【mitt】

  1. 概述:与消息订阅与发布(`pubsub`)功能类似,可以实现任意组件间通信。
  2. 安装`mitt`
  3. ```shell
  4. npm i mitt
  5. ```
  6. 新建文件:`src\utils\emitter.ts`
  7. ```javascript
  8. // 引入mitt
  9. import mitt from "mitt";
  10. // 创建emitter
  11. const emitter = mitt();
  12. /*
  13. // 绑定事件
  14. emitter.on('abc',(value)=>{
  15. console.log('abc事件被触发',value)
  16. })
  17. emitter.on('xyz',(value)=>{
  18. console.log('xyz事件被触发',value)
  19. })
  20. setInterval(() => {
  21. // 触发事件
  22. emitter.emit('abc',666)
  23. emitter.emit('xyz',777)
  24. }, 1000);
  25. setTimeout(() => {
  26. // 清理事件
  27. emitter.all.clear()
  28. }, 3000);
  29. */
  30. // 创建并暴露mitt
  31. export default emitter;
  32. ```
  33. 接收数据的组件中:绑定事件、同时在销毁前解绑事件:
  34. ```typescript
  35. import emitter from "@/utils/emitter";
  36. import { onUnmounted } from "vue";
  37. // 绑定事件
  38. emitter.on("send-toy", (value) => {
  39. console.log("send-toy事件被触发", value);
  40. });
  41. onUnmounted(() => {
  42. // 解绑事件
  43. emitter.off("send-toy");
  44. });
  45. ```
  46. 【第三步】:提供数据的组件,在合适的时候触发事件
  47. ```javascript
  48. import emitter from "@/utils/emitter";
  49. function sendToy() {
  50. // 触发事件
  51. emitter.emit("send-toy", toy.value);
  52. }
  53. ```
  54. **注意这个重要的内置关系,总线依赖着这个内置关系**

17.4.【v-model】

  1. 1. 概述:实现 **父 ↔ 子** 之间相互通信。
  2. 2. 前序知识 —— `v-model`的本质
  3. ```vue
  4. <!-- 使用v-model指令 -->
  5. <input type="text" v-model="userName">
  6. <!-- v-model的本质是下面这行代码 -->
  7. <input
  8. type="text"
  9. :value="userName"
  10. @input="userName =(<HTMLInputElement>$event.target).value"
  11. >
  12. ```
  13. 3. 组件标签上的`v-model`的本质:`:moldeValue` + `update:modelValue`事件。
  14. ```vue
  15. <!-- 组件标签上使用v-model指令 -->
  16. <AtguiguInput v-model="userName" />
  17. <!-- 组件标签上v-model的本质 -->
  18. <AtguiguInput
  19. :modelValue="userName"
  20. @update:model-value="userName = $event"
  21. />
  22. ```
  23. `AtguiguInput`组件中:
  24. ```vue
  25. <template>
  26. <div class="box">
  27. <!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 -->
  28. <!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件-->
  29. <input
  30. type="text"
  31. :value="modelValue"
  32. @input="emit('update:model-value', $event.target.value)"
  33. />
  34. </div>
  35. </template>
  36. <script setup lang="ts" name="AtguiguInput">
  37. // 接收props
  38. defineProps(["modelValue"]);
  39. // 声明事件
  40. const emit = defineEmits(["update:model-value"]);
  41. </script>
  42. ```
  43. 4. 也可以更换`value`,例如改成`abc`
  44. ```vue
  45. <!-- 也可以更换value,例如改成abc-->
  46. <AtguiguInput v-model:abc="userName" />
  47. <!-- 上面代码的本质如下 -->
  48. <AtguiguInput :abc="userName" @update:abc="userName = $event" />
  49. ```
  50. `AtguiguInput`组件中:
  51. ```vue
  52. <template>
  53. <div class="box">
  54. <input
  55. type="text"
  56. :value="abc"
  57. @input="emit('update:abc', $event.target.value)"
  58. />
  59. </div>
  60. </template>
  61. <script setup lang="ts" name="AtguiguInput">
  62. // 接收props
  63. defineProps(["abc"]);
  64. // 声明事件
  65. const emit = defineEmits(["update:abc"]);
  66. </script>
  67. ```
  68. 5. 如果`value`可以更换,那么就可以在组件标签上多次使用`v-model`
  69. ```vue
  70. <AtguiguInput v-model:abc="userName" v-model:xyz="password" />
  71. ```

17.5.【$attrs 】

  1. 1. 概述:`$attrs`用于实现**当前组件的父组件**,向**当前组件的子组件**通信(**祖 → 孙**)。
  2. 2. 具体说明:`$attrs`是一个对象,包含所有父组件传入的标签属性。
  3. > 注意:`$attrs`会自动排除`props`中声明的属性(可以认为声明过的 `props` 被子组件自己“消费”了)
  4. 父组件:
  5. ```vue
  6. <template>
  7. <div class="father">
  8. <h3>父组件</h3>
  9. <Child
  10. :a="a"
  11. :b="b"
  12. :c="c"
  13. :d="d"
  14. v-bind="{ x: 100, y: 200 }"
  15. :updateA="updateA"
  16. />
  17. </div>
  18. </template>
  19. <script setup lang="ts" name="Father">
  20. import Child from "./Child.vue";
  21. import { ref } from "vue";
  22. let a = ref(1);
  23. let b = ref(2);
  24. let c = ref(3);
  25. let d = ref(4);
  26. function updateA(value) {
  27. a.value = value;
  28. }
  29. </script>
  30. ```
  31. 子组件:
  32. ```vue
  33. <template>
  34. <div class="child">
  35. <h3>子组件</h3>
  36. <GrandChild v-bind="$attrs" />
  37. </div>
  38. </template>
  39. <script setup lang="ts" name="Child">
  40. import GrandChild from "./GrandChild.vue";
  41. </script>
  42. ```
  43. 孙组件:
  44. ```vue
  45. <template>
  46. <div class="grand-child">
  47. <h3>孙组件</h3>
  48. <h4>a:{{ a }}</h4>
  49. <h4>b:{{ b }}</h4>
  50. <h4>c:{{ c }}</h4>
  51. <h4>d:{{ d }}</h4>
  52. <h4>x:{{ x }}</h4>
  53. <h4>y:{{ y }}</h4>
  54. <button @click="updateA(666)">点我更新A</button>
  55. </div>
  56. </template>
  57. <script setup lang="ts" name="GrandChild">
  58. defineProps(["a", "b", "c", "d", "x", "y", "updateA"]);
  59. </script>
  60. ```

17.6.【$refs、$parent】

  1. 1. 概述:
  2. - `$refs`用于 :**父 → 子。**
  3. - `$parent`用于:**子 → 父。**
  4. 2. 原理如下:
  5. | 属性 | 说明 |
  6. | --------- | -------------------------------------------------------- |
  7. | `$refs` | 值为对象,包含所有被`ref`属性标识的`DOM`元素或组件实例。 |
  8. | `$parent` | 值为对象,当前组件的父组件实例对象。 |
  9. ## 6.7. 【provide、inject】
  10. 1. 概述:实现**祖孙组件**直接通信
  11. 2. 具体使用:
  12. - 在祖先组件中通过`provide`配置向后代组件提供数据
  13. - 在后代组件中通过`inject`配置来声明接收数据
  14. 3. 具体编码:
  15. 【第一步】父组件中,使用`provide`提供数据
  16. ```vue
  17. <template>
  18. <div class="father">
  19. <h3>父组件</h3>
  20. <h4>资产:{{ money }}</h4>
  21. <h4>汽车:{{ car }}</h4>
  22. <button @click="money += 1">资产+1</button>
  23. <button @click="car.price += 1">汽车价格+1</button>
  24. <Child />
  25. </div>
  26. </template>
  27. <script setup lang="ts" name="Father">
  28. import Child from "./Child.vue";
  29. import { ref, reactive, provide } from "vue";
  30. // 数据
  31. let money = ref(100);
  32. let car = reactive({
  33. brand: "奔驰",
  34. price: 100,
  35. });
  36. // 用于更新money的方法
  37. function updateMoney(value: number) {
  38. money.value += value;
  39. }
  40. // 提供数据
  41. provide("moneyContext", { money, updateMoney });
  42. provide("car", car);
  43. </script>
  44. ```
  45. > 注意:子组件中不用编写任何东西,是不受到任何打扰的
  46. 【第二步】孙组件中使用`inject`配置项接受数据。
  47. ```vue
  48. <template>
  49. <div class="grand-child">
  50. <h3>我是孙组件</h3>
  51. <h4>资产:{{ money }}</h4>
  52. <h4>汽车:{{ car }}</h4>
  53. <button @click="updateMoney(6)">点我</button>
  54. </div>
  55. </template>
  56. <script setup lang="ts" name="GrandChild">
  57. import { inject } from "vue";
  58. // 注入数据
  59. let { money, updateMoney } = inject("moneyContext", {
  60. money: 0,
  61. updateMoney: (x: number) => {},
  62. });
  63. let car = inject("car");
  64. </script>
  65. ```

17.7. 【slot】

  1. ### 1. 默认插槽
  2. ![img](http://49.232.112.44/images/default_slot.png)
  3. ```vue
  4. 父组件中:
  5. <Category title="今日热门游戏">
  6. <ul>
  7. <li v-for="g in games" :key="g.id">{{ g.name }}</li>
  8. </ul>
  9. </Category>
  10. 子组件中:
  11. <template>
  12. <div class="item">
  13. <h3>{{ title }}</h3>
  14. <!-- 默认插槽 -->
  15. <slot></slot>
  16. </div>
  17. </template>
  18. ```
  19. ### 2. 具名插槽
  20. ```vue
  21. 父组件中:
  22. <Category title="今日热门游戏">
  23. <template v-slot:s1>
  24. <ul>
  25. <li v-for="g in games" :key="g.id">{{ g.name }}</li>
  26. </ul>
  27. </template>
  28. <template #s2>
  29. <a href="">更多</a>
  30. </template>
  31. </Category>
  32. 子组件中:
  33. <template>
  34. <div class="item">
  35. <h3>{{ title }}</h3>
  36. <slot name="s1"></slot>
  37. <slot name="s2"></slot>
  38. </div>
  39. </template>
  40. ```
  41. ### 3. 作用域插槽
  42. 1. 理解:<span style="color:red">数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。</span>(新闻数据在`News`组件中,但使用数据所遍历出来的结构由`App`组件决定)
  43. 2. 具体编码:
  44. ```vue
  45. 父组件中:
  46. <Game v-slot="params">
  47. <!-- <Game v-slot:default="params"> -->
  48. <!-- <Game #default="params"> -->
  49. <ul>
  50. <li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
  51. </ul>
  52. </Game>
  53. 子组件中:
  54. <template>
  55. <div class="category">
  56. <h2>今日游戏榜单</h2>
  57. <slot :games="games" a="哈哈"></slot>
  58. </div>
  59. </template>
  60. <script setup lang="ts" name="Category">
  61. import { reactive } from "vue";
  62. let games = reactive([
  63. { id: "asgdytsa01", name: "英雄联盟" },
  64. { id: "asgdytsa02", name: "王者荣耀" },
  65. { id: "asgdytsa03", name: "红色警戒" },
  66. { id: "asgdytsa04", name: "斗罗大陆" },
  67. ]);
  68. </script>

18.其它 API

18.1【shallowRef 与 shallowReactive 】

  1. ### `shallowRef`
  2. 1. 作用:创建一个响应式数据,但只对顶层属性进行响应式处理。
  3. 2. 用法:
  4. ```js
  5. let myVar = shallowRef(initialValue);
  6. ```
  7. 3. 特点:只跟踪引用值的变化,不关心值内部的属性变化。
  8. ### `shallowReactive`
  9. 1. 作用:创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性则不会变成响应式的
  10. 2. 用法:
  11. ```js
  12. const myObj = shallowReactive({ ... });
  13. ```
  14. 3. 特点:对象的顶层属性是响应式的,但嵌套对象的属性不是。
  15. ### 总结
  16. > 通过使用 [`shallowRef()`](https://cn.vuejs.org/api/reactivity-advanced.html#shallowref) 和 [`shallowReactive()`](https://cn.vuejs.org/api/reactivity-advanced.html#shallowreactive) 来绕开深度响应。浅层式 `API` 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可提升性能。

18.2.【readonly 与 shallowReadonly】

  1. ### **`readonly`**
  2. 1. 作用:用于创建一个对象的深只读副本。
  3. 2. 用法:
  4. ```js
  5. const original = reactive({ ... });
  6. const readOnlyCopy = readonly(original);
  7. ```
  8. 3. 特点:
  9. - 对象的所有嵌套属性都将变为只读。
  10. - 任何尝试修改这个对象的操作都会被阻止(在开发模式下,还会在控制台中发出警告)。
  11. 4. 应用场景:
  12. - 创建不可变的状态快照。
  13. - 保护全局状态或配置不被修改。
  14. ### **`shallowReadonly`**
  15. 1. 作用:与 `readonly` 类似,但只作用于对象的顶层属性。
  16. 2. 用法:
  17. ```js
  18. const original = reactive({ ... });
  19. const shallowReadOnlyCopy = shallowReadonly(original);
  20. ```
  21. 3. 特点:
  22. - 只将对象的顶层属性设置为只读,对象内部的嵌套属性仍然是可变的。
  23. - 适用于只需保护对象顶层属性的场景。

18.3.【toRaw 与 markRaw】

  1. ### `toRaw`
  2. 1. 作用:用于获取一个响应式对象的原始对象, `toRaw` 返回的对象不再是响应式的,不会触发视图更新。
  3. > 官网描述:这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。
  4. > 何时使用? —— 在需要将响应式对象传递给非 `Vue` 的库或外部系统时,使用 `toRaw` 可以确保它们收到的是普通对象
  5. 2. 具体编码:
  6. ```js
  7. import { reactive, toRaw, markRaw, isReactive } from "vue";
  8. /* toRaw */
  9. // 响应式对象
  10. let person = reactive({ name: "tony", age: 18 });
  11. // 原始对象
  12. let rawPerson = toRaw(person);
  13. /* markRaw */
  14. let citysd = markRaw([
  15. { id: "asdda01", name: "北京" },
  16. { id: "asdda02", name: "上海" },
  17. { id: "asdda03", name: "天津" },
  18. { id: "asdda04", name: "重庆" },
  19. ]);
  20. // 根据原始对象citys去创建响应式对象citys2 —— 创建失败,因为citys被markRaw标记了
  21. let citys2 = reactive(citys);
  22. console.log(isReactive(person));
  23. console.log(isReactive(rawPerson));
  24. console.log(isReactive(citys));
  25. console.log(isReactive(citys2));
  26. ```
  27. ### `markRaw`
  28. 1. 作用:标记一个对象,使其**永远不会**变成响应式的。
  29. > 例如使用`mockjs`时,为了防止误把`mockjs`变为响应式对象,可以使用 `markRaw` 去标记`mockjs`
  30. 2. 编码:
  31. ```js
  32. /* markRaw */
  33. let citys = markRaw([
  34. { id: "asdda01", name: "北京" },
  35. { id: "asdda02", name: "上海" },
  36. { id: "asdda03", name: "天津" },
  37. { id: "asdda04", name: "重庆" },
  38. ]);
  39. // 根据原始对象citys去创建响应式对象citys2 —— 创建失败,因为citys被markRaw标记了
  40. let citys2 = reactive(citys);
  41. ```

18.4.【customRef】

  1. 作用:创建一个自定义的`ref`,并对其依赖项跟踪和更新触发进行逻辑控制。
  2. 实现防抖效果(`useSumRef.ts`):
  3. ```typescript
  4. import { customRef } from "vue";
  5. export default function (initValue: string, delay: number) {
  6. let msg = customRef((track, trigger) => {
  7. let timer: number;
  8. return {
  9. get() {
  10. track(); // 告诉Vue数据msg很重要,要对msg持续关注,一旦变化就更新
  11. return initValue;
  12. },
  13. set(value) {
  14. clearTimeout(timer);
  15. timer = setTimeout(() => {
  16. initValue = value;
  17. trigger(); //通知Vue数据msg变化了
  18. }, delay);
  19. },
  20. };
  21. });
  22. return { msg };
  23. }

19.Vue3 新组件

19.1. 【Teleport】

  1. - 什么是 Teleport?—— Teleport 是一种能够将我们的**组件 html 结构**移动到指定位置的技术。
  2. ```html
  3. <teleport to="body">
  4. <div class="modal" v-show="isShow">
  5. <h2>我是一个弹窗</h2>
  6. <p>我是弹窗中的一些内容</p>
  7. <button @click="isShow = false">关闭弹窗</button>
  8. </div>
  9. </teleport>
  10. ```

19.2. 【Suspense】

  1. - 等待异步组件时渲染一些额外内容,让应用有更好的用户体验
  2. - 使用步骤:
  3. - 异步引入组件
  4. - 使用`Suspense`包裹组件,并配置好`default` 与 `fallback`
  5. ```tsx
  6. import { defineAsyncComponent, Suspense } from "vue";
  7. const Child = defineAsyncComponent(() => import("./Child.vue"));
  8. ```
  9. ```vue
  10. <template>
  11. <div class="app">
  12. <h3>我是App组件</h3>
  13. <Suspense>
  14. <template v-slot:default>
  15. <Child />
  16. </template>
  17. <template v-slot:fallback>
  18. <h3>加载中.......</h3>
  19. </template>
  20. </Suspense>
  21. </div>
  22. </template>
  23. ```

19.3.【全局 API 转移到应用对象】

- `app.component`

- `app.config`

- `app.directive`

- `app.mount`

- `app.unmount`

- `app.use`

 19.4.【其他】

- 过渡类名 `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`。

  ......

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/441050
推荐阅读
相关标签
  

闽ICP备14008679号