当前位置:   article > 正文

vue3vue3vue3vue3vue3vue3vue3vue3vue3vue3vue3vue3

vue3vue3vue3vue3vue3vue3vue3vue3vue3vue3vue3vue3

纯vue3的语法

一.创建(基于vite)

1.在指定目录下运行

npm create vue@latest
在这里插入图片描述

  • 项目名称:英文小写+下划线+数字
  • 回车表示确定
  • 是、否 左右切换
  • 路由、pina、单元测试、端到端的测试、开启eslint控制代码质量 先选择no,学的时候自己手动配置

2.目录的介绍
1).vscode文件下的extensions.json文件用于 插件安装推荐,也可以删掉
在这里插入图片描述
又这个文件,vscode的右下角就会出现插件提示
在这里插入图片描述
需要安装这两插件
在这里插入图片描述
在这里插入图片描述

2)env.d.ts文件
由于ts不认识.ts、.jpg、.vue等文件,所以引入的时候会飘红。有了这个文件就不会红了
在这里插入图片描述
3)index.html入口文件
4)package.json 包的管理文件
5)tsconfig.json ts的配置文件
在这里插入图片描述
6)vite.config.ts等的配置文件

二.安装

1.vue devtool

安装地址:https://chrome.zzzmh.cn/index
在这里插入图片描述
把解压包里面的crx 文件直接拖拽到浏览器中的扩展工具
在这里插入图片描述
在这里插入图片描述
如果没有显示 添加扩展工具的话 请把左侧的开发者模式打开
在这里插入图片描述

2.vue-offical

安装以后,ref的value自动补充完整
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三.vue3的核心语法

1.OptionsAPI 与CompositionAPI

Vue2 是选项式API(OptionsAPI,选项式风格),data、methods、name都是选项
vue3 组合式API(CompositionAPI,配置式风格)

1.1 OptionsAPI的弊端

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

1.2 CompositionAPI 的优势

可以用函数式的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。

2.setup

  • setup中的this是undefiendVue3中已经弱化this
  • 直接在setup中打印数据比beforeCreate和data还靠前。data里面通过this.可以获取到steup中定义的数据;但steup里面无法获取data里面的数据
  • setup的返回值也可以是个渲染函数

2.1 setup语法糖

  1. 引入组件的时候不需要注册组件
  2. 定义变量的时候不需要return出去,顶层的绑定会暴露给模板,模板中可以直接使用
<script lang="ts" setup>
  import {ref} from "vue"
  // 只需要引入需要的组件,不需要注册组件
  import Test from "./components/Test.vue"
  // 定义变量不需要return出去
  const count = ref(0)
  const Add = () =>{
    count.value++
  }
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. setup中没法直接定义或修改组件的name。组件的name默认是文件的名字,但如果要求跟文件名字不一样,就要修改。
    如果需要重新定义的话,有两种办法:
  • 在单独写个script,专门写name的,但这样就会有两个script标签
  • 引入vite-plugin-vue-setup-extend插件
    安装命令:npm install vite-plugin-vue-setup-extend -D 然后在vite.config.js`添加如下代码:
    在这里插入图片描述
    在页面就可以直接在script标签里面加name属性了:
    在这里插入图片描述

2.2 ref和reactive

  1. 宏观角度看:
  • ref 用来定义:基本类型教据、对象类型数据;
  • reactive 只能定义:对象类型数据。
  1. 区别:
  • ref 创建的变量必须使用.value(可以使用volar播件自动添加.value)。
  • reactive 重新分配一个对象,会失去响应式(可以使用 0bject.assign去整体替换)
  1. 使用原则:
  • 若需要一个基本类型的响应式数据,必须使用ref。
  • 若需要一个响应式对象,展级不深,ref、reactive 都可以
  • 若需要一个响应式对象,且层圾较深,推荐使用reactive。
<template>
  <div>
    食物:{{ food.type }}---{{ food.price }}

    汽车:{{ car.type }}----{{ car.price }}
    <br />
    <button @click="changeFood">修改</button>
  </div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
let food = reactive({
  type: "apple",
  price: 15,
});
let car = ref({ type: "宝马", price: 33000 });

const changeFood = () => {
  //直接这样写不更新
  // food = reactive({
  //   type: "orange",
  //   price: 21,
  // });
  // food = {
  //   type: "orange",
  //   price: 21,
  // };

  // //以下写法可以更新
  Object.assign(food, { type: "orange", price: 21 });
  // //或者
  car.value = { type: "奥拓", price: 666000 };
};
</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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

2.3 toRefs和toRef

  • toRefs 解构赋值,给新的变量转为ref
  • toRef 给新的变量取值
<template>
  <div class="person">
    <h2>姓名:{{ person.name }}---{{ name }}</h2>
    <h2>年龄:{{ person.age }}----{{age}}---{{ nl }}</h2>
    <button @click="changeName">修改姓名</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>
<script setup lang="ts">
import { ref, reactive, toRefs, toRef } from "vue";
let person = reactive({
  name: "张三",
  age: 15,
});
//解构赋值,给新的变量转为ref
let { name, age } = toRefs(person);
//给新的变量取值
let nl = toRef(person, "age");

const changeName = () => {
  name.value += "~";
};
const changeAge = () => {
  age.value += 1;
  nl.value += 2;
};
</script>

<style scoped>
</style>

  • 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

2.4 computed

1)只读的写法——get

<template>
  <div class="person">
    <h2>姓:{{ firstName }}</h2>
    <br  />
    <h2>名:{{ lastName }}</h2>
    <br  />
    <h2>全名:{{ fullName }}</h2>
    
  </div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
let firstName = ref('zhang')
let lastName = ref('san')

let fullName = computed(()=>{
  return firstName.value +lastName.value
})

</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2)可读可写的写法_get和set

<template>
  <div class="person">
    <h2>姓:{{ firstName }}</h2>
    <br />
    <h2>名:{{ lastName }}</h2>
    <br />
    <h2>全名:{{ fullName }}</h2>
    <br />
    <button @click="changeFullName">改全名</button>
  </div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
let firstName = ref("zhang");
let lastName = ref("san");

let fullName = computed({
  get() {
    return firstName.value + "-" + lastName.value;
  },
  set(val) {
    let arr = val.split("-");
    firstName.value = arr[0];
    lastName.value = arr[1];
  },
});

const changeFullName = () => {
  fullName.value = "li-si";
};
</script>

<style scoped>
</style>

  • 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

2.5 watch

vue3中的watch只能监听以下四种数据

  • ref 定义的数据
  • reactive 定义的数据
  • 函数返回的一个值(getter函数)
  • 一个包含上述内容的数组
    我们在vue3中使用watch的时候,通常会遇到以下几种情况:

情况一

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

监听的ref值不用写.value

<template>
  <div class="person">
    <h2>当前求和为:{{ sum }}</h2>
    <br />
    <button @click="changeNum">点我sum+1</button>
  </div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from "vue";
let sum = ref(0);
const changeNum = () => {
  sum.value += 1;
};
const stopWatch = watch(sum, (newVal, oldVal) => {
  console.log("sum变化了", newVal, oldVal);
  //停止监听
  if (newVal >= 10) {
    stopWatch();
  }
});
</script>

<style scoped>
</style>

  • 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

情况二

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

注意:

  • 若修改的是ref 定义的对象中的属性,newValue 和 oldValue 都是新值,因为它们是同一个对象,。
  • 若修改整个 ref 定义的对象, newValue 是新值, oldvalue 是旧值,因为不是同一个对象了。
<template>
  <div class="person">
    <h2>person:{{ person.name }}-------{{ person.age }}</h2>
    <br />
    <button @click="changeName">修改名字</button>
  </div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
let person = ref({
  name: "张三",
  age: 15,
});
const changeName = () => {
  person.value.name = '李四';
};
watch(
  person,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  //深度监听
  {
    deep: true,
  }
);
</script>

<style scoped>
</style>

  • 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

情况三

监视 reactive 定义的【对象类型】数据,且默认开启了深度监视。而且这个深度监视关不掉。
不需要手动加{deep:true}

<template>
  <div class="person">
    <h2>person:{{ person.name }}-------{{ person.age }}</h2>
    <br />
    <button @click="changeName">修改名字</button>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from "vue";
let person = reactive({
  name: "张三",
  age: 15,
});
const changeName = () => {
  person.name += '~';
  person.age += 1;
};
watch(
  person,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  }
);
</script>

<style scoped>
</style>

  • 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

情况四

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

  1. 若该属性值不是【对象类型】,需要写成函数形式。
  2. 若该属性值是依然是【对象类型】,可直接编,也可写成函数,不过建议写成函数。

总结:修改对象下的某个属性,都写成函数

<template>
  <div class="person">
    <h2>person:{{ person.name }}-------{{ person.age }}</h2>
    <br />
    车:{{ person.car.c1 }},{{ person.car.c2 }}
    <button @click="changeName">修改名字</button>
    <button @click="changeC1">修改第一台车</button>
    <button @click="changeC2">修改第二台车</button>
    <button @click="changeCar">修改所有车</button>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from "vue";
let person = reactive({
  name: "张三",
  age: 15,
  car: {
    c1: "奥迪",
    c2: "宝马",
  },
});
const changeName = () => {
  person.name += "~";
  person.age += 1;
};

const changeC1 = () => {
  person.car.c1 = "特斯拉";
};

const changeC2 = () => {
  person.car.c2 = "比亚迪";
};
const changeCar = () => {
  person.car = {
    c1: "摩托罗拉",
    c2: "大众",
  };
};
watch(
  () => person.name,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  }
);

watch(
  () => person.car,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  },
  {
    deep: true,
  }
);
</script>

<style scoped>
</style>

  • 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
  • 59
  • 60

情况五

监听上述多个数据

<template>
  <div class="person">
    <h2>person:{{ person.name }}-------{{ person.age }}</h2>
    <br />
    车:{{ person.car.c1 }},{{ person.car.c2 }}
    <button @click="changeName">修改名字</button>
    <button @click="changeC1">修改第一台车</button>
    <button @click="changeC2">修改第二台车</button>
    <button @click="changeCar">修改所有车</button>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from "vue";
let person = reactive({
  name: "张三",
  age: 15,
  car: {
    c1: "奥迪",
    c2: "宝马",
  },
});
const changeName = () => {
  person.name += "~";
  person.age += 1;
};

const changeC1 = () => {
  person.car.c1 = "特斯拉";
};

const changeC2 = () => {
  person.car.c2 = "比亚迪";
};
const changeCar = () => {
  person.car = {
    c1: "摩托罗拉",
    c2: "大众",
  };
};

watch([() => person.name, () => person.car.c1], (newVal, oldVal) => {
  console.log(newVal, oldVal);
});

</script>

<style scoped>
</style>

  • 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

2.6 watchEffect

  • 官网:立即远行一个函数,同时响应式地追踪其依稳,并在依较更改时重新执行该的数

  • watch 对比watchEffect

  1. 都能监听前应式敷冢的变化。不同的是监听数报变化的方式不同
  2. watch 要明确指出监视的数据watch
  3. watcheffect:不用明确指出监视的数据(函数中用到哪些属性,那就监视哪些属性)。
<template>
  <div class="person">
    <h1>需求:水温达到50℃,或水位达到80cm,则联系服务器</h1>
    <h2>水温:{{ temp }}</h2>
    <h2>水位:{{ height }}</h2>
    <button @click="changeTemp">水温+10</button>
    <button @click="changeHeight">水位+10</button>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, watch, watchEffect } from "vue";
let temp = ref(0);
let height = ref(0);

const changeTemp = () => {
  temp.value += 10;
};

const changeHeight = () => {
  height.value += 10;
};

//watch 实现需求
// watch([temp, height], (val) => {
//   let [temp, height] = val;
//   if (temp >= 50 || height >= 80) {
//     console.log("联系服务器");
//   }
// });

//watchEffect 实现需求
watchEffect(() => {
  if (temp.value >= 50 || height.value >= 80) {
    console.log("联系服务器");
  }
});
</script>

<style scoped>
</style>

  • 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

2.7 【标签的 ref 属性】

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

  • 用在普通 DOM 标签上,获取的是 DOM 节点.
  • 用在组件标签上,获取的是组件实例对象。

父组件:

<template>
  <div class="person">
    <h1 ref="title2">您好</h1>
    <button @click="showlog1">点我输出h2【您好】 这个元素</button>
    <button @click="showlog2">点我输出子组件【人】 这个元素</button>

    <hr>
    <Person ref="ren"></Person>
  </div>
</template>
<script setup lang="ts">
import Person from "./components/Person.vue";
import { ref } from "vue";
let title2 = ref()
let ren = ref()

const showlog1 = ()=>{
  console.log(title2.value)
}
const showlog2 = ()=>{
  console.log(ren.value.a)
  console.log(ren.value.b)
  console.log(ren.value.c)
}
</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
  • 26
  • 27

子组件Person:

<template>
  <div class="person">
    <h1>我是--人组件</h1>
    <h3 ref="title2"></h3>
    <button @click="showlog">点我输出h3【人】这个元素</button>
    
  </div>
</template>
<script setup lang="ts">
import { ref, defineExpose } from "vue";

//创建一个title2,用于存储ref标记的内容
let title2 = ref()
let a = ref(1)
let b = ref(2)
let c = ref(3)

const showlog = ()=>{
  console.log(title2.value)
}
//子组件向父组件暴露数据,让父组件能访问
defineExpose({a,b,c})
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

3.ts的接口、泛型、自定义类型

1. 定义.ts的文件

//定义一个接口,用于限制person对象的具体属性
export interface PersonInter {
    id: string,
    name: string,
    age: number
}

//一个自定义类型(一类人,数组)
//第一种写法
export type Persons = Array<PersonInter>
//第二种写法
// export type Persons = PersonInter[]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2. 使用

属性名不对,或者类型不对,就可以校验提示

<template>
  <div class="person">???</div>
</template>
<script setup lang="ts">
import { type PersonInter, type Persons } from "@/types/index.ts";
//固定一个人
let person: PersonInter = { id: "sgdiuahsdiahi1", name: "张三", age: 19 };

//固定一类人
let personList: Array<PersonInter> = [
  { id: "sgdiuahsdiahi1", name: "张三", age: 19 },
  { id: "sgdiuahsdiahi2", name: "李四", age: 22 },
  { id: "sgdiuahsdiahi3", name: "王五", age: 21 },
];
// 或者这样写
// let personList: Persons = [
//   { id: "sgdiuahsdiahi1", name: "张三", age: 19 },
//   { id: "sgdiuahsdiahi2", name: "李四", age: 22 },
//   { id: "sgdiuahsdiahi3", name: "王五", age: 21 },
// ];

</script>

<style scoped>
</style>

  • 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

4.props的使用

注意:

withDefaults,和 defineExpose 不用引入,可以直接使用。
defineXXX属于宏函数,Vue3中不用引入,直接使用

.ts文件

//定义一个接口,用于限制person对象的具体属性
export interface PersonInter {
    id: string,
    name: string,
    age: number
}

//一个自定义类型(一类人,数组)
export type Persons = Array<PersonInter>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

父组件:

<template>
  <Person :list="personList" />
</template>
<script setup lang="ts">
import { reactive } from "vue";
import { type Persons } from "@/types/index.ts";
import Person from "@/components/Person.vue";

//对reactive进行类型限制
let personList = reactive<Persons>([
  { id: "sgdiuahsdiahi1", name: "张三", age: 19 },
  { id: "sgdiuahsdiahi2", name: "李四", age: 22 },
  { id: "sgdiuahsdiahi3", name: "王五", age: 21 },
]);
</script>

<style scoped>
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

子组件:

<template>
  <div class="person">
    <ul>
      <li v-for="item in list" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>
<script setup lang="ts">
import { ref, defineProps, withDefaults } from "vue";
import { type Persons } from "@/types/index.ts";

//限定类型+限定必要性+指定默认值
let props = withDefaults(defineProps<{ list: Persons }>(), {
  list: () => [{ id: "1", name: "小妖", age: 22 }],
});
console.log(props);
</script>

<style scoped>
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

5.生命周期

生命周期分为四个阶段:创建,挂载,更新,销毁 每个阶段两个钩子,一前一后。

vue2的生命周期:

创建阶段:beforeCreatecreated
挂载阶段:beforemountmounted
更新阶段:beforeUpdateupdated
销毁阶段:beforeDestroydestroyed

vue3的生命周期:

创建阶段:setup
挂载阶段:onBeforemountonMounted
更新阶段:onBeforeUpdateonUpdated
销毁阶段:onBeforeUnmountonUnmounted

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

6.hooks

用于提取js或ts

主页面:

<template>
  <h2>当前求和为:{{ sum }}</h2>
  <el-button type="primary" @click="add">点我sum+1</el-button>
  <hr>
  其他内容
</template>
<script setup lang="ts">
import { reactive,ref,computed, watch } from "vue";
import useSum from '@/hooks/useSum'
let {sum, add} = useSum()
</script>

<style scoped>
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

hooks页面:

import { reactive, ref } from "vue";
export default function () {
  let sum = ref(0);

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

  return {sum, add}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

7.路由

7.1 路由模式

1)history模式

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

const router = VueRouter.createRouter({
  history: VueRouter.createWebHistory(),
  routes, // `routes: routes` 的缩写
})
  • 1
  • 2
  • 3
  • 4

2)hash模式

优点:兼容性更好,因为不需要服务器端处理路径,缺点: URL 带有#不太美观,且在 SE0 优化方面相对较差。

const router = VueRouter.createRouter({
  history: VueRouter.createWebHashHistory(),
  routes, // `routes: routes` 的缩写
})
  • 1
  • 2
  • 3
  • 4

7.2 路由传参

7.2.1 query

1)传参

第一种方式:
在路由路径后直接拼接?参数名:参数值 ,多组参数间使用&分隔

<RouterLink to="/path/path1?name=小红&age=23"></RouterLink>
  • 1

第二种方式:
to不再传递字符,而是传一个对象,由于参数为对象,所以to前需要加上

<RouterLink :to="{ 
	path: "/path/path1",
	query: {
		name: "小红",
		age: 23
	}
}"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
2)参数接收:
// 接收
import { useRoute } from "vue-router"
const route = useRoute()

// 使用
<div>{{ route.query.name }}</div>
<div>{{ route.query.age }}</div>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

1.2.2 params

1)传参

第一种写法:
在路由路径后直接拼接/参数值

<RouterLink to="/path/path1/小红/23"></RouterLink>
  • 1

在路由规则中提前声明参数名,参数名前不要丢失冒号
这里给 路由/path/path1 添加了name和age参数,由于age后添加了问号,所以age为可传参数,否则未传age会报错。

{
	path:"/path",
	component: Comp1
	children:[
		{ path:'path1/:name/:age?',component: Comp2 }
	]
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

第二种写法:
to传对象写法
路径使用name,注意name需保持与路由规则中的一致

<RouterLink :to="{ 
	name: "path1Name",
	params: {
		name: "田本初",
		age: 23
	}
}"/>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
2)参数接收
// 接收
import { useRoute } from "vue-router"
const route = useRoute()

// 使用
<div>{{ route.params.name }}</div>
<div>{{ route.params.age }}</div>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

备注1:传递 parans 参数时,若使用 to的对象写法,必须使用 na=e 配置项,不能用 path。
备注2:params拼接字符串在路径后直接 /参数值即可,但需要在路由规则中提前声明参数名
备注3:对象写法中,query参数既可以使用path又可以使用name,但是params参数只能使用name

7.3 将 props 传递给路由组件

如何简化参数使用
方法一: 解构 配合 toRefs
如果解构使用query/params对象,由于是直接从响应式数据中解构,变量会丢失响应式,需要使用toRefs

// 接收
import { useRoute } from "vue-router"
import { toRefs } from "vue"
const route = useRoute()
const { query } = toRefs(route)

// 使用
<div>{{ query.name }}</div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

方法二:路由的props配置
下面就会讲到props配置的三种方法

7.3.1 第一种写法

将路由收到的所有params参数作为props传给路由组件(只用于params传参)

  1. 路由规则中添加 props:true
// 路由规则配置
{ path:'/path/path1/:name/:age', component: Comp2, props: true }
  • 1
  • 2
  1. 使用参数时,defineProps([‘name’,‘age’])
defineProps(['name','age'])

<div>{{ name }}</div>
<div>{{ age }}</div>

  • 1
  • 2
  • 3
  • 4
  • 5

7.3.2 第二种写法:函数写法

params和query传参都可以使用,一般用于处理query参数,需要写成函数形式

  1. 路由规则中添加props函数
// 路由规则配置
{ 
	path:'/path/path1/:name/:age', 
	component: Comp2, 
	props(route){
		return route.query
	} 
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

使用参数时,defineProps([‘name’,‘age’])

defineProps(['name','age'])

<div>{{ name }}</div>
<div>{{ age }}</div>

  • 1
  • 2
  • 3
  • 4
  • 5

7.3.3 第三种写法

很少使用,就是写死的

props:{
	a:100
	b:200
	c:380
}
  • 1
  • 2
  • 3
  • 4
  • 5

8.pina

来个对象解构赋值

let obj = {
  country: {
    province: {
      city: {
        qu: "瑶海区",
      },
    },
  },
};
//下面写法是连续解构+重命名
let {
  country: {
    province: {
      city: { qu: qulala },
    },
  },
} = obj;
console.log(qulala);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

8.1 搭建环境

  1. 安装

npm install pinia

  1. 引入
import { createApp } from 'vue'
//第一步:引入pinia
import { createPinia } from 'pinia'
import App from './App.vue'
//第二步:创建pinia
const pinia = createPinia()
const app = createApp(App)

//第三步:安装pinia
app.use(pinia).mount('#app')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注意:第三步不能错,不然vue调试工具没有Pinia模块
在这里插入图片描述

8.2 存储+读取数据

定义

// stores/counter.js
import { createPinia, defineStore } from "pinia";
export const usePersonStore = defineStore("person", {
  state: () => {
    return {
      count: 0,
    };
  },
});

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

使用:

<template>
  <div class="person">
    {{ personStore.count }}
  </div>
</template>
<script setup lang="ts">
import { usePersonStore } from "@/store/person";
const personStore = usePersonStore();

//如果打印count有两种取值的方法:
// 第一种:
console.log(personStore.count);
// 第二种:
console.log(personStore.$state.count);
</script>

<style scoped>
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

需要注意的是取值的时候,如果是对象里面有ref,直接去值就行,不用加.value
但如果是外层为ref,才需要加.value

8.3 修改数据的三种方式

person.vue

<template>
  <div class="person">
    姓名:{{ personStore.name }}
    <br />
    年龄:{{ personStore.count }}
    <br />
    <el-button type="primary" @click="addBtn">按钮</el-button>
  </div>
</template>
<script setup lang="ts">
import { usePersonStore } from "@/store/person";
const personStore = usePersonStore();
const addBtn = () => {
  //第一种修改方式,直接修改
  // personStore.count += 1;

  // 第二种修改方式,多次修改只会触发一次commit
  // personStore.$patch({
  //   name: "李四",
  //   count: 18,
  // });

  // 第三次修改方式,调用actions里的方法
  personStore.increament(1);
};
</script>

<style scoped>
</style>

  • 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

person.ts

import { createPinia, defineStore } from "pinia";
export const usePersonStore = defineStore("person", {
  // actions 里面放置的是一个个方法,用于响应组件的“动作”
  actions: {
    increament(value: number) {
      // 修改数据,this是当前的store
      this.count += value;
    },
  },
  // 存储数据的地方
  state: () => {
    return {
      name: "张三",
      count: 0,
    };
  },
});

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

8.4 store的解构赋值 - storeToRefs

storeToRefs 只会关注store中数据,不会对方法进行ref包裹。

<template>
  <div class="person">
    姓名:{{ name }}
    <br />
    年龄:{{ count }}
    <br />
    <el-button type="primary" @click="addBtn">按钮</el-button>
  </div>
</template>
<script setup lang="ts">
import { usePersonStore } from "@/store/person";
import { toRefs } from "vue";
import { storeToRefs } from "pinia";

const personStore = usePersonStore();
const { name, count } = toRefs(personStore);
console.log("storeToRefs", storeToRefs(personStore));

const addBtn = () => {
  personStore.increament(1);
};
</script>

<style scoped>
</style>

  • 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

其实toRefs也能实现响应式,但性能相对比较差,他会把所有的vue属性都通过ref包裹了

在这里插入图片描述
在这里插入图片描述

8.5 getters的使用

  1. 可以返回一个函数或箭头函数,但箭头函数中不能通过this去取state的值
  2. 箭头函数可以通过state取上面state的值

person.ts

import { createPinia, defineStore } from "pinia";
export const usePersonStore = defineStore("person", {
  // actions 里面放置的是一个个方法,用于响应组件的“动作”
  actions: {
    increament(value: number) {
      // 修改数据,this是当前的store
      this.count += value;
    },
  },
  // 存储数据的地方
  state: () => {
    return {
      name: "zhangsan",
      count: 1,
    };
  },
  getters: {
    upperName(): string {
      return this.name.toUpperCase() + "~~";
    },
    bigCount: (state) => state.count * 20,
  },
});

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

8.6 $subscribe的使用——监听

类似于watch用于监听,共两参数,只要关注的是第二个参数

<template>
  <div class="person">
    姓名:{{ name }},大名:{{ upperName }}
    <br />
    年龄:{{ count }},长大了:{{ bigCount }}
    <br />
    <el-button type="primary" @click="addBtn">按钮</el-button>
  </div>
</template>
<script setup lang="ts">
import { usePersonStore } from "@/store/person";
import { toRefs } from "vue";
import { storeToRefs } from "pinia";
const personStore = usePersonStore();
const { name, count, upperName, bigCount } = toRefs(personStore);
const addBtn = () => {
  personStore.increament(1);
};
//监听count值的变化,共两参数,只要关注的是第二个参数
personStore.$subscribe((mutate, state) => {
  console.log(mutate, state);
  console.log("count", count.value);
});
</script>

<style scoped>
</style>

  • 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

8.7 store组合式写法

上面person.ts都是选项式的写法,下面没问来接下组合式的写法。两种写法都可以
在这里插入图片描述

9.Vue3组件间通信

props、自定义事件、mitt、v-model、 r e f s 、 refs、 refsparent、pinia、slot
在这里插入图片描述

9.1 props

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

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

son.vue

<template>
  <div class="son">
    <h3>子组件</h3>
    <h4>玩具:{{ toy }}</h4>
    <h4>父给的车:{{ car }}</h4>
    <el-button @click="sendToy(toy)">把玩具给父亲</el-button>
  </div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let toy = ref("奥特曼");
defineProps(["car", "sendToy"]);
</script>
    
<style scoped>
.son {
  height: 200px;
  background: cornflowerblue;
}
</style>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

father.vue

<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>汽车:{{ car }}</h4>
    <h4 v-if="toy">孩子给爸爸:{{ toy }}</h4>
    <Son :car="car" :sendToy="getToy" />
  </div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Son from "./son.vue";

let car = ref("奔驰");
let toy = ref("");

const getToy = (val: string) => {
  console.log("孩子给爸爸", val);
  toy.value = val;
};
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 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

在这里插入图片描述

9.2 自定义事件

  • 父组件调用子组件的自定义事件
  • 子组件 通过defineEmits声明自定义事件后,父组件才能调用

father.vue

<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>子给的玩具:{{ toy }}</h4>
    <Son @send-toy="getToy" />
  </div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Son from "./son.vue";
let toy = ref("");
const getToy = (val: string) => {
  toy.value = val;
};
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

son.vue

<template>
  <div class="son">
    <h3>子组件</h3>
    <el-button type="primary" @click="emit('send-toy', toy)">按钮</el-button>
  </div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let toy = ref("奥特曼");
const emit = defineEmits(["send-toy"]);
</script>
    
<style scoped>
.son {
  height: 200px;
  background: cornflowerblue;
}
</style>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

9.3 mitt

  1. 安装

npm i mitt

  1. 四个属性:
  • mitt.on(事件名,回调) 绑定事件
  • mitt.emit(事件名,回调) 触发事件
  • mitt.off(事件名) 解绑事件
  • mitt.all.clear() 全部清除

utils/emitter.ts

//引入mitt
import mitt from "mitt";

// 调用mitt得到emitter,可以绑定事件,触发事件
const emitter = mitt();

//绑定事件
emitter.on("test1", () => {
  console.log("test1被调用");
});
emitter.on("test2", () => {
  console.log("test2被调用");
});

//触发事件
setInterval(() => {
  emitter.emit("test1");
  emitter.emit("test2");
}, 2000);

setTimeout(() => {
  //解除绑定
  emitter.off("test1");
  //清除所有绑定
  emitter.all.clear();
}, 8000);

export default emitter;

  • 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

main.ts

import emitter from "./utils/emitter";
  • 1
  1. 注意:
    组件中使用完以后,在onmounted钩子里面解除绑定

  2. 示例
    son1.vue哥哥组件:

<template>
  <div class="son">
    <h3>子组件1-哥哥</h3>
    <el-button type="primary" @click="emitter.emit('send-toy', toy)"
      >按钮</el-button
    >
  </div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import emitter from "@/utils/emitter";

let toy = ref<string>("奥特曼");
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

son2.vue弟弟组件:

<template>
  <div class="son">
    <h3>子组件2-弟弟</h3>
    <h4>哥哥给的玩具:{{ toy }}</h4>
  </div>
</template>
<script setup lang="ts">
import { ref, onUnmounted } from "vue";
import emitter from "@/utils/emitter";

let toy = ref<any>("");
//给emitter绑定send-toy事件
emitter.on("send-toy", (val: string) => {
  toy.value = val;
});
//在卸载的时候,解绑send-toy事件
onUnmounted(() => {
  emitter.off("send-toy");
});
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

9.4 v-model

  1. v-model用在html标签上
<template>
  <div class="father">
    <h3>父组件</h3>
    <!-- v-model用在html标签上 -->
    <input type="text" v-model="username" />
    <!-- 相当于下面的写法(input原生的属性就是绑定value值,触发input事件) -->
    <input
      type="text"
      :value="username"
      @input="username = (<HTMLInputElement>$event.target).value"
    />
  </div>
</template>
<script setup lang="ts">
import { ref } from "vue";

let username = ref("你好");
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  1. v-model用在组件标签上(vue3的写法)
    father.vue
<template>
  <div class="father">
    <h3>父组件</h3>
    <!-- v-model用在组件标签上 -->
    <ASYGInput v-model="username" />
    <!-- 相当于下面的写法(modelValue和@update:modelValue事vue3约定的标准写法) -->
    <!-- <ASYGInput :modelValue="username" @update:modelValue="username = $event" /> -->
  </div>
</template>
<script setup lang="ts">
import ASYGInput from "./ASYGInput.vue";
import { ref } from "vue";

let username = ref("555");
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

ASYGInput.vue

<template>
  <input
    type="text"
    :value="modelValue"
    @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)"
  />
</template>
  <script setup lang="ts">
defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
</script>
      
<style scoped>
input {
  background-image: radial-gradient(red, green, blue, yellow);
  color: #fff;
}
</style>
      
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

$event到底是啥?啥时候能.target

  • 对于原生事件,$event就是事件对象====>能.target
  • 对于自定义事件,$event就是触发事件时,所传递的数据====>不能.target
  1. 修改自定义属性modelValue
    father.vue
<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>{{ username }}</h4>
    <!-- v-model用在组件标签上 -->
    <!-- <ASYGInput v-model="username" /> -->

    <!-- 修改自定义属性modelValue -->
    <ASYGInput v-model:mingzi="username" />
  </div>
</template>
<script setup lang="ts">
import ASYGInput from "./ASYGInput.vue";
import { ref } from "vue";

let username = ref("22");
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 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

ASYGInput.vue

<template>
  <input
    type="text"
    :value="mingzi"
    @input="emit('update:mingzi', (<HTMLInputElement>$event.target).value)"
  />
</template>
  <script setup lang="ts">
defineProps(["mingzi"]);
const emit = defineEmits(["update:mingzi"]);
</script>
      
<style scoped>
input {
  background-image: radial-gradient(red, green, blue, yellow);
  color: #fff;
}
</style>
      
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

9.5 $attrs

  1. 概述:$attrs用于实现当前组件的父组件,向当前组件的子组件通信(祖一>孙)。
  2. 具体说明: $attrs 是一个对象,包含所有父组件传入的标签属性。

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

  1. 示例
  • 通过v-bind传递对象相当于一个一个传值过去
  • 子级可以通过defineProps接收父级传过来的数据,但没有接收的都在都在$attrs上,可以直接取值使用

father.vue

<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>a:{{ a }}</h4>
    <h4>b:{{ b }}</h4>
    <h4>c:{{ c }}</h4>
    <h4>d:{{ d }}</h4>

    <Child :a="a" v-bind="{ b: b, c: c, d: d }" />
    <!-- 相当于下面的写法 -->
    <!-- <Child :a="a" :b="b" :c="c" :d="d" /> -->
  </div>
</template>
<script setup lang="ts">
import Child from "./child.vue";
import { ref } from "vue";

let a = ref("a");
let b = ref("b");
let c = ref("c");
let d = ref("d");
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 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

child.vue

<template>
  <div class="son">
    <h3>子组件</h3>
    <h4>a:{{ a }}</h4>
    <!-- 父级给传了,但子级没有通过defineProps接收的,都在$attrs -->
    <h4>其他:{{ $attrs }}</h4>
    <GrandChild />
  </div>
</template>
<script setup lang="ts">
import GrandChild from "./grandChild.vue";
import { ref } from "vue";
defineProps(["a"]);
</script>
    
<style scoped>
.son {
  height: 200px;
  background: cornflowerblue;
}
</style>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

在这里插入图片描述
4. 示例:
父->子->孙,都可以通过$attrs传递变量或者方法

father.vue

<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>a:{{ a }}</h4>
    <h4>b:{{ b }}</h4>
    <h4>c:{{ c }}</h4>
    <h4>d:{{ d }}</h4>

    <!-- 传属性值和方法 -->
    <Child v-bind="{ a: a, b: b, c: c, d: d }" :updateA="updateA" />
  </div>
</template>
<script setup lang="ts">
import Child from "./child.vue";
import { ref } from "vue";

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

const updateA = (val: number) => {
  a.value += val;
};
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 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

child.vue

<template>
  <div class="son">
    <h3>子组件</h3>
    <h4>其他:{{ $attrs }}</h4>
    <GrandChild v-bind="$attrs" />
  </div>
</template>
<script setup lang="ts">
import GrandChild from "./grandChild.vue";
</script>
    
<style scoped>
.son {
  height: 200px;
  background: cornflowerblue;
}
</style>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

grandChild.vue

<template>
  <div class="grandChild">
    <h3>孙子组件</h3>
    <h4>其他:{{ $attrs }}</h4>
    <el-button @click="updateA(10)">修改A</el-button>
  </div>
</template>
<script setup lang="ts">
defineProps(["updateA"]);
</script>
    
<style scoped>
.grandChild {
  height: 200px;
  background: pink;
}
</style>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在这里插入图片描述

9.6 $refs$parent

1.概述:

  • $refs用于:父->子
  • $parent 用于:子->父

都需要通过defineExpose暴露值才能使用

2.原理如下:

属性说明
$refs值为对象,包含所有被 ref 属性标识的 DOM 元素或组件实例。
$parent值为对象,当前组件的父组件实例对象

3.示例

通过ref修改子级的数据,通过parent修改父级的数据。但都需要defineExpose的帮助
parent.vue

<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>房产:{{ house }}</h4>
    <el-button type="primary" @click="changeToy">修改child的玩具</el-button>
    <Son1 ref="son1" />
    <Son2 ref="son2" />
  </div>
</template>
<script setup lang="ts">
import Son1 from "./son1.vue";
import Son2 from "./son2.vue";
import { ref } from "vue";
let house = ref(4);
const son1 = ref();

const changeToy = () => {
  son1.value.toy = "小猪佩奇";
};

defineExpose({ house });
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 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

son1.vue
小注意点:响应式对象,他里面的ref不需要.value,底层会自动读取响应式数据

<template>
  <div class="son">
    <h3>子组件1</h3>
    <h4>玩具:{{ toy }}</h4>
    <h4>书籍:{{ book }}</h4>
    <el-button type="primary" @click="minusHouse($parent)"
      >干掉父亲一套房产</el-button
    >
  </div>
</template>
<script setup lang="ts">
import { ref } from "vue";

let toy = ref("奥特曼");
let book = ref(3);

const minusHouse = (parent: any) => {
//parent是个响应式对象,他里面的ref不需要.value
  parent.house -= 1;
};

defineExpose({ toy, book });
</script>
    
<style scoped>
.son {
  height: 200px;
  background: cornflowerblue;
}
</style>
    
  • 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
  • 如何修改多个子级的数据?
    通过$refs可以获取所有儿子

father.vue

<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>房产:{{ house }}</h4>
    <el-button type="primary" @click="changeBook($refs)"
      >修改所有子级的书数量</el-button
    >
    <Son1 ref="son1" />
    <Son2 ref="son2" />
  </div>
</template>
<script setup lang="ts">
import Son1 from "./son1.vue";
import Son2 from "./son2.vue";
import { ref } from "vue";
let house = ref(4);
const son1 = ref();

const changeBook = (refs: any) => {
  for (let key in refs) {
    refs[key].book += 3;
  }
};

defineExpose({ house });
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 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

son1.vue

<template>
  <div class="son">
    <h3>子组件1</h3>
    <h4>书籍:{{ book }}</h4>
  </div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let book = ref(3);

defineExpose({ book });
</script>
    
<style scoped>
.son {
  height: 200px;
  background: cornflowerblue;
}
</style>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

son2.vue

<template>
  <div class="son">
    <h3>子组件2</h3>
    <h4>书籍:{{ book }}</h4>
  </div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let book = ref(6);

defineExpose({ book });
</script>
    
<style scoped>
.son {
  height: 200px;
  background: skyblue;
}
</style>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

9.7 provide和inject

祖孙之间传值,前面也说到了一个祖孙之间传值的$attrs,但是会影响中间人。而这个provide和inject会对中间人0影响。
在这里插入图片描述

示例1:祖传子

注意:

  • 祖组件传递的ref数据 provide的时候不能.value,否则就不会响应式,传递的只是单纯的数据
  • ts的报红可以通过默认值解决

father.vue

<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>银子:{{ money }}万元</h4>
    <h4>车子:一辆{{ car.brand }}车,价值{{ car.price }}万元</h4>
    <Child />
  </div>
</template>
<script setup lang="ts">
import Child from "./child.vue";
import { ref, reactive, provide } from "vue";

let money = ref(100);
let car = reactive({
  brand: "奔驰",
  price: 100,
});
//向后代提供数据
//注意:这里不能.value,不然就不会响应式,传递的只是单纯的数据
provide("money", money);
provide("car", car);
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 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

grandChild.vue

<template>
  <div class="grandChild">
    <h3>孙子组件</h3>
    <h4>银子:{{ money }}万元</h4>
    <h4>车子:一辆{{ car.brand }}车,价值{{ car.price }}万元</h4>
  </div>
</template>
<script setup lang="ts">
import { inject } from "vue";

//第二个参数的默认值,解决ts的报红问题
let money = inject("money", "我是默认值");
let car = inject("car", { brand: "未知", price: 0 });
</script>
    
<style scoped>
.grandChild {
  height: 200px;
  background: pink;
}
</style>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

示例2:子修改祖

子触发 祖传递的方法(修改祖自己)
father.vue

<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>银子:{{ money }}万元</h4>
    <Child />
  </div>
</template>
<script setup lang="ts">
import Child from "./child.vue";
import { ref, reactive, provide } from "vue";

let money = ref(100);
const updateMoney = (val: number) => {
  money.value -= val;
};

//向后代提供数据
provide("moneyContext", { money, updateMoney });
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 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

grandChild.vue

<template>
  <div class="grandChild">
    <h3>孙子组件</h3>
    <h4>银子:{{ money }}万元</h4>
    <el-button @click="updateMoney(2)" type="parmary">花爷爷的钱</el-button>
  </div>
</template>
<script setup lang="ts">
import { inject } from "vue";

//第二个参数的默认值,解决ts的报红问题
let { money, updateMoney } = inject("moneyContext", {
  money: 0,
  updateMoney: (params: number) => {},
});
</script>
    
<style scoped>
.grandChild {
  height: 200px;
  background: pink;
}
</style>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

9.9 slot

9.9.1 默认插槽和具名插槽

  • 插槽需要写在template或者组件上
  • 具名插槽v-slot:插槽名,还有语法糖,直接#插槽名也可以

father.vue

<template>
  <div class="father">
    <h4>父组件</h4>

    <Child>
      <!-- 默认插槽 -->
      啦啦啦

      <!-- 具名插槽 -->
      <template v-slot:name>
        <h4>我是小花</h4>
      </template>
      <!-- 具名插槽另一种写法,语法糖 -->
      <template #age>
        <h4>今年30</h4>
      </template>
    </Child>
  </div>
</template>
<script setup lang="ts">
import Child from "./child.vue";
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 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

children.vue

<template>
  <div class="son">
    <h4>子组件</h4>
    <!-- 默认插槽 -->
    <slot></slot>

    <!-- 具名插槽 -->
    <slot name="name"></slot>
    <slot name="age"></slot>
  </div>
</template>
<script setup lang="ts">
</script>
    
<style scoped>
.son {
  height: 200px;
  background: cornflowerblue;
}
</style>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

9.9.2 作用域插槽

值在自定义组件那儿,传给使用的组件,但样式的展示由需要使用的组件决定
children.vue

<template>
  <div class="son">
    <h4>子组件</h4>

    <slot name="person" hello="你好" happy="啦啦啦"></slot>
  </div>
</template>
<script setup lang="ts">
</script>
    
<style scoped>
.son {
  height: 200px;
  background: cornflowerblue;
}
</style>
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

father.vue

<template>
  <div class="father">
    <h4>父组件</h4>

    <Child>
      <!-- 具名插槽 -->
      <template v-slot:person="params">
        <h4 style="background: pink">{{ params.happy }}</h4>
        <h4 style="background: blue">{{ params.hello }}</h4>
      </template>
    </Child>
  </div>
</template>
<script setup lang="ts">
import Child from "./child.vue";
</script>
  
<style scoped>
.father {
  height: 200px;
  background: cadetblue;
}
</style>
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

10.其他API

10.1 shallowRefshallowReactive

1)shallowRef

  1. 作用:创建一个响应式数据,但只对顶层属性进行响应式处理
  2. 用法:
    let myVar = shallowRef(initialvalue):
  3. 特点:只跟踪引用值的变化,不关心值内部的属性变化

2)shallowReactive

  1. 作用:创建一个浅层响应式对象,只会使对象的最顶层属性变成应式的,对象内部的嵌套属性则不会变成响应式的
  2. 用法:
    const my0b = shallowReactive({ ... });
  3. 特点:对象的顶层属性是响应式的,但嵌套对象的属性不是。
  • 总结

通过使用 shallowRef()shallowReactive()来绕开深度响应。浅展式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问交得更快,可提升性能。如对象嵌套对象,则内部的对象就无法监听该属性的响应式,也就是说修改后不变化。

10.2 readonlyshallowReadOnly

1)readonly

  1. 作用:用于创建一个对象的深只读副本
  2. 用法:
const original=reactive({...});
const readOnlyCopy=readonly(original);
  • 1
  • 2
  1. 特点:
  • 对象的所有嵌套属性都将变为只读。
  • 嵌套的内容必须是个响应式对象,这样ref的值就不需要.value。嵌套的属性可以是ref也可以是reactive
  • 任何尝试修改这个对象的操作都会被阻止(在开发模式下,还会在控制台中发出警告)。
  1. 应用场景:
  • 创建不可变的状态快照给别人,自己可以改数据,别人不能改数据
  • 保护全局状态或配置不被修改。
<template>
  <h2>当前sum1为:{{ sum1 }}</h2>
  <h2>当前sum2为:{{ sum2 }}</h2>
  <el-button @click="changeSum1">修改sum1</el-button>
  <el-button @click="changeSum2">修改sum2</el-button>
</template>
<script setup lang="ts">
import { ref, reactive, readonly } from "vue";

let sum1 = ref(0);
//注意这里不是sum1.value,readonly里面必须传值是个响应式对象
let sum2 = readonly(sum1);

//修改sum1的时候,sum2也会响应式变化
const changeSum1 = () => {
  sum1.value += 1;
};

const changeSum2 = () => {
  sum2.value += 1; //这一行代码会直接爆红,不允许修改(无法为“value”赋值,因为它是只读属性)
};
</script>

<style scoped>
</style>

  • 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

2)shallowReadonly

  1. 作用:与 readonly 类似,但只作用于对象的顶展属性,
  2. 用法:
const original = reactive((...));
const shalloaReadOnlyCopy =  shallowReadonly(original):
  • 1
  • 2
  1. 特点:
  • 只将对象的顶展属性设置为只读,对象内部的嵌套属性仍然是可变的
  • 透用于只需保护对象顶展属性的场景,

10.3 toRawmarkRaw

1)toRaw

  1. 作用:用于获取一个响应式对象的原始对象, toRaw 返回的对象不再是响应式的,不会触发视图更新。

官网描述:这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。
何时使用?–在需要将响应式对象传递给非 vue 的库或外部系统时,使用 toRaw 可以确保它们收到的是普通对象

  1. 示例
<template>
  <h2>姓名:{{ person.name }}</h2>
  <h2>年龄:{{ person.age }}</h2>

  <el-button @click="person.age += 1">修改年龄(响应式数据)</el-button>
  <el-button @click="person2.age += 1">修改年龄(原始数据)</el-button>
</template>
<script setup lang="ts">
import { ref, reactive, readonly, toRaw } from "vue";

let person = reactive({
  name: "tony",
  age: 19,
});
let person2 = toRaw(person);
console.log("响应式数据", person);
console.log("原始数据", person2);
</script>

<style scoped>
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

在这里插入图片描述

2)markRaw

  1. 作用:标记一个对象,使其永远不会变成响应式的。
    例如使用 mockjs 时,为了防止误把 mockjs 变为响应式对象,可以使用 markRaw 去标记 mockis

  2. 示例

<template></template>
<script setup lang="ts">
import { ref, reactive, readonly, toRaw, markRaw } from "vue";

let person = {
  name: "tony",
  age: 19,
};
let person2 = markRaw(person);
console.log("person", person);
console.log("person2", person2);
</script>

<style scoped>
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

打印都是普通对象
在这里插入图片描述

10.4 自定义 ref

场景:input输入框输入,2秒以后才响应式到其他地方
原本的ref是会实时响应式,所以我们需要自定义一个ref

useMsgRefs.ts

import { customRef } from "vue";

export default function (initValue: string, delay: number) {
  // 使用vue提供的customRef定义响应式数据
  let timer: number;
  // track(跟踪) 、triggerAsyncId(触发)
  let msg = customRef((track, trigger) => {
    return {
      // get何时调用?——msg被读取时
      get() {
        track(); //告诉Vue数据msg很重要,你要对msg进行跟踪
        return initValue;
      },
      // set何时调用?- msg被修改时
      set(value) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          initValue = value;
          trigger(); //通知Vue一下数据msg变化了
        }, delay);
      },
    };
  });
  return { msg };
}

  • 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

调用

<template>
  <h1>{{ msg }}</h1>
  <input type="text" v-model="msg" />
</template>
<script setup lang="ts">
import { ref, reactive, readonly, toRaw, markRaw } from "vue";
import useMsgRefs from "./components/useMsgRefs";
let { msg } = useMsgRefs("你好", 2000);
</script>

<style scoped>
</style>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

10.5 Teleport

什么是Teleport?-- Teleport 是一种能够将我们的组件html结构移动到指定位置的技术(传送门)

如下示例:原本这个模态框在元素里面,但现在通过to属性,给他写到body里面了。
to属性里面可以写类名.class#app,body都可以
在这里插入图片描述

10.6 Suspense

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验——>子组件里面有异步任务,需求希望网速慢的时候,子组件有东西
  • 但这是一个试验性api,后期可能会改变
  • 使用步骤:
    • 异步引入组件
    • 使用 Suspense包裹组件,并配置好defaultfallback

如下:
如果异步请求,像下面这样使用await,setup顶层直接有async,不需要加async。但引用子组件的时候,需要Suspense包裹,而且可以写入加载中的插槽
在这里插入图片描述

10.7 全局API转移到应用对象

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

在这里插入图片描述

10.8 其他

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

建议去看下vue官网的飞兼容性改变,了解的更全面,这里都是vue2可以用,但vue3不能这样写的语法

在这里插入图片描述

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

闽ICP备14008679号