一、Vue3 和 Vue2 的区别:
- (1)vue2 的
<template>
标签内只允许存在一个根节点,vue3 允许出现多个根节点 - (2)vue3 组件引入只需要 import,不需要再像 vue2 上进行 component 注册
- (3)vue3 的结构是
<script><template><style>
,vue2 的结构是<template><script><style>
,但 vue3 能够兼容 vue2 的结构 - (4)vue3 的数据和方法定义都在 setup(){}里进行
二、ref 和 reactive
定义变量的两种方式:ref 和 reactive,直接将赋值包括在 ref/reactive 的函数括号内:
- ref:适用于定义普通类型的数据,number、string
- reactive:适用于定义引用类型的数据,object、Array
let num = ref(“123”); let obj=reactive({price:”123”});
console.log(num.value);
console.log(obj);
ref 也可以包括引用数据类型的数据,想使用 ref 所包括的数据,需要指向 ref 包括的变量的 value 属性:
let num = ref({ price: "123" });
console.log(num.value.price);
let nums = ref({ price: "123" }, { price: "456" });
console.log(num.value[0].price);
实际原理其实是 ref 底层去调用了 reactive 实现的
被 ref 修饰的变量只要修改其 value 指向的值,结果都是响应式的。
被 reactive 修饰的变量被重新赋值之后不再具有响应式,需要使用Object.assign(a,b)
进行响应式赋值
被 reactive 包裹的对象,内部的属性用ref修饰后会被reactive解构,无需再使用.value
let obj=reactive({price:”123”});
obj={name:"abc"};//不具有响应式
Object.assign(obj,{name:"abc"});//表示将新对象分配给obj时,才具有响应式
响应式对象数据进行解构之后,解构之后的修改数据不再具有响应式,且不会修改到原先对象的属性值。
let obj=reactive({price:”123”,name:"abc"});
let {price,name}=obj;
//修改price和name的值时,页面显示不具有响应式,且obj.price,obj.name不会发生任何改变
let {price,name}=toRefs(obj);//将多个属性解构为ref包括的响应式数据
let n=toRef(obj,'name');//将单个属性解构为ref包括的响应式数据
//修改price和name的值时,页面显示具有响应式,且obj.price,obj.name会同步发生改变
Q:vue2 中的双向数据绑定和 vue3 的 ref 有什么区别?
A:vue2 中的双向数据绑定是利用了 Object.defineProperties(数据劫持),允许通过此属性劫取各个属性的 get 和 set 来实现的,修改的都是同一个目标。ref 借助的是 new proxy()(数据代理),通过 ref 传的值,用 msg 变量进行代理,msg 作为数据的克隆体
msg===‘我是内容’(×) msg.value===‘我是内容’(√)
三、父组件传值:
//父组件
//<Person a="哈哈哈" :c="personList"/>
//a="哈哈哈"是字符串, :c="personList"是表达式
let peronList =[{name:"aaa",age:12},{name:"bbb",age:14}]
//子组件
//<h2>{{a}}</h>
import {defineProps} from 'vue'
import type Persons from '@/...'
//接收变量
defineProps(['a','b','c']);//可以接收但是无法在js里使用,这种写法无法校验父组件传递的类型
let x = defineProps(['a','b','c']);//接收a,b且将props保存起来,又b未传递,b=undefined
//调用时:
console.log(x.a);
//接收c,且校验类型:父组件调用子组件时必须传c,并且c必须是Persons类型的值
defineProps<{c:Persons}>();
//接收c,并限制c的类型,并调整c为非必传,指定默认值
withDefaults( defineProps<{c?:Persons}>(),{
c:()=>[{name:"21edf",age:12}]
})
四、子组件传值
建立一个父页面.vue 和子页面.vue,在父页面中将子组件引入进来,作为组件进行声明。
- 子页面:
<button @click=’handler’>我是子组件</button>
export default{
emits:[‘showChildMsg’],//检验子组件调用的传参方法是不是showChildMsg,不对的话控制台会报出警告,也可写作:
emits:{
showChildMsg:(value)=>{
if(typeof value ===’string’)
return true;
else{
console.warn(“类型不对!”)
return false;//但传值还是会穿过去,只是作为一个提醒
}
}
}
setup(props,context){
const handler = ()=>{
context.emit(“showChildMsg”,”我是子组件内部的消息”) }
return{ handler}
}
}
- 父页面:
<child @showChildMsg=‘fn($event)’></child>
export default{
setup(){
const fn= (event)=>{
altert(event)
}
return{ fn}
}
}
五、计算属性
//<p>{{fullName }}</p>
const num = ref("123");
let fullName = computed(() => {
return num.value + "2778";
});
//这种计算属性为只读属性,即fullName 只可以获取值,而不可以赋值
let fullName = computed({
get() {
return num.value + "2778";
},
set(val) {
num.value = val;
},
});
fullName.value = "7557";
//会执行一次set方法,因为set里num的值改变了,所以执行get,最终{{fullName}}=75572778
//这种计算属性为可读可写属性
六、watch 监听
取消了 vue2 的深度监听和普通监听,整合成了同一种监听
//(1)监听ref修饰的基本类型数据
let num = ref(1);
watch(num ,(newValue , oldValue)=>{
console.log(newValue,oldValue);
} );
//(2)监听ref修饰的对象类型数据
let num = ref({name:"abc",old:"12"});
watch(num,(newValue,oldValue)=>{
console.log("num变化了");
})
//此时如果修改num的属性值,watch监听并不执行,因为num始终指向的是原先的地址
num.value.name="aaaa";//watch监听不执行
num.value={name:"cba",old:"21"};//watch监听执行,因为num指向了新的地址
//如果需要监听具体数据变化,则需开启深度监听:
watch(num,(newValue,oldValue)=>{
console.log("num变化了");
},{deep:true,immediate:true});
//immediate:true意思为初始化时就触发监听,即oldValue: undefined → newValue: {name:"abc",old:"12"}
num.value.name="aaaa";
//watch监听执行,但oldValue和newValue打印是相同的(修改后的)值,因为num指向的仍然是原先的地址
num.value={name:"cba",old:"21"};
//watch监听执行,因为num指向了新的地址,所以oldValue和newValue不相同
//(3)监听reactive修饰的对象类型数据,隐式开启深度监听(不可关闭)
let num1 = reactive( {name:"abc",old:"12"});
watch(num1 , (newValue,oldValue)=>{
console.log(newValue,oldValue)
} );
num1.name="dbc";
Object.assign(num1,{name:"cba",old:"21"})
//都会触发watch,且newValue和oldValue相同,因为num1始终指向的是同一地址
//(4)用ref、reactive包括的对象类型,单独监听对象内的基本类型的属性,
//需要用函数形式(本质上是一个getter函数);监听对象内的对象类型属性需要分类查看:
let person=reactive({
name:"xiaoming",
grade:{
grade1: "12",
grade2: "23"
}
})
//单独监听对象内的基本类型的属性
watch(()=>person.name,(newValue,oldValue)=>{
console.log("person.name改变了");
})
//监听对象内的对象类型,分类讨论:
watch(person.grade,(newValue,oldValue)=>{
console.log("person.grade属性改变了");
})//只能监听到grade1或grade2的变化,无法监听到person.grade={grade1:"22",grade2:"22"}情况,
//因为只监听最初的对象{grade1: "12",grade2: "23"}
watch(()=>person.grade,(newValue,oldValue)=>{
console.log("person.grade改变了");
})//只能监听到person.grade={grade1:"22",grade2:"22"}情况,无法监听到grade1或grade2的变化
watch(()=>person.grade,(newValue,oldValue)=>{
console.log("person.grade改变了");
},{deep:true})//两种变化皆可监听
//(5)监听多个元素(任一变化就会触发)
watch([()=>person.name,person.grade],()=>{
console.log("")
})
watch([()=>person.name,()=>person.grade.grade1],()=>{
console.log("")
})
- watchEffect(默认监听):不设定监听的具体值,但在监听的方法体内用到的所有的值发生改变了,那么就会重新调用监听方法
setup(){
const count = ref(0);
const count1 =ref(‘wwww’);
watchEffect(()=>{//页面初始化的时候会触发监听
console.log(count.value);
console.log(count1.value);
//count、count1任一值发生改变之后两个console.log都会被执行,效果类似于全局监听
})
}
- 停止默认监听:
const count = ref(0);
const count1 =ref(‘wwww’);
const abc= watchEffect(()=>{//页面初始化的时候会触发监听
console.log(count.value);
console.log(count1.value);
if(某条件){
abc()//停止默认监听
}});
七、配置路径别名
vue3 中没有@的路径名,我们可以通过 vite.config.js 进行配置:
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'//引入路径模块
export default defineConfig({
plugins:[vue()],
resolve:{
alias:{
'@': path.resolve(__dirname , 'src/');
//__dirname指当前vite.config.js的绝对路径,即进入vite.config.js绝对路径/src的目录下,定义之后需要重启整个项目
}
}
})
八、配置 Router 路由
-
路由组件
通常存放在/pages 或/views 文件夹里,一般组件
通常存放在\components 文件夹 -路由组件
:靠路由的规则渲染出来的
routes:[ {path:'\home',component:Home}] -一般组件
:亲手写标签进行使用的:<HOME/>
通过点击导航,视觉上消失了的路由组件默认是被卸载了,需要的时候再去挂载。
在 src 目录下创建 router 文件夹,在文件夹下创建 commonRouter.js 和 index.js
- index.js:
import { createRouter, createWebHashHistory } from "vue-router"; //引入createRouter
import { routes } from "./commonRouter"; //引入路由配置
const router = createRouter({
//创建路由器实例并传递routes配置
history: createWebHistory(), //使用了history模式,需要配合后端
routes, //’routes:routes’的缩写
});
export default router;
- commonRouter.js:也可以直接定义在 index.js 里的 const router = createRouter...之前
export const routes = [
{
path: "/",
alias: "/home", //路径别名
component: () => import("@/components/HelloWorld.vue"),
},
];
或写成:
import Home from "@/components/HelloWorld.vue";
import News from "@/components/News.vue";
export const routes = [
{
path: "/",
alias: "/home", //路径别名
component: Home,
},
{
path: "/news",
component: News,
},
];
- src 路径下的 main.js 进行引入:
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index"; //引入路由
const app = createApp(App);
app.use(router);
app.mount("#app"); //页面渲染
//可写成:router.isReady(()=>app.mount(‘#app’)),
//即等router加载完成之后进行页面的渲染,可以大大提高性能
- 导航区 router 使用:
<div>
<RouterLink to="/home" active-class="isActive">首页</RouterLink >
<RouterLink to="/news" active-class="isActive">新闻</RouterLink >
<!-- to:跳转到的path ,也可写成 :to="{path:'/news'}"
active-class:点击后使用的布局class-->
</div>
命名路由:{
name:'xinwen',
path:'/news',
component: News
}
<RouterLink :to="{name:'xinwen'}" >新闻</RouterLink >
- 使用路由组件展示:
在展示区域的 div 标签内:
<div>
<RouterView></RouterView>
</div>
//import { RouterView , RouterLink } from 'vue-router'
路由器工作模式
- history 模式:
vue2: mode:'history'
vue3: history:createWebHistory()
react: BrowserRouter
优点:URL 更加美观,不带有#,更接近传统的网站 URL
缺点:后期项目上线,需要服务器配合处理路径问题,否则刷新会有 404 错误
const router = createRouter({
//创建路由器实例并传递routes配置
history: createWebHistory(), //使用了history模式,需要配合后端
routes,
});
- hash 模式
优点:兼容性更好,因为不需要服务器端处理路径
缺点:URL 带有#不太美观,且在 SEO 优化方面相对较差
const router = createRouter({
//创建路由器实例并传递routes配置
history: createWebHashHistory(),
routes,
});
嵌套路由
如果路由组件的内部,需要引用其他路由组件(例:Detail.vue),则称为嵌套路由
{
name:'xinwen',
path:'/news',
component: News ,
children:[
{ path:'detail',//嵌套路由不需要写斜杠
component:Detail
}
]
}
在 news.vue 中:
//导航区
<RouterLink to="/news/detail">详细</RouterLink>
//展示区
<div>
<RouterView></RouterView>
</div>
路由传值
- query 用法:
<ul>
<li v-for="news in newsList" :key="news.id">
<!-- 写法一 -->
<RouterLink :to=" `/news/detail?id=${news.id}&title=${news.title}` " >
<!-- 写法二 -->
<RouterLink :to="{
path:'/news/detail',
query:{
id:news.id,
title:news.title,
content:news.content
}
} " >
详细</RouterLink >
</li>
</ul>
获取路由传递的值:
<!-- {{query.id}} -->
import {toRefs} from 'vue'
import {useRoute} from 'vue-router'
let route = useRoute()
let {query} = toRefs(route)
- params 用法
{
name:'xinwen',
path:'/news',
component: News ,
children:[
{
name:'xian',
path:'detail/:id/:title/:content?',//先声明传递参数,加问号代表该参数可传可不传
component:Detail
}
]
}
//第一种写法
<RouterLink :to=" `/news/detail?id=${news.id}&title=${news.title}` " >
//第二种写法
<RouterLink :to="{
name:'xian', //不可以写path
params:{//不可以传递对象和数组
id:news.id,
title:news.title,
content:news.content
}
} " >
详细</RouterLink >
</li>
</ul>
获取路由传递的值:
<!-- {{route.params.id}} -->
import {toRefs} from 'vue'
import {useRoute} from 'vue-router'
let route = useRoute()
- props 用法
{
name:'xinwen',
path:'/news',
component: News ,
children:[
{
name:'xian',
path:'detail/:id/:title/:content?',
component:Detail,
//第一种写法:将路由收到的所有params参数作为props传递给路由组件
props:true
//第二种写法:函数写法:自己决定将什么参数作为props传递给路由组件(主要为适应query写法)
props(route){
return route.query
}
//第三种写法:对象写法:自己决定将什么参数作为props传递给路由组件
props:{
a:100,
b:200,
c:300
}
}
]
}
获取路由传递的值:
<!-- {{id}} -->
defineProps(['id','title','content'])
push 和 replace 属性
我们通常浏览网页都是push
模式,将浏览的历史记录通过push
,压入栈内,我们可以通过浏览器的前进后退来调整栈顶指针的指向,以此来实现内容的切换。如果我们需要当浏览了当前网页后无法再回到以前的网页,则使用replace
模式
<RouterLink replace :to=" `` "> </RouterLink>
编程式路由
作用:为了取代 RouterLink 标签实现编程式的写法,RouterLink 在浏览器里会被解释为 a 标签
import {useRouter} from 'vue-router'
const router = useRouter();
//写法一:字符串
router.push('/news') //用push(也可以用replace)的方式跳转到 /news
//写法二:对象
router.push({
name:'xiang'
query:{
id:"123",
title:"456",
content:"789"
}
})
//用法和:RouterLink的:to是一样的
路由重定向
当我们访问一个指定路径的时候,默认跳转到另一个路径,即为路由重定向
export const routes = [
{
path: "/home", //路径别名
component: Home,
},
{
path: "/news",
component: News,
},
{
path:"/"
redirect:'/home'
//当我们访问http//:localhost:8088时,路由重定向到当我们访问http//:localhost:8088/home
}
];
九、ref 唯一标识的使用
ref 的使用:由于父子组件中相同 id 会导致一些 Dom 操作的混淆,所以推荐使用 ref 唯一标识当前 vue 文件中的元素,类似于局部变量,防止组件之间 id 元素相互混淆。
- ref 使用在基本 HTML 标签:
//<h ref="title">abccccc</h>
let title = ref(); //创建一个title,存储ref标记的内容
console.log(title.value); //输出<h >abccccc</h>
- ref 使用在组件标签中:
//<Person ref="title1"/>
let title1 = ref();
console.log(title1.value);
//能够获取Person组件的相关内容,但是内容被封装无法展示,需要子组件使用`defineExpose`
子组件 Person:
let a = ref(0);
let b = ref(1);
defineExpose({ a, b }); //父组件中即可输出a,b的内容
十、TS 接口规范
用于在 vue3 内规范引用数据类型的使用,避免不必要的错误
export interface PersonInter{
name:string,
age:number,
id:string,
x?:string //?代表这个属性可有可无
}
export type Persons=Array<PersonInter>//数组写法1
export type Persons=PersonInter[]//数组写法2
import {type PersonInter , type Persons} from "@/..."
let person:PersonInter=reactive({name:"abc",age:12,id:"001"});
let personList:Array<PersonInter>=[{},{},{}]
let personList:Persons=[{},{},{}];
let personList=reactive<Persons>([{},{},{}])
十一 、生命周期函数
vue2 vue3
创建 创建前 beforeCreate() setup
创建完毕 created() setup
挂载 挂载前 beforeMount() onBeforeMount(()=>{})
挂载完毕 mounted() onMounted(()=>{})
更新 更新前 beforeUpdate() onBeforeUpdate(()=>{})
更新完毕 updated() onUpdated(()=>{})
销毁 销毁前 beforeDestory() onBeforeUnmount(()=>{}) (卸载前)
销毁完毕 destoryed() onUnmounted(()=>{})(卸载完毕)
通常父组件需要等所有的子组件挂载完毕后,才算父组件挂载完毕。
十二、自定义 hooks
体现的是封装的思想,把一系列处理特定数据的行为封装存入在 src 目录下创建的 hooks 文件夹,并命名为 useXXX.ts,此处 XXX 一般指处理指定的数据名称。且 hooks 内也存在属于自己的生命周期
例子:src/hooks/useSum.ts:
import { ref } from 'vue'
export default function () {//默认抛出的方法或属性不能设定名称
\\ 数据
let sum = ref(0)
\\方法
function add(){
sum.value+=1;
}
//向外部提供相关属性和方法
return {sum ,add}
}
app.vue:
import useSum from "@/hooks/useSum";
const { sum, add } = useSum();
十三、 Pinia的使用
(1) 准备:npm i pinia
(2) 在main.ts里引入:
import {createApp} from 'vue'
import App from './App.vue'
//引入pinia
import {createPinia} from 'pinia'
///
const app = createApp(App)
//创建pinia
const pinia = createPinia()
//安装pinia
app.use(pinia)
app.mount('#app')
(3) 在src目录下创建文件夹store,里面根据存储的分类创建相应命名的ts文件,下文以count.ts为例:
import {defineStore} from 'pinia'
export const useCountStore = defineStore('count',{
//真正存储数据的地方
state(){
return{
sum:6
school:'aaaa',
address:'bbbbb'
}
}
})//第一个参数是文件名,第二个参数时存储的内容
使用时:
import {useCountStore} from '@/store/count'
const countStore = useCountStore()
//以下两种方式皆可拿到state中的数据
console.log(countStore.sum)
console.log(countStore.$state.sum)
//<p> {{countStore.sum}} </p>
//修改数据
function add(){
//第一种修改方式,改几条数据就会执行几次修改
countStore.sum+=1;
//第二种修改方式:适合批量修改,在内部只执行一次修改,
countStore.$patch({
sum:888,
school:"cccc",
address:"12121"
})
//第三种修改方式,需要设定
countStore.increment(n.value)
}
import {defineStore} from 'pinia'
export const useCountStore = defineStore('count',{
//actions里面放置的是一个一个的方法,用于相应组件中的动作
actions:{
increment(value){
this.sum += value
}
}
//真正存储数据的地方
state(){
return{
sum:6
school:'aaaa',
address:'bbbbb'
}
}
})//第一个参数是文件名,第二个参数时存储的内容
响应式解构:storeToRefs()
const {sum,school,address} = toRefs(countStore)
//这会导致countStore里包括属性和方法都受到响应式包裹,消耗大
const {sum,school,address} = storeToRefs(countStore);
//仅关注属性,消耗小
getters的使用:对数据进行加工
import {defineStore} from 'pinia'
export const useCountStore = defineStore('count',{
//actions里面放置的是一个一个的方法,用于相应组件中的动作
actions:{
increment(value){
this.sum += value
}
},
//真正存储数据的地方
state(){
return{
sum:6
school:'aaaa',
address:'bbbbb'
}
},
getters:{
bigSum(state){
return state.sum * 10
},
// bigSum:state=> state.sum * 10,//箭头函数
upperSchool():string{
return this.school.toUpperCase
}//要用this就不可以用箭头函数
}
})//第一个参数是文件名,第二个参数时存储的内容
// <p>{{bigSum}}使用展示{{upperSchool}}</p>
const {sum,school,address,bigSum,upperSchool} = storeToRefs(countStore);
订阅$subscribe()
的使用:
设talkList是一个字符串数组
import {useTalkStore} from '@/store/loveTalk'
import {storeToRefs} from 'pinia'
const talkStore = unTalkStore()
const {talkList} = storeToRefs(talkStore)
talkStore.$subscribe((mutate,state)=>{
//当talkStore里面保存的数据发生了变化时会调用
localStorage.setItem('talkList',JSON.stringify(state.talkList);
//json格式存储对象类型数据需要使用JSON.stringify
//将变化后的talkList存入浏览器的localStorage,保证关闭浏览器后数据也不会丢失
})
export const useTalkStore = defineStore('talk',{
state(){
return{
talkList:JSON.parse(localStorage.getItem('talkList') as string)|| []
//talkList可能为空,设定一个空数组避免返回null
}
}
})
Store的组合式写法
export const useTalkStore = defineStore('talk',()=>{
//talkList就是state
const talkList = reactive(JSON.parse(localStorage.getItem('talkList') as string)|| [])
//getTalk函数相当于action
function getATalk(){
let obj={id:aaa,title:1111}
talkList.unshift(obj)
}
//组合式写法一定要注意进行return
return {talkList,getATalk}
})
十四、组件传值
基础传值:父组件向子组件传递数据,可直接通过属性传递给子组件,如car
。但子组件要向父组件传递数据,需要父组件给予子组件一个方法,子组件通过调用方法向父组件传递数据,如sendToy
<!-- 父组件 -->
<div class="father">
<Child :car="car" :sendToy="getToy" />
<!-- 当子组件调用sendToy时,执行父组件的getToy -->
</div>
<script>
import Child from './Child.vue'
import {ref} from 'vue'
let car = ref('奔驰')
let toy = ref('')
//方法
function getToy(value:string){
toy.value = value
}
</script>
<!-- 子组件 -->
<div class="child">
<h>{{toy}}</h>
<h>{{car}}</h>
<!-- 用法一 -->
<button @click="sendToy(toy)">把toy值传递给父组件</button>
<!-- 用法二 -->
<button @click="func">把toy值传递给父组件</button>
<!-- 用法三 -->
<button @click="emit('sendToy',toy)">把toy值传递给父组件</button>
</div>
<script>
import {ref} from 'vue'
let toy = ref('aaa')
//声明接收props
defineProps(['car','sendToy'])//用法一
let props = defineProps(['car','sendToy'])//用法二
function func(){//用法二
props.sendToy(toy);
}
// 用法三
const emit = defineEmits(['sendToy'])
</script>
mitt实现任意组件通信
类似于pubsub
、$bus
、mitt
都是实现:
任意组件之间:
接收数据方:提前绑定好事件(pubsub
:提前订阅消息)
提供数据方:任意组件在合适的时候触发事件(pubsub
:发布消息)
- 准备:
npm i mitt
- 在src目录下创建utils文件夹,新建emitter.ts:
//引入mitt
import mitt from 'mitt'
//调用mitt得到emitter,emitter能:绑定事件、触发事件
const emitter = mitt()
//暴露emitter
export default emitter
- 使用方法:
//绑定事件:
emitter.on('test1',()=>{
console.log('test1被调用了')
})
//调用
function fun(){
//触发事件
emitter.emit('test1')
//解绑事件
emitter.off('test1')
//获取所有绑定事件,统一解绑
emitter.all.clear()
}
- 样例:
接收方:
import {ref,onUnmounted} from 'vue'
import emitter from '@/utils/emitter'
//数据
let toy = ref("")
//给emitter绑定send-toy事件
emitter.on('send-toy',(value:any)=>{
toy.value = value
})
//在组件卸载时解绑send-toy事件,避免资源消耗
onUnmounted(()=>{
emitter.off('send-toy')
})
发送方:
<button @click="emitter.emit('send-toy',toy)"></button>
<script setup lang='ts'>
import {ref} from 'vue'
import emitter from '@/utils/emitter'
let toy = ref("aaaa");
</script>
使用v-model 进行组件通信
<!-- 父组件 -->
<Child v-model:name="userName"/>
<!-- name是username的别名,也可以:v-model="userName" -->
<!-- 子组件 -->
<script>
defineProps(['name'])
</script>
使用$attrs实现(爷)祖-孙组件通信
在组件通信中,未被defineProsps获取的数据都会存在$attrs
中,子组件不做获取时,可以将$attrs
传递给它的子组件使用,即实现祖孙通信。
<!-- 父组件 -->
<!-- 可写成 v-bind="{a:123}"-->
<Child :a="123" :update="getName"></Child>
<script>
function getName(value){}
</script>
<!-- 子组件 -->
<GrandChild v-bind="$attrs"/>
<!-- 孙组件 -->
<button @click="update('12345')"></button>
<script>
defineProps(['a','update'])
</script>
使用 $refs
和$parent
-
$refs
实现父组件向子组件通信 -
$parent
实现子组件向父组件通信
<!-- 父组件 -->
<button @click="changeToy">改变child1的玩具</button>
<Child1 ref="c1"></Child1>
<Child1 ref="c2"></Child1>
<script>
let c1 = ref();
function changeToy(){
c1.value.toy="bbb"
}
</script>
<!-- 子组件1 -->
<script>
let toy = ref('aaaa')
let book = ref(3)
defineExpose({toy,book})//把数据交给外部
</script>
$refs
里存着所有的子组件暴露给父组件的数据,值为对象,包含所有被ref属性标识的Dom元素或组件实例
<button @click="getChild($refs)">改变child1的玩具</button>
<Child1 ref="c1"></Child1>
<Child1 ref="c2"></Child1>
<script>
function getChild(refs:{[key:string]:any}){
for(let key in refs){
refs[key].book +=3
}
}
</script>
$parent
里存着所有的父组件抛出的数据。值为对象,当前组件的父组件实例对象。
<!-- 父组件 -->
<script>
let name = "AAA"
defineExpose({name})
</script>
<!-- 子组件 -->
<button @click="changeName($parent)"></button>
<script>
function changeName(parent:any){
parent.name+="123"
}
</script>
使用provide-inject
优势在于:实现祖孙之间直接传递数据,且不需要经过中间的父组件。
//祖组件
let money = ref(123)
let car = reactive({
brand:'奔驰',
price:200
})
//向后代提供数据,无论隔了多少代都可以获取
provide('qian',money)//不能传.value是因为需要传一个响应式对象过去才能具有响应式
provide('che',car)
// 后代组件获取
let x = inject("qian","我是默认值")//如果没有找到qian,则x为默认值,如果不设定默认值可以运行但是ts会有代码警告
let y = inject("che",{brand:"未知",price:0})
祖组件给予后代组件修改数据的方法:
<script>
let money = ref(123)
function changeMoney(value:number){
money.value-=value;
}
provide('moneyContext',{money,changeMoney})
</script>
// 子组件获取
<button @click="changeMoney(6)">改变祖组件的money值</button>
<script>
let {money,changeMoney} = inject("moneyContext",{money:0,changeMoney:(param:number)=>{}})
</script>
插槽
父组件中自定义展示在子组件内部的内容
- 默认插槽
<!-- 父组件 -->
<Category>
<!-- 双标签内部加入需要展示在子组件内部的内容 -->
<p>12334452</p>
</Category>
<!-- 子组件 -->
<div>
<h2>title</h2>
<slot>如果父组件没有定义传入内容,则默认展示这段文字。若传了内容,则不展示这段文字</slot>
<!-- 若子组件中出现了多次默认插槽,则父组件中传入的内容都会展示在每个slot的位置 -->
</div>
- 具名插槽
为了指定内容展示在特定的子组件区域
<!-- 父组件 -->
<Category>
<template v-slot:s2>
<!-- 指定这段内容展示在子组件中插槽名为s2的插槽中 -->
<p>12334452</p>
</template>
<template #s1>
<!-- v-slot:s1 也可写成#s1 -->
<p>这是标题</p>
</template>
</Category>
<!-- 子组件 -->
<div>
<slot name="s1"></slot>
<slot name="s2"></slot>
<!-- 默认插槽的name="default" -->
</div>
- 作用域插槽
为了让子组件统一管理需要展示的数据内容,父组件调用时数据需要从子组件获取
<!-- 父组件 -->
<Game v-slot="params">
<p>{{params.x}}</p>
</Game>
<Game v-slot:s1="{x}">
<p>{{x}}</p>
</Game>
<!-- 子组件 -->
<slot name="s1" x="2334" :youxi="games"></slot>
<slot x="2334" ></slot>
<!-- 传递的数据全部会生成一个params对象被父组件接收 -->
<script>
let games = reactive([{...}])
</script>
其他API
shallowRef
与shallowReactive
-
shallowRef
:创建一个响应式数据,但只对顶层属性进行响应式处理
用法:
let a = shallowRef({
name:"123"
})
a.value={name:"321"}//可以生效
a.value.name="321"//不可以生效 shallowRef只关注.value(最外层)层级的修改
特点:只跟踪引用值的变化,不关心值内部的属性变化
-
shallowReactive
:创建一个浅层响应式对象,只会使对象的最顶部属性变成响应式的,对象内部的嵌套属性则不会变成响应式的
用法:
const obj =shallowReactive({...})
特点:对象的顶层属性是响应式的,但嵌套对象的属性不是
通过使用
shallowRef()
和shallowReactive()
来绕开深度响应。浅层式API创建的状态只在其顶层是响应式的,对所有深层的对象不会做任何处理,避免了对每个内部属性做响应式所带来的性能成本,这使得属性的访问变得更快,可以提升性能
readonly
和shallowReadonly
限制属性的修改权限
let sum = ref(1)
let sum2 = readonly(sum)//sum2不可修改,但sum改变时sum2会跟着改变
let car = reactive({
brand:"奔驰",
option:{
color:'红色',
price:100
}
})
let car1 = shallowReadonly(car)
//car1.brand不可更改,但car1.option.color可以更改,shallowReadonly只保证最顶层只读。
//但car1的所有值都会随着car更改而更改
toRaw
和markRaw
-
toRaw
:用于获取一个响应式对象的原始对象,toRaw返回的对象不再是响应式的,不会触发试图更新 -
markRaw
:一个非响应式对象可以通过reactive包裹后变成一个具有响应式的对象,markRaw
标记一个对象后,永远不会是响应式的。其即便是reactive包裹之后也不会变成响应式的
customRef
作用:用于创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制
export default function(initValue:string,delay:number){
let msg = customRef((track,trigger)=>{
let timer:number
return{
get(){//msg被读取时调用
track()//告诉vue数据msg很重要,你要对msg进行持续关注一旦msg变化就更新
return initValue
},
set(value){//msg被修改的时候调用
clearTimeout(timer)
timer = setTimeout(()=>{
initValue = value
trigger()//通知vue数据msg变化了
},delay)
}
}
})
}
Teleport
一种能够将我们的组件html结构移动到指定位置的技术
<teleport to='body'>
<!-- 将内部的内容转移至body标签内部 -->
<div>...</div>
</teleport>