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. 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>


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>

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>

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>

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>

情况二

监视 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>

情况三

监视 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>

情况四

监视 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>

情况五

监听上述多个数据

<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>

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>

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>


子组件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>

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

1. 定义.ts的文件

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

//一个自定义类型(一类人,数组)
//第一种写法
export type Persons = Array<PersonInter>
//第二种写法
// export type Persons = PersonInter[]

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>

4.props的使用

注意:

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

.ts文件

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

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

父组件:

<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>

子组件:

<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>

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>

hooks页面:

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

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

  return {sum, add}
}

7.路由

7.1 路由模式

1)history模式

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

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

2)hash模式

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

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

7.2 路由传参

7.2.1 query

1)传参

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

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

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

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

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

1.2.2 params

1)传参

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

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

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

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

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

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

2)参数接收
// 接收
import { useRoute } from "vue-router"
const route = useRoute()

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

备注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>

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

7.3.1 第一种写法

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

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

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

7.3.2 第二种写法:函数写法

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

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

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

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

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

7.3.3 第三种写法

很少使用,就是写死的

props:{
	a:100
	b:200
	c:380
}

8.pina

来个对象解构赋值

let obj = {
  country: {
    province: {
      city: {
        qu: "瑶海区",
      },
    },
  },
};
//下面写法是连续解构+重命名
let {
  country: {
    province: {
      city: { qu: qulala },
    },
  },
} = obj;
console.log(qulala);

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')

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

8.2 存储+读取数据

定义

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

使用:

<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>

需要注意的是取值的时候,如果是对象里面有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>

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,
    };
  },
});

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>

其实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,
  },
});

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>

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>
    

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>
  

在这里插入图片描述

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>
  

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>
    

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;

main.ts

import emitter from "./utils/emitter";
  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>

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>

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. 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>
  

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>
      

$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>
  

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>
      

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>
  

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>
    

在这里插入图片描述
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>
  

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>
    

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>
    

在这里插入图片描述

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>
  

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>
    
  • 如何修改多个子级的数据?
    通过$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>
  

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>
    

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>
    

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>
  

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>
    

示例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>
  

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>
    

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>
  

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>
    

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>
    

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>
  

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. 特点:
  • 对象的所有嵌套属性都将变为只读。
  • 嵌套的内容必须是个响应式对象,这样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>

2)shallowReadonly

  1. 作用:与 readonly 类似,但只作用于对象的顶展属性,
  2. 用法:
const original = reactive((...));
const shalloaReadOnlyCopy =  shallowReadonly(original):
  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>

在这里插入图片描述

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>

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

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 };
}

调用

<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>

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不能这样写的语法

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/616093.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Golang | Leetcode Golang题解之第78题子集

题目&#xff1a; 题解&#xff1a; func subsets(nums []int) (ans [][]int) {set : []int{}var dfs func(int)dfs func(cur int) {if cur len(nums) {ans append(ans, append([]int(nil), set...))return}set append(set, nums[cur])dfs(cur 1)set set[:len(set)-1]df…

Reactor Netty 其他-响应式编程-018

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace The Nex…

triton编译学习

一 流程 Triton-MLIR: 从DSL到PTX - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/671434808Superjomns blog | OpenAI/Triton MLIR 迁移工作简介https://superjom

ctfshow SSRF 351-358

做题前,需要先学习关于ssrf漏洞的相关知识 小注意: 当使用 file_get_contents() 函数访问远程 URL 时&#xff0c;它会尝试获取该 URL 指向的资源的内容&#xff0c;并将内容以字符串的形式返回。 如果 b.php 文件是一个 PHP 文件&#xff0c;它包含的内容取决于该 PHP 文件…

【QT学习】补充:qt使用已经存在的类

1.右键项目--》添加现有文件 注意&#xff1a;不是添加新文件&#xff01;&#xff01;&#xff01; 2.添加配置

【Unity Shader入门精要 第6章】基础光照(二)

1. 获取环境光 unity shader中可以通过 UNITY_LIGHTMODEL_AMBIENT获取当前环境光颜色信息。 fixed4 frag(v2f i) : SV_Target {return UNITY_LIGHTMODEL_AMBIENT; }2. 漫反射 2.1 兰伯特模型 创建Chapter_6_Diffuse_Lambert作为测试材质创建Chapter_6_Diffuse_Lambert作为测…

i春秋-GetFlag

题目 考点 sql注入&#xff0c;md5加密&#xff0c;代码审计&#xff0c;利用eval函数 解题 参考wp https://www.cnblogs.com/qiaowukong/p/13630130.html找md5值 看见验证码中的提示&#xff0c;就是去找一个md5值前六位是指定值的数&#xff08;严格来说不一定是数&…

408算法题专项-2019年

题目&#xff1a; 分析&#xff1a;要求空间复杂度为O&#xff08;1&#xff09;&#xff0c;我们可以逆向假设可以开空间&#xff0c;得出一种思路&#xff0c;然后对这种思路优化空间即可得到O&#xff08;1&#xff09; 思路一&#xff1a;假设开空间 思考&#xff1a;再开…

【C语言题解】用函数来模拟实现strlen()、strcpy()、strcmp()、strcat()

&#x1f970;欢迎关注 轻松拿捏C语言系列&#xff0c;来和 小哇 一起进步&#xff01;✊ 学习了函数后&#xff0c;老师让我们用函数来实现上面这四个字符串函数。 我们首先来了解一下这四个字符串函数&#xff1a; 1.strlen函数 用于获取字符串长度&#xff08;不包括末尾…

读天才与算法:人脑与AI的数学思维笔记25_涌现理论

1. 人工智能新闻 1.1. 人工智能新闻报道算法的核心是如何将未经处理的原始数据转换成新闻报道 1.2. 很少有记者为美联社决定使用机器来帮助报道这些新闻持反对意见 1.2.1. 像“Wordsmith”这样的算法&#xff0c;具有自动化的洞察力、科学的叙事能力&#xff0c;现在正被应用…

2024年建筑施工特种作业人员安全生产知识试题

100分题库提供安全员考试试题、建筑安全员考试预测题、建筑安全员ABC考试真题、安全员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 单选题&#xff08;1-10&#xff09; 1.因生产安全事故受损害的从业人员&#xff0c;除…

打开远程连接的命令是什么?

远程连接是一种能够在不同设备之间建立连接并共享信息的技术。在许多情况下&#xff0c;我们需要通过远程连接来访问其他设备或处理一些远程任务。本文将介绍一些常用的打开远程连接的命令。 使用SSH连接远程设备 SSH&#xff08;Secure Shell&#xff09;是一种安全的网络协议…

大数据Scala教程从入门到精通第六篇:Scala编译结果反编译分析

一&#xff1a;Scala编译结果反编译分析 问题&#xff1a;为什么Scalac之后的生成的class文件有两个&#xff0c;一个带$的&#xff0c;一个不带$的&#xff1f; 不能直接java 执行scala编译的字节码文件。 直接运行的话就会报错&#xff0c;会报一个类没有被找到。 引入类库就…

免费思维13招之六:功能型思维

免费思维13招之六&#xff1a;功能型思维 这节来学习一下免费思维的另一大思维——功能型思维。 这个思维通俗易懂。功能型思维是指将其他产品的功能在我们的产品上进行体现&#xff0c;让客户获得免费的使用。 也就是说&#xff0c;客户买了你的产品&#xff0c;却可以免费得…

Android Studio在android Emulator中运行的项目黑屏

前言&#xff1a; 最近在做一个Android相关的小项目&#xff0c;因为之前这方面的项目做的比较的少。今天在使用虚拟机调试的时候经常出现一些莫名其妙的问题&#xff0c;经过自己多次的尝试和搜索终于解决了这些问题。 问题&#xff1a; 每次run&#xff08;运行&#xff09…

报表-集成

1、部署报表服务器 以centos为例 1.1 将服务拷贝到服务器 其中JDK-17是对应平台的jdk 1.2 修改lite-report下的source.config 1.3 把设计好的报表文件拷贝到lite-report/report 1.4 启动服务&#xff1a;sh run.sh restart 2、使用Nginx location /litereport/ { …

【全面介绍下Spring】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

Golang | Leetcode Golang题解之第77题组合

题目&#xff1a; 题解&#xff1a; func combine(n int, k int) (ans [][]int) {// 初始化// 将 temp 中 [0, k - 1] 每个位置 i 设置为 i 1&#xff0c;即 [0, k - 1] 存 [1, k]// 末尾加一位 n 1 作为哨兵temp : []int{}for i : 1; i < k; i {temp append(temp, i)}t…

转载:ubuntu18.04 安装wine以及添加mono和gecko打开简单.net应用的方法

https://www.cnblogs.com/jinanxiaolaohu/p/12191576.html 1. 今天突然想试试能不能用ubuntu跑一下公司的.net的智能客户端(SmartClient). 想到的办法就是 安装wine 但是过程略坑..这里简单说一下总结之后的过程. 2. 第一步安装wine相关内容 查了下有winehq和wine两种. …

[数据集][目标检测]管道焊缝质量检测数据集VOC+YOLO格式1134张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1134 标注数量(xml文件个数)&#xff1a;1134 标注数量(txt文件个数)&#xff1a;1134 标注…