赞
踩
纯vue3的语法
1.在指定目录下运行
npm create vue@latest
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
等的配置文件
安装地址:https://chrome.zzzmh.cn/index
把解压包里面的crx 文件直接拖拽到浏览器中的扩展工具
如果没有显示 添加扩展工具的话 请把左侧的开发者模式打开
安装以后,ref的value自动补充完整
Vue2 是选项式API(OptionsAPI,选项式风格),data、methods、name都是选项
vue3 组合式API(CompositionAPI,配置式风格)
Options
类型的API数据、方法、计算属性等,是分散在:data、methods、computed中的,若想新增或者修改一个需求,就需要分别修改:data、methods、computed,不便于维护和复用。
可以用函数式的方式,更加优雅的组织代码,让相关功能的代码更加有序的组织在一起。
setup
中的this是undefiend
,Vue3
中已经弱化this
了setup
中打印数据比beforeCreate
和data还靠前。data
里面通过this.
可以获取到steup
中定义的数据;但steup
里面无法获取data
里面的数据setup
的返回值也可以是个渲染函数<script lang="ts" setup>
import {ref} from "vue"
// 只需要引入需要的组件,不需要注册组件
import Test from "./components/Test.vue"
// 定义变量不需要return出去
const count = ref(0)
const Add = () =>{
count.value++
}
</script>
vite-plugin-vue-setup-extend
插件npm install vite-plugin-vue-setup-extend -D 然后
在vite.config.js`添加如下代码:<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>
<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>
<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>
<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>
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}
开启深度监视。
注意:
<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 定义的【对象类型】数据中的某个属性,注意点如下:
总结:修改对象下的某个属性,都写成函数
<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>
官网:立即远行一个函数,同时响应式地追踪其依稳,并在依较更改时重新执行该的数
watch 对比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>
作用:用于注册模板引用。
父组件:
<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>
.ts
的文件//定义一个接口,用于限制person对象的具体属性
export interface PersonInter {
id: string,
name: string,
age: number
}
//一个自定义类型(一类人,数组)
//第一种写法
export type Persons = Array<PersonInter>
//第二种写法
// export type Persons = PersonInter[]
属性名不对,或者类型不对,就可以校验提示
<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>
注意:
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>
生命周期分为四个阶段:创建,挂载,更新,销毁 每个阶段两个钩子,一前一后。
vue2的生命周期:
创建阶段:
beforeCreate
、created
挂载阶段:beforemount
、mounted
更新阶段:beforeUpdate
、updated
销毁阶段:beforeDestroy
、destroyed
vue3的生命周期:
创建阶段:setup
挂载阶段:onBeforemount
、onMounted
更新阶段:onBeforeUpdate
、onUpdated
销毁阶段:onBeforeUnmount
、onUnmounted
常用的钩子:onMounted
(挂载完毕)、onUpdated
(更新完毕)、onBeforeUnmount
(卸载之前)
用于提取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}
}
优点:URL更加美观,不带有#,更接近传统的网站 URL缺点:后期项目上线,需要服务端配合处理路径问题,否则刷新会有 404 错误。
const router = VueRouter.createRouter({
history: VueRouter.createWebHistory(),
routes, // `routes: routes` 的缩写
})
优点:兼容性更好,因为不需要服务器端处理路径,缺点: URL 带有#不太美观,且在 SE0 优化方面相对较差。
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes, // `routes: routes` 的缩写
})
第一种方式:
在路由路径后直接拼接?参数名:参数值
,多组参数间使用&分隔
<RouterLink to="/path/path1?name=小红&age=23"></RouterLink>
第二种方式:
to不再传递字符,而是传一个对象,由于参数为对象,所以to前需要加上
<RouterLink :to="{
path: "/path/path1",
query: {
name: "小红",
age: 23
}
}"/>
// 接收
import { useRoute } from "vue-router"
const route = useRoute()
// 使用
<div>{{ route.query.name }}</div>
<div>{{ route.query.age }}</div>
第一种写法:
在路由路径后直接拼接/参数值
<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
}
}"/>
// 接收
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
如何简化参数使用
方法一: 解构 配合 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配置的三种方法
将路由收到的所有params参数作为props传给路由组件(只用于params传参)
// 路由规则配置
{ path:'/path/path1/:name/:age', component: Comp2, props: true }
defineProps(['name','age'])
<div>{{ name }}</div>
<div>{{ age }}</div>
params和query传参都可以使用,一般用于处理query参数,需要写成函数形式
// 路由规则配置
{
path:'/path/path1/:name/:age',
component: Comp2,
props(route){
return route.query
}
}
使用参数时,defineProps([‘name’,‘age’])
defineProps(['name','age'])
<div>{{ name }}</div>
<div>{{ age }}</div>
很少使用,就是写死的
props:{
a:100
b:200
c:380
}
来个对象解构赋值
let obj = { country: { province: { city: { qu: "瑶海区", }, }, }, }; //下面写法是连续解构+重命名 let { country: { province: { city: { qu: qulala }, }, }, } = obj; console.log(qulala);
npm install pinia
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模块
定义
// 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
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, }; }, });
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包裹了
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, }, });
类似于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>
上面person.ts
都是选项式的写法,下面没问来接下组合式的写法。两种写法都可以
props、自定义事件、mitt、v-model、 r e f s 、 refs、 refs、parent、pinia、slot
概述: 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>
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>
npm i mitt
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";
注意:
组件中使用完以后,在onmounted钩子里面解除绑定
示例
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>
<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>
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
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>
$attrs
$attrs
用于实现当前组件的父组件,向当前组件的子组件通信(祖一>孙)。$attrs
是一个对象,包含所有父组件传入的标签属性。注意:
$attrs
会自动排除props
中声明的属性(可以认为声明过的props
被子组件自己“消费”了)
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>
$refs
、$parent
$refs
用于:父->子$parent
用于:子->父都需要通过defineExpose
暴露值才能使用
属性 | 说明 |
---|---|
$refs | 值为对象,包含所有被 ref 属性标识的 DOM 元素或组件实例。 |
$parent | 值为对象,当前组件的父组件实例对象 |
通过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>
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>
祖孙之间传值,前面也说到了一个祖孙之间传值的$attrs
,但是会影响中间人。而这个provide和inject
会对中间人0影响。
注意:
ref
数据 provide的时候不能.value,否则就不会响应式,传递的只是单纯的数据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>
子触发 祖传递的方法(修改祖自己)
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>
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>
值在自定义组件那儿,传给使用的组件,但样式的展示由需要使用的组件决定
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>
shallowRef
与shallowReactive
顶层
属性进行响应式处理let myVar = shallowRef(initialvalue):
const my0b = shallowReactive({ ... });
通过使用
shallowRef()
和shallowReactive()
来绕开深度响应。浅展式 API 创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,这使得属性的访问交得更快,可提升性能。如对象嵌套对象,则内部的对象就无法监听该属性的响应式,也就是说修改后不变化。
readonly
与shallowReadOnly
深只读副本
const original=reactive({...});
const readOnlyCopy=readonly(original);
.value
。嵌套的属性可以是ref
也可以是reactive
<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>
readonly
类似,但只作用于对象的顶展属性,const original = reactive((...));
const shalloaReadOnlyCopy = shallowReadonly(original):
toRaw
与markRaw
toRaw
返回的对象不再是响应式的,不会触发视图更新。官网描述:这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。
何时使用?–在需要将响应式对象传递给非vue
的库或外部系统时,使用toRaw
可以确保它们收到的是普通对象
<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>
作用:标记一个对象,使其永远不会
变成响应式的。
例如使用 mockjs
时,为了防止误把 mockjs
变为响应式对象,可以使用 markRaw
去标记 mockis
示例
<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>
打印都是普通对象
场景: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>
什么是Teleport?-- Teleport 是一种能够将我们的组件html结构移动到指定位置的技术(传送门)
如下示例:原本这个模态框在元素里面,但现在通过to
属性,给他写到body里面了。
to
属性里面可以写类名.class
,#app
,body
都可以
Suspense
包裹组件,并配置好default
与 fallback
如下:
如果异步请求,像下面这样使用await,setup顶层直接有async,不需要加async。但引用子组件的时候,需要Suspense
包裹,而且可以写入加载中的插槽
app.component
app.config
app.directive
app.mount
app.unmount
app.use
v-enter
修改为 v-enter-from
、过渡类名 v-leave
修改为 v-leave-from
,keyCode
作为 v-on
修饰符的支持。v-model
指令在组件上的使用已经被重新设计,替换掉了v-bind.sync
。v-if
和 v-for
在同一个元素身上使用时的优先级发生了变化。son
、 $off
和 $once
实例方法。 filter
。$children
实例 propert
。建议去看下vue官网的飞兼容性改变,了解的更全面,这里都是vue2可以用,但vue3不能这样写的语法
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。