做网站公司北京,知乎广告代理商,成全视频免费观看在线看第6季高清版下载,wordpress版 影视站Vue3中的常见组件通信
概述
在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。
组件关系传递方式父传子1. props2. v-model3. $refs4. 默认…Vue3中的常见组件通信
概述
在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。
组件关系传递方式父传子1. props2. v-model3. $refs4. 默认插槽、具名插槽子传父1. props2. 自定义事件3. v-model4. $parent5. 作用域插槽祖传孙、孙传祖1. $attrs2. provide、inject兄弟间、任意组件间1. mitt2. pinia
以下是各个组件通信方式的详细用法。
1.props
1.1 准备父子组件
首先准备一个简单的父子组件的样式如下是父组件代码
templatediv classfatherh2这是父组件/h2Child//div
/templatescript setup langts nameFather//引入子组件import Child from ./Child.vue;/scriptstyle scoped.father{margin: 5px;background-color:rgb(205, 165, 32);padding: 20px;color: white;}
/style以下是子组件代码
templatediv classchildh2这是子组件/h2/div
/templatescript setup langts nameChild/scriptstyle scoped.child{margin: 5px;background-color: rgba(93, 135, 20, 0.224);border: 1px solid;border-color: white;box-shadow: 0 0 5px;padding: 10px;color: #000;}
/style运行结果如下所示 1.2 父传子通信的实现
用props实现父传子通信可以用标签中的属性值直接传递数据父组件代码中只需要在子组件标签上添加属性值即可如下代码传递两个数据
Child :f2CMsgf2CMsg :f2CObjf2CObj/定义传递的数据内容两个数据分别是ref定义的基本类型的响应式数据和reactive定义的对象类型的响应式数据。
//引入ref和reactive
import {ref,reactive} from vue//数据ref定义的基本类型的响应式数据
let f2CMsg ref(你好子组件。)//数据reactive定义的对象类型的响应式数据
let f2CObj reactive({
id:asdfg01,
name:张三,
age:18,
detail:这是父组件中的对象信息
})
在子组件中需要声明接收数据props注意接收的数据格式是数组哪怕是只传递一个数据也要写成数组的形式。
//声明接收数据props
defineProps([f2CMsg,f2CObj])子组件中在页面展示接收的数据如下代码
h4父组件传递的信息{{ f2CMsg }}/h4
h4父组件传递的对象/h4
ulli姓名{{f2CObj.name}}/lili年龄{{f2CObj.age}}/lili详情{{f2CObj.detail}}/li
/ul然后运行结果如下 1.3 子传父通信的实现
用props实现子传父的通信需要父组件先向子组件传递一个函数然后传递的数据以参数的形式传递给函数父组件中先定义子传父的数据变量和函数或者方法如下
//定义子传父的数据变量
let c2FMsg ref()
//定义方法传递给子组件
function getMsg(value:string){c2FMsg.value value
}在标签中把方法传给子组件
Child :f2CMsgf2CMsg :f2CObjf2CObj :sendMsggetMsg/在子组件中接收数据,并定义数据内容
defineProps([f2CMsg,f2CObj,sendMsg])
let c2FMsg ref(你好父组件。)在子组件中设置一个按钮给按钮绑定点击事件点击事件触发sendMsg方法并传递参数
button clicksendMsg(c2FMsg)点我向父组件传递信息/button现在数据应该已经传递给了父组件在父组件可以用如下代码展示
h4 v-showc2FMsg子组件传递的信息{{ c2FMsg }}/h4运行结果如下 点击按钮后运行结果如下 至此我们已经用props实现了子传父的通信功能。当然子传父也可以传递对象。此处不再展示。如下是完整代码
父组件中的代码
templatediv classfatherh2这是父组件/h2h4 v-showc2FMsg子组件传递的信息{{ c2FMsg }}/h4 Child :f2CMsgf2CMsg :f2CObjf2CObj :sendMsggetMsg/ /div
/templatescript setup langts nameFather//引入子组件import Child from ./Child.vue;//引入ref和reactiveimport {ref,reactive} from vue//数据ref定义的基本类型的响应式数据let f2CMsg ref(你好子组件。)//数据reactive定义的对象类型的响应式数据let f2CObj reactive({id:asdfg01,name:张三,age:18,detail:这是父组件中的对象信息})//定义子传父的数据变量let c2FMsg ref()//定义方法传递给子组件function getMsg(value:string){c2FMsg.value value }
/scriptstyle scoped.father{margin: 5px;background-color:rgb(205, 165, 32);padding: 20px;color: white;}
/style子组件的代码
templatediv classchildh2这是子组件/h2h4父组件传递的信息{{ f2CMsg }}/h4h4父组件传递的对象/h4ulli姓名{{f2CObj.name}}/lili年龄{{f2CObj.age}}/lili详情{{f2CObj.detail}}/li/ulbutton clicksendMsg(c2FMsg )点我向父组件传递信息/button/div
/templatescript setup langts nameChild
import { ref,reactive } from vue;//声明接收数据props
defineProps([f2CMsg,f2CObj,sendMsg])let c2FMsg ref(你好父组件。)/scriptstyle scoped.child{margin: 5px;background-color: rgba(93, 135, 20, 0.224);border: 1px solid;border-color: white;box-shadow: 0 0 5px;padding: 10px;color: #000;}
/style1.4 小结
用props实现父子通信的步骤是这样的
父传子父组件中定义传递的数据–标签中用属性值直接传递数据–子组件中声明接收数据–用插值语法展示数据。
子传父: 父组件先定义接收数据的变量–父组件中定义函数–将函数传递给子组件–子组件中声明接收数据接收的数据为函数–子组件中定义传递的数据–子组件中调用接收的函数将定义的要传递的数据作为参数进行传递–父组件中收到数据可以在页面中展示。
最后总结如下
父传子标签中的属性值是非函数
子传父标签中的属性值是函数
2. 自定义事件
自定义事件通常用于子传父需要注意在原生事件中事件名是特定的比如clickkeyup等在自定义事件中事件名是任意的在原生事件中事件对象 e v e n t 是包含事件相关信息的对象 ‘ p a g e X ‘ 、 ‘ p a g e Y ‘ 、 ‘ t a r g e t ‘ 、 ‘ k e y C o d e ‘ 在定义事件中事件对象 event是包含事件相关信息的对象pageX、pageY、target、keyCode在定义事件中事件对象 event是包含事件相关信息的对象‘pageX‘、‘pageY‘、‘target‘、‘keyCode‘在定义事件中事件对象event是调用emit所提供的数据可以是任意类型。
2.1 准备父子组件
父子组件代码与样式与本文中1.1中的完全相同此处不再赘述。
2.2 自定义事件实现子传父通信
首先在子组件中定义要传递的数据此次依然已字符串为例如下代码
let c2FMsg ref(你好父组件。)然后在父组件中定义接收的数据变量并声明函数用来保存接收的数据。
let c2FMsg ref()//声明函数saveMsg用来保存接收的数据
function saveMsg(value:string){c2FMsg.value value
}之后给子组件绑定自定义事件,事件名为send-message同时将函数saveMsg传进去注意自定义事件名的命名规范官方建议采用肉串形式的命名方式。
!-- 给子组件绑定自定义事件 --
Child send-messagesaveMsg/在子组件中需要声明事件
//声明事件
let emit defineEmits([send-message])触发事件的代码如下
emit(send-message)只要在子组件中写出上面触发事件的代码就可以实现子传父的通信这次以子组件挂载3秒后自动触发事件为例如下代码
//组件挂载3秒后触发事件
onMounted((){setTimeout((){//触发事件send-message并传c2FMsgemit(send-message,c2FMsg)},3000)
})此时已经实现了子传父的通信最后在父组件中展示出来如下代码
h3 v-showc2FMsg子组件传递的信息{{ c2FMsg }}/h3最后运行页面效果如下在刚启动页面是如下效果 3秒钟之后看下的效果如下图所示 以下是完整代码
父组件代码
templatediv classfatherh2这是父组件/h2h3 v-showc2FMsg子组件传递的信息{{ c2FMsg }}/h3!-- 给子组件绑定自定义事件 --Child send-messagesaveMsg//div/templatescript setup langts nameFather//引入子组件import Child from ./Child.vue;import { ref } from vue;let c2FMsg ref()//声明函数用来保存接收的数据function saveMsg(value:string){c2FMsg.value value}/scriptstyle scoped.father{margin: 5px;background-color:rgb(205, 165, 32);padding: 20px;color: white;}/style以下是子组件代码
templatediv classchildh2这是子组件/h2/div/templatescript setup langts nameChildimport {ref,onMounted} from vuelet c2FMsg ref(你好父组件。)//声明事件let emit defineEmits([send-message])//组件挂载3秒后触发事件onMounted((){setTimeout((){//触发事件send-message并传c2FMsgemit(send-message,c2FMsg)},3000)})/scriptstyle scoped.child{margin: 5px;background-color: rgba(93, 135, 20, 0.224);border: 1px solid;border-color: white;box-shadow: 0 0 5px;padding: 10px;color: #000;} /style2.3 小结
以上便是自定义事件的基本用法在实际开发中是比较常用的用来实现子传父的通信方式。
3.mitt
mitt与pubsub订阅消息与发布消息功能类似它可以实现在任意组件间的通信。
3.1安装mitt及引入mitt
mitt需要安装在终端中输入命令npm i mitt来安装。
mitt安装好之后按照工程化的管理需要在src的文件下新建文件夹utils然后在utils文件夹中新建文件emitter.ts。
在emitter.ts文件中引入mitt并创建emitter同时暴露emitter如下代码
//引入mitt
import mitt from mitt//调用mitt得到emitter,emitter可以绑定事件和触发事件
const emitter mitt()//暴露emitter
export default emitter之后需要再在main.ts中引入emitter如下代码
import emitter from /utils/emitter3.2 emitter基本用法
emitter身上有四个方法分别是
**on()**用来绑定事件接收两个参数第一个参数是事件名第二个参数是事件触发时的回调函数**emit()**用来触发事件参数为事件名**off()**用来解绑事件参数为事件名**all**all有clear属性直接调用clear()属性可以解绑全部事件。
以下代码为展示emitter的基本用法
//绑定事件test1,当事件触发时执行回调
emitter.on(test1,(){console.log(test1被调用了)
})//绑定事件test2,当事件触发时执行回调
emitter.on(test2,(){console.log(test2被调用了)
})//绑定事件test3,当事件触发时执行回调
emitter.on(test3,(){console.log(test3被调用了)
})//触发事件每间隔1秒触发一次
setInterval((){//触发事件test1emitter.emit(test1)//触发事件test2emitter.emit(test2)//触发事件test3emitter.emit(test3)
},1000)//解绑事件2秒后解绑test1
setTimeout((){emitter.off(test1)console.log(--------test1解绑了)
},2000)//解绑事件4秒后解绑所有事件
setTimeout((){emitter.all.clear()console.log(--------所有的事件解绑了)
},4000)运行后在控制台输出如下内容 3.3emitter在组件中的用法
首先创建一个父组件两个子组件父组件代码如下
templatediv classfatherh3父组件/h3Child1/Child2//div
/templatescript setup langts nameFatherimport Child1 from ./Child1.vueimport Child2 from ./Child2.vue
/scriptstyle scoped.father{margin: 5px;background-color:rgb(79, 186, 111);padding: 20px;color: white;}
/style子组件1代码
templatediv classchild1h3子组件1/h3/div
/templatescript setup langts nameChild1/scriptstyle scoped.child1{margin: 5px;background-color: rgba(7, 7, 7, 0.224);border: 1px solid;border-color: white;box-shadow: 0 0 5px;padding: 10px;color: #760e0e;}
/style子组件2代码
templatediv classchild2h3子组件2/h3/div
/templatescript setup langts nameChild2/scriptstyle scoped.child2{margin: 5px;background-color: rgba(255, 255, 255, 0.224);border: 1px solid;border-color: white;box-shadow: 0 0 5px;padding: 10px;color: #05035f;}
/style运行效果如下 然后我们在子组件1中准备一些数据如下
//数据
let book reactive({name:西游记,author:吴承恩,price:119.95
})然后在页面中展示
!-- 展示 --
h4图书名称{{ book.name }}/h4
h4图书作者{{ book.author }}/h4
h4图书价格{{ book.price }}/h4运行效果如下 接下来在子组件2中引入emitter然后创建book数据给emitter绑定事件并传入回调函数
//引入emitterimport emitter from /utils/emitter;import { reactive } from vue;//数据let book reactive({name:,author:,price:null})//给emitter绑定getBook事件传入回调函数回调函数接收一个参数emitter.on(getBook,(value:any){// console.log(value)book.name value.namebook.author value.authorbook.price value.price})然后在子组件1中创建一个按钮绑定click事件触发getBook事件并传递book参数
button clickemitter.emit(getBook,book)将book信息发送给子组件2/button最后在子组件2中展示接收的到的信息 !-- 展示 --h4图书名称{{ book.name }}/h4h4图书作者{{ book.author }}/h4h4图书价格{{ book.price }}/h4最后运行后页面效果如下 点击按钮后效果如下 至此已经完成了子组件1向子组件2通信。
子组件1完整代码如下
templatediv classchild1h3子组件1/h3!-- 展示 --h4图书名称{{ book.name }}/h4h4图书作者{{ book.author }}/h4h4图书价格{{ book.price }}/h4button clickemitter.emit(getBook,book)将book信息发送给子组件2/button/div
/templatescript setup langts nameChild1import emitter from /utils/emitter;import { reactive } from vue;//数据let book reactive({name:西游记,author:吴承恩,price:119.95})
/scriptstyle scoped.child1{margin: 5px;background-color: rgba(7, 7, 7, 0.224);border: 1px solid;border-color: white;box-shadow: 0 0 5px;padding: 10px;color: #760e0e;}
/style子组件2 的完整代码如下
templatediv classchild2h3子组件2/h3!-- 展示 --h4图书名称{{ book.name }}/h4h4图书作者{{ book.author }}/h4h4图书价格{{ book.price }}/h4/div
/templatescript setup langts nameChild2//引入emitterimport emitter from /utils/emitter;import { reactive } from vue;//数据let book reactive({name:,author:,price:null})//给emitter绑定getBook事件传入回调函数回调函数接收一个参数emitter.on(getBook,(value:any){// console.log(value)book.name value.namebook.author value.authorbook.price value.price})
/scriptstyle scoped.child2{margin: 5px;background-color: rgba(255, 255, 255, 0.224);border: 1px solid;border-color: white;box-shadow: 0 0 5px;padding: 10px;color: #05035f;}
/style3.4 小结
接收数据的组件必须要先绑定事件订阅发送数据的组件要触发事件只要组件中引入了emitter并执行了emitter.emit()代码并传递参数即可实现任意组件间的通信。
4.v-model
v-model常用于普通html标签中的双向绑定这个绑定用法无法实现跨组件通信v-model用在组件标签中的时候可以实现父子间的组件通信而这样通信方式常用于UI组件库。要理解UI组件库的v-model的双向通信原理需要先明白普通html标签中的v-model的底层原理。
4.1 普通HTML标签中v-model实现双向绑定的底层原理
在普通html标签中用v-model可以实现数据的双向绑定如下代码所示是把input输入框里的数据与username进行双向绑定
templatediv classfatherh3父组件/h3 brinput idinput1 typetext v-modelusername/div
/templatescript setup langts nameFatherimport { ref } from vue; // 数据let username ref(zhansan)/scriptstyle scoped
.father {height: 300px;padding: 20px;color: #ffffff; text-align:center; background-image: url(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406041301030.png);background-size: cover
}
#input1{color: #000;
}
/style运行后在浏览器打开vue开发者工具如下图所示 上图中更改vue开发者工具中的username的值页面也会跟着发生变化这个实现的是把数据呈现在页面如果修改input输入框中的内容username的数据也会跟着发生改变这个实现的是页面传向数据这就双向绑定中的双向而实现这个双向绑定的关键就是在于input标签中写了v-model如下所示
input idinput1 typetext v-modelusername而上面的代码实现的底层原理是这样的先把上面的代码改成如下图所示
input idinput1 typetext :valueusername这样可以实现数据呈现在页面数据修改页面也会跟着修改但是修改页面数据却不会变化这只实现了一个方向的数据绑定接着再给input标签增加属性,如下代码
input idinput1 typetext :valueusername inputusername$event.target.value这样再测试就会发现页面中的数据也可以传向数据了修改input标签中的内容数据也会变化。
注意上面代码中inputusername e v e n t . t a r g e t . v a l u e 这句代码 t s 会报警我们需要处理一下对 event.target.value这句代码ts会报警我们需要处理一下对 event.target.value这句代码ts会报警我们需要处理一下对event.target进行断言报警就会消失
input idinput1 typetext :valueusername inputusername(HTMLInputElement$event.target).value普通input标签中v-model实现双向绑定的底层原理就是:valueinput事件。
4.2组件标签中v-model中实现双向绑定
首先准备一个自己的UI组件作为子组件代码如下
templateinput typetext
/templatescript setup langts nameMyInput/scriptstyle scopedinput{background-color:transparent;color: #ffffff;border: 0px;border-bottom: 1px solid #ffffff ;margin: 5px;}
/style然后在父组件中引入
//引入MyInput组件import MyInput from ./MyInput.vue;在父组件中把MyInput组件呈现在页面中
label用户名MyInput//label运行效果如下 这样效果出来了但是没有还没有实现数据绑定首先在MyInput组件标签上增加:modelValue属性和绑定update:model-value事件如下代码
MyInput :modelValueusername update:model-valueusername$event/然后需要在MyInput组件中声明props和声明事件来接收数据和事件
//接收props
defineProps([modelValue])
//声明事件
let emit defineEmits([update:model-value])最后在MyInput组件中的普通html标签中添加:value属性和绑定input事件
input typetext :valuemodelValueinputemit(update:model-value,(HTMLInputElement$event.target).value)至此已经实现了父组件和子组件MyInput组件的双向通信,如下图所示 最后在父组件中的MyInput组件标签上可以直接简写为如下代码
MyInput v-modelusername/实现的效果是完全一样的。
我们在用UI组件库的时候可以直接这样写前提是UI组件库已经处理好了底层逻辑。
以下是完整代码
父组件
templatediv classfatherh3父组件/h3 br!-- input idinput1 typetext v-modelusername --!-- 下面是v-model 的本质 --!-- input idinput1 typetext :valueusername inputusername(HTMLInputElement$event.target).value --!-- 下面是v-model 的本质 --!-- label用户名MyInput :modelValueusername update:model-valueusername$event//label --label用户名MyInput v-modelusername//label/div
/templatescript setup langts nameFatherimport { ref } from vue; //引入MyInput组件import MyInput from ./MyInput.vue;// 数据let username ref(zhansan)/scriptstyle scoped
.father {height: 300px;padding: 20px;color: #ffffff; text-align:center; background-image: url(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406041301030.png);background-size: cover
}
#input1{color: #000;
}
/style
MyInput组件
templateinput typetext :valuemodelValueinputemit(update:model-value,(HTMLInputElement$event.target).value)
/templatescript setup langts nameMyInput//声明propsdefineProps([modelValue])//声明事件let emit defineEmits([update:model-value])
/scriptstyle scopedinput{background-color:transparent;color: #ffffff;border: 0px;border-bottom: 1px solid #ffffff ;margin: 5px;}
/style4.3一个UI组件实现多个数据传送
在父组件中可以改value比如改成usName如下代码所示
MyInput v-model:usNameusername/这个代码的本质是如下代码
MyInput :usNameusername update:usNameusername$event/
在MyInput组件代码中就需要改成如下代码
templateinput typetext :valueusNameinputemit(update:usName,(HTMLInputElement$event.target).value)
/templatescript setup langts nameMyInput//声明propsdefineProps([usName])//声明事件let emit defineEmits([update:usName])
/script这样改完后运行效果跟之前是完全一样的接下来再扩展一下父组件中的MyInput标签改成如下代码
MyInput v-model:usNameusername v-model:pawordpassword/然后在MyInput组件中代码改成如下
templateinput typetext :valueusNameinputemit(update:usName,(HTMLInputElement$event.target).value)brinput typetext :valuepawordinputemit(update:paword,(HTMLInputElement$event.target).value)
/templatescript setup langts nameMyInput//声明propsdefineProps([usName,paword])//声明事件let emit defineEmits([update:usName,update:paword])
/script这样就实现一个组件内双向绑定两个数据了如下图所示 4.4小结
v-model可以实现父子间的通信v-model即可以设置在普通html标签中也可以设置在组件标签中设置在组件标签中可以实现父子间的双向通信前提是子组件底层做了处理。
5.$attrs
$attrs用于当前组件的父组件与当前组件的子组件相互通信也就是祖孙间通信。
5.1准备三个组件
首先准备三个组件Father组件代码如下
templatediv classfatherh3父组件/h3Child//div
/templatescript setup langts nameFatherimport Child from ./Child.vue /scriptstyle scoped.father{background-color: rgb(74, 108, 110);padding: 20px;color: #fff;}
/styleChild组件代码如下
templatediv classchildh3子组件/h3GrandChild//div
/templatescript setup langts nameChildimport GrandChild from ./GrandChild.vue
/scriptstyle scoped.child{margin-top: 20px;background-color: rgb(213, 217, 219);padding: 20px;color: #000;}
/styleGrandChild组件代码如下
templatediv classgrand-childh3孙组件/h3 /div
/templatescript setup langts nameGrandChild/scriptstyle scoped.grand-child{margin-top: 20px;background-color: rgb(164, 85, 31);padding: 20px;color: #fff; }
/style运行后效果如下 5.2祖传孙的实现
首先给父组件添加一些数据 import {ref} from vue//数据let a ref(100)let b ref(200)let c ref(300)然后在父组件页面上呈现出来
ullia: {{ a }}/lilib: {{ b }}/lilic: {{ c }}/li
/ul同时将数据传给Child组件
Child :aa :bb :cc/接下来再写Child组件的代码首先要接收数据
//接收props
defineProps([a])页面呈现出来如下
ullia: {{ a }}/li
/ul呈现效果如下 注意父组件给子组件传递了abc三个数据但是子组件只接收了一个数据a那么剩下的两个bc数据去哪里了呢此时需要借助浏览器的vue开发者工具打开后可以看到b和c的数据在attrs里。 在页面呈现一下attrs
ullia: {{ a }}/lili其他: {{ $attrs }}/li
/ul页面效果如下 可以看出来$attrs是个对象格式的数据这样Child组件就可以把attrs的数据传递给GrandChild组件
!-- 将attrs传给GrandChild --
GrandChild :$attrs/**注意:**上面的代码等价于下面的代码
GrandChild v-bind$attrs/由于$attrs的值是对象{‘‘b’’:200“c”:300},所以上的代码又等价于
GrandChild :bb :cc/然后在GrandChild组件中接收b和c
defineProps([b,c])并在页面呈现
ul lib: {{ b }}/lilic: {{ c }}/li
/ul运行结果如下 这样就实现了祖传孙的过程在上面的代码中Child组件也可以不接收任何propsFather组件传的所有的数据都通过$attrs传给GrandChild组件运行效果如下 5.3孙传祖的实现
如果实现孙传祖的效果需要在Father组件中给Child传递一个函数GrandChild组件接收后触发这个函数与props实现子传父的做法一致如下是在Father组件中定义函数如下
function changeA(value:number){a.value value
}传给Child组件
Child :aa :bb :cc :changeAchangeA/在GrandChild组件中接收
defineProps([a,b,c,changeA])再添加一个按钮绑定定click事件触发函数并传递参数
button clickchangeA(100)点我更改父组件中a的值增加100/button这样就实现了孙传祖的过程当点击按钮时Father组件中的a值会增加100每点击一次增加100这个新的值也会再传给GrandChild实现的效果就是点击按钮后Father组件和GrandChild组件中的a值会同时变化如下图所示 以下是各个组件的完整代码
Father组件
templatediv classfatherh3父组件/h3ullia: {{ a }}/lilib: {{ b }}/lilic: {{ c }}/li/ulChild :aa :bb :cc :changeAchangeA//div
/templatescript setup langts nameFatherimport Child from ./Child.vue import {ref} from vue//数据let a ref(100)let b ref(200)let c ref(300)function changeA(value:number){a.value value}/scriptstyle scoped.father{background-color: rgb(74, 108, 110);padding: 20px;color: #fff;}
/style
Child组件
templatediv classchildh3子组件/h3!-- ullia: {{ a }}/lili其他: {{ $attrs }}/li/ul --!-- 将attrs传给GrandChild --GrandChild :$attrs//div
/templatescript setup langts nameChildimport GrandChild from ./GrandChild.vue//接收props// defineProps([a])
/scriptstyle scoped.child{margin-top: 20px;background-color: rgb(213, 217, 219);padding: 20px;color: #000;}
/styleGrandChild组件
templatediv classgrand-childh3孙组件/h3 ullia: {{ a }}/lilib: {{ b }}/lilic: {{ c }}/li/ulbutton clickchangeA(100)点我更改父组件中a的值增加100/button/div
/templatescript setup langts nameGrandChild//接收props
defineProps([a,b,c,changeA])/scriptstyle scoped.grand-child{margin-top: 20px;background-color: rgb(164, 85, 31);padding: 20px;color: #fff; }button{color: #000;}
/style5.4小结
$attrs用于祖孙间的通信$attrs是一个对象包含所有父组件传入的标签属性。
6. r e f s 、 refs、 refs、parent
$refs用于父传子$parent用于子传父。
6.1准备组件
准备三个组件一个父组件两个子组件。
父组件代码
templatediv classFatherdiv idd1h3这是父组件/h3存款{{ money }} 万元/divChild1/Child2//div
/templatescript setup langts nameFather
import Child1 from ./Child1.vue
import Child2 from ./Child2.vue
import {ref} from vue//数据
let money ref(100)/scriptstyle scoped.Father{background-color: rgb(155, 162, 168);padding: 10px;margin: 10px;}#d1{margin-left: 10px;}
/style子组件1代码
templatediv classChild1h3这是子组件1/h3ulli书籍{{ book }} 本/lili玩具{{ toy }}/li/ul/div/templatescript setup langts nameChild1
import {ref} from vue//数据
let book ref(10)
let toy ref(滑板车)/scriptstyle scoped.Child1{background-color: rgb(132, 114, 148);margin: 10px 0;padding: 10px;color: white;}
/style子组件2代码
templatediv classChild2h3这是子组件2/h3ulli书籍{{ book }} 本/lili玩具{{ toy }}/li/ul/div
/templatescript setup langts nameChild2
import {ref} from vue//数据
let book ref(6)
let toy ref(水枪)/scriptstyle scoped.Child2{background-color: rgb(128, 132, 31);margin-top: 10px;padding: 10px;color:white}
/style运行效果如下 6.2$refs实现父传子通信
需要先了解标签的ref属性的基本知识ref用在普通DOM标签上获取的是DOM节点ref用在组件标签上获取的是组件实例对象。
了解上面的基础知识后要在父组件中创建c1和c2用来存储ref标记的内容
//创建c1和c2用于存储ref标记的内容
let c1 ref()
let c2 ref()在CHild1和Ch2组件标签上添加ref属性
Child1 refc1/
Child2 refc2/在Child1和Child2的组件内需要添加以下代码用来把数据交出去
//把数据交出去
defineExpose({book,toy})此时在父组件中已经拿到了子组件中的数据可以对这些数据进行操作如下代码定义一个函数用来改变子组件1中的toy的值
function changeC1Toy(){c1.value.toy 积木
}在父组件创建按钮并绑定click事件用来触发 changeC1Toy函数
button clickchangeC1Toy修改子组件1中的玩具/button运行后效果如下 $refs可以在父组件中获取所有的用ref标记的子组件的实例对象如果没有用ref标记则获取不到例如再增加一个子组件Child3代码如下
templatediv classChild3h3这是子组件3/h3ulli书籍{{ book }} 本/lili玩具{{ toy }}/li/ul/div/templatescript setup langts nameChild3
import {ref} from vue//数据
let book ref(30)
let toy ref(毛绒玩具)//把数据交出去
defineExpose({book,toy})
/scriptstyle scoped.Child3{background-color: rgb(120, 148, 114);margin: 10px 0;padding: 10px;color: white;}
/style在父组件中引入子组件3
import Child3 from ./Child3.vue在页面呈现但是不添加ref属性
Child3 /接下来给父组件创建一个按钮并绑定click事件触发changeAllBook函数并传入$refs
button clickchangeAllBook($refs)修改子组件的书籍数量/buttonchangeAllBook的函数代码如下
function changeAllBook(refs:any){console.log(refs)for (let key in refs){refs[key].book 1}
}运行后点击按钮控制台打印的内容如下 可以看到$refs是一个响应式的对象对象内是c1和c2没有子组件3的实例对象。通过遍历把c1和c2中的book增加1运行效果如下图 以上通过操控父组件的按钮实现改变子组件中书籍的数量这便是父传子通信的一种。
6.3$parent实现子传父通信
$parent的用法与$refs用法类似$parent获取的是父组件的实例对象如下在子组件1中添加一个按钮并绑定单击事件触发minusMoney方法实现减少父组件中的存款
button clickminusMoney($parent)减少父组件存款/buttonminusMoney的代码如下
function minusMoney(parent:any){ parent.money - 1
}父组件需要写个宏函数把数据交出去
//将数据交出去
defineExpose({money})至此已经完成了子传父的通信点击子组件中的按钮可以对父组件中的数据进行操控如下图 6.4小结
以上便是$refs和$parent实现父子间通信的用法小结如下
**$refs**用来获取所有用ref标记的子组件的实例对象得到的是响应式对象数据类型不能获取没有用ref标记的子组件实例对象。
**$parent**用来获取父组件的实例对象。
注意组件中需要用宏函数defineExpose()把数据交出去不然获取不到数据。
以下是完整代码
父组件
templatediv classFatherdiv idd1h3这是父组件/h3存款{{ money }} 万元/divbutton clickchangeC1Toy修改子组件1中的玩具/buttonbutton clickchangeAllBook($refs)修改子组件的书籍数量/button!-- 组件标签的ref属性获取的是组件的实例对象 --Child1 refc1/Child2 refc2/Child3 //div
/templatescript setup langts nameFather
import Child1 from ./Child1.vue
import Child2 from ./Child2.vue
import Child3 from ./Child3.vue
import {ref} from vue//数据
let money ref(100)//创建c1和c2用于存储ref标记的内容
let c1 ref()
let c2 ref()//方法
function changeC1Toy(){c1.value.toy 积木
}function changeAllBook(refs:any){// console.log(refs)for (let key in refs){refs[key].book 1}
}//将数据交出去
defineExpose({money})
/scriptstyle scoped.Father{background-color: rgb(155, 162, 168);padding: 10px;margin: 10px;}#d1{margin-left: 10px;}
/style子组件1
templatediv classChild1h3这是子组件1/h3ulli书籍{{ book }} 本/lili玩具{{ toy }}/li/ulbutton clickminusMoney($parent)减少父组件存款/button/div
/templatescript setup langts nameChild1
import {ref} from vue//数据
let book ref(10)
let toy ref(滑板车)//方法
function minusMoney(parent:any){ parent.money - 1
}//把数据交出去
defineExpose({book,toy})
/scriptstyle scoped.Child1{background-color: rgb(132, 114, 148);margin: 10px 0;padding: 10px;color: white;}button{color: #000;}
/style子组件2
templatediv classChild2h3这是子组件2/h3ulli书籍{{ book }} 本/lili玩具{{ toy }}/li/ul/div
/templatescript setup langts nameChild2
import {ref} from vue//数据
let book ref(6)
let toy ref(水枪)//把数据交出去
defineExpose({book,toy})
/scriptstyle scoped.Child2{background-color: rgb(128, 132, 31);margin-top: 10px;padding: 10px;color:white}
/style7.provide和inject
provide和inject用于当前组件向其后代组件直接通信需要先在祖先组件中通过provide配置向后代组件提供数据然后在后代组件中通过inject配置声明接收数据。
7.1准备三个组件
先准备三个组件分别为父组件、子组件和孙组件具体代码如下
父组件代码
templatediv classfatherh3父组件/h3h4用户订单详情/h4ulli订单编号{{ oderDetail.id }}/lili订单用户{{ oderDetail.username }}/lili订单商品{{ oderDetail.goods }}/lili订单价格{{ oderDetail.price }}/li/ulChild//div
/templatescript langts setup nameFather
import Child from ./Child.vue
import {reactive} from vuelet oderDetail reactive({id:abc01,username:xiaopeng,goods:神仙水,price:998
})
/scriptstyle scoped.father{background-color: rgb(112, 150, 66);margin: 10px;padding: 10px;border-radius: 5px;}
/style子组件代码
templatediv classchildh3子组件/h3GrandChild//div
/templatescript langts setup nameChildimport GrandChild from ./GrandChild.vue/scriptstyle scoped.child{background-color: burlywood;margin: 10px;padding: 10px;border-radius: 10px;}
/style孙组件代码
templatediv classgrandChildh3这是孙组件/h3/div
/templatescript langts setup nameGrandChild/scriptstyle scoped.grandChild{background-color: aqua;margin: 10px;padding:10px;border-radius: 5px;}
/style运行效果如下 7.2 祖传孙通信的实现
在父组件中引入provide,并提供数据
import {reactive,provide} from vue//提供数据
provide(oderDetail,oderDetail)注意此处第一个参数的名字可以是任意的第二个参数为数据如果是多个数据可以为对象格式的数据。
在后代组件中以孙组件为例引入inject,并注入数据
import { inject } from vue;//注入数据
let oderDetail inject(oderDetail)注意inject中的第一个参数必须与祖组件中provide中第一个参数相同第二个参数为默认值即当父组件中没有传递oderDetail那么孙组件中的oderDetail的值就是procide中的第二个参数。
孙组件中在页面中呈现
h4父组件传递过来的用户订单详情/h4ulli订单编号{{ oderDetail.id }}/lili订单用户{{ oderDetail.username }}/lili订单商品{{ oderDetail.goods }}/lili订单价格{{ oderDetail.price }}/li/ul注意上面代码中VS Code进行TS检查会提示oderDetail有错误但是实际不影响页面呈现运行结果如下 接下来需要处理ts的问题ts显示oderDetail类型为未知那么可以在定义oderDetail的时候给个默认值如下代码
let oderDetail inject(oderDetail,{id:,username:,goods:,price:0})此时VS Code就不再报错并且运行结果一样。
7.3 孙传祖通信的实现
provide和inject也可以实现孙传祖通信需要在父组件中定义一个函数并传递给后代
//方法
function discount(value:number){oderDetail.price oderDetail.price * value/10
}
//提供方法
provide(discount,discount)孙组件中接收方法
let discount inject(discount,(value:number){})孙组件中添加按钮并绑定单击事件触发接收的方法并传递参数
button clickdiscount(7)父组件中的订单价格打7折/button运行后单击按钮可以实现更改父组件中的价格由于孙组件中接收的数据为相应式的因此更改父组件中的价格孙组件中的订单价格也会相应变化如下图所示 至此以及实现了孙传祖通信。
不过上面代码还可以简化之前提到过provide第二个参数如果是多个数据可以为对象格式的数据,这样就可以把数据和对象同时传递和接收如下代码所示
//提供数据和方法
provide(oderContent,{oderDetail,discount})let {oderDetail,discount} inject(oderContent,{oderDetail:{id:,username:,goods:,price:0},discount:(value:number){}})这样运行的结果是完全一样的。
7.4 小结
provide和inject用于当前组件向其后代组件直接通信需要先在祖先组件中通过provide配置向后代组件提供数据然后在后代组件中通过inject配置声明接收数据。这个过程是完全不打扰中间的子组件实现的是祖孙间的直接通信。
下面是完整代码
父组件
templatediv classfatherh3父组件/h3h4用户订单详情/h4ulli订单编号{{ oderDetail.id }}/lili订单用户{{ oderDetail.username }}/lili订单商品{{ oderDetail.goods }}/lili订单价格{{ oderDetail.price }}/li/ulChild//div
/templatescript langts setup nameFather
import Child from ./Child.vue
import {reactive,provide} from vue//数据
let oderDetail reactive({id:abc01,username:xiaopeng,goods:神仙水,price:998
})//方法
function discount(value:number){oderDetail.price oderDetail.price * value/10
}
// //提供数据
// provide(oderDetail,oderDetail)
// 提供方法
// provide(discount,discount)//提供数据和方法
provide(oderContent,{oderDetail,discount})/scriptstyle scoped.father{background-color: rgb(112, 150, 66);margin: 10px;padding: 10px;border-radius: 5px;}
/style子组件
templatediv classchildh3子组件/h3GrandChild//div
/templatescript langts setup nameChildimport GrandChild from ./GrandChild.vue/scriptstyle scoped.child{background-color: burlywood;margin: 10px;padding: 10px;border-radius: 10px;}
/style孙组件
templatediv classgrandChildh3这是孙组件/h3h4父组件传递过来的用户订单详情/h4ulli订单编号{{ oderDetail.id }}/lili订单用户{{ oderDetail.username }}/lili订单商品{{ oderDetail.goods }}/lili订单价格{{ oderDetail.price }}/li/ulbutton clickdiscount(7)父组件中的订单价格打7折/button/div
/templatescript langts setup nameGrandChild
import { inject } from vue;// // 注入数据
// let oderDetail inject(oderDetail,{id:,username:,goods:,price:0})
// let discount inject(discount,(value:number){})let {oderDetail,discount} inject(oderContent,{oderDetail:{id:,username:,goods:,price:0},discount:(value:number){}})/scriptstyle scoped.grandChild{background-color: aqua;margin: 10px;padding:10px;border-radius: 5px;}
/style8.pinia
pinia是一个集中式状态管理工具是符合直觉的集中式状态管理工具。
8.1准备组件
首先准备3个组件父组件代码如下
templatediv classfatherHeader/Content//div
/templatescript setup langts nameIndex
import Header from ./Header.vue;
import Content from ./Content.vue;/scriptstyle scoped.father{height: 300px;width: 800px;background-color: rgb(169, 169, 169);margin: 5px;padding: 0;}
/styleHeader组件代码如下
templatediv classheaderimg classthoto :srcuser.thoto alt头像span{{ user.name }}/span/div
/templatescript setup langts nameHeader
import {ref,reactive} from vuelet user reactive({id:m0_63165331,name:m0_63165331,thoto:https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg
})
/scriptstyle scoped.header{height: 50px;background-color: #3b818c;border-bottom: 1px solid rgb(255, 255, 255);overflow: hidden;font-size: 0;text-align: center;line-height: 50px;}span{font-size: 16px;vertical-align: middle;color: #fff;}.thoto{height: 35px;border-radius: 50%;vertical-align: middle;margin-right: 5px;}
/styleContent组件代码如下
templatediv classouterdiv classuser img classthoto :srcuser.thoto alt头像 brdiv classuser-infspan账号{{ user.id }}/span brspan昵称{{ user.name }}/spanbrspan性别{{ user.gender }}/spanbrspan排名{{ user.rank }}/span brspan粉丝{{ user.fans }}/span /div /divdiv classcontenttextarea namemsg 评论内容/textarea/div/div
/templatescript setup langts nameContent
import {ref,reactive} from vuelet user reactive({id:m0_63165331,name:m0_63165331,gender:男,rank:19102,fans:1040,thoto:https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg
})
/scriptstyle scoped.outer{height: 250px;overflow: hidden;}.user{height: 240px;width: 150px;margin: 5px;background-color: #c6e6e8;text-align: center;float: left;border-radius: 5px;box-shadow: 0 0 5px black;}.user-inf{width: 150px;text-align: left;padding: 0 20px;}span{font-size: 16px;color: #000;font-size: 10px;font-family: 微软雅黑;font-weight: 600; }.thoto{height: 50px;border-radius: 40%;margin: 10px;}.content{width:630px;height: 240px;/* background-color: #c7d2d4; */float: right;margin: 5px; }textarea{width:630px;height: 240px;background-color: #c6e6e8;border: none;padding: 5px;border-radius: 5px;box-shadow: 0 0 5px black;}
/style呈现效果如下 8.2 搭建pinia环境
第一步先安装pinia
npm indtall pinia第二步在src/main.ts中引入createPinia并创建pinia,然后再安装pinia插件如下代码
import {createApp} from vue
import App from ./App.vue
//1.引入createPinia用于创建pinia
import {createPinia} from pinia// 创建应用
const app createApp(App)// 2.创建pinia
const pinia createPinia()// 3.安装pinia插件
app.use(pinia)// 挂载应用
app.mount(#app)此时我们已经创建了pinia环境在浏览器开发者工具中能看到pinia 8.3存储和读取数据
pinia是一个轻量化的状态管理工具一般把组件中共用的数据存储在store中不适合把所有的数据都用pinia来管理。前面创建的例子中user数据是共用的可以放入到store中。
Store是一个保存状态、业务逻辑 的实体每个组件都可以读取、写入它。
它有三个概念state、getter、action相当于组件中的 data、 computed 和 methods。
在src文件夹下新建stoer文件夹然后在文件夹下新建文件user.ts。这个文件用于存储user相关的数据、逻辑。
在user.ts文件中先//引入defineStore,用于创建store。
//引入defineStore,用于创建store
import {defineStore} from piniastore的变量名建议以use开头Srore结尾的大驼峰式命名。如下代码创建useUserStore并暴露。defineStore接收两个参数第一个参数是id值官方建议与文件名保持一致第二个参数有两种写法第一种是选项式写法参数是配置对象第二种是组合式写法是函数。
如下代码是选项式写法它有一个state配置项state必须写成函数内部return一个对象对象内部写具体的数据如下
export const useUserStore defineStore(user,{//状态state() {return {user:{id:m0_63165331,name:m0_63165331,gender:男,rank:19102,fans:1040,thoto:https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg}}},
})这样写完就拥有了一个用于存储user相关内容的仓库接下来需要在组件中引入仓库引入后可直接调用便可得到对应的store如下代码:
// 引入对应的useXxxxxStore
import { useUserStore } from ../store/user;
// 调用useXxxxxStore得到对应的store
const userStore useUserStore()那个这个userStore具体是什么可以在控制台打印输出得到如下内容 从上面控制台打印的信息可以得到userStore是个响应式对象它身上有user对象还有 s t a t e state statestate身上也有user对象说明有两种方式可以拿到user对象的数据如下
console.log(userStore.user)
console.log(userStore.$state.user)控制台结果如下 可以看到两种拿数据的方法得到的结果是完全一样的所以我们使用简单的方式来拿数据在页面中展示用如下代码
div classuser img classthoto :srcuserStore.user.thoto alt头像 brdiv classuser-infspan账号{{ userStore.user.id }}/span brspan昵称{{ userStore.user.name }}/spanbrspan性别{{ userStore.user.gender }}/spanbrspan排名{{ userStore.user.rank }}/span brspan粉丝{{ userStore.user.fans }}/span /div
/div运行后结果跟没有用pinia之前是完全一样的不再附图。
8.4 修改数据
pinia修改数据有三种方式。
**第一种修改方式**是符合直觉的方式拿到数据后就直接修改如下代码先创建一个方法用来更改粉丝数量
function addFans(){userStore.user.fans 1
}然后在组件中添加按钮并绑定事件
button clickaddFans粉丝数量1/button运行后效果如下 **第二种修改方式**是批量修改方式用$path它只需要把要修改的数据传递即可如下代码
function changeInf(){userStore.$patch( {user : {name:一叶知秋,rank:5000,fans:10000,}})
}添加按钮并绑定事件如下
button clickchangeInf更改数据/button运行效果如下 **第三种修改方式**借助pinia的action修改。在user.ts文件中的defineStore第二个参数中增加如下配置项
//动作
actions:{changeRankAddFans(){if (this.user.fans10000){this.user.fans 1000};if (this.user.rank10000){this.user.rank - 1000}}
},在组件中引入过userStore后可以直接调用如下代码添加按钮并绑定事件
button clickuserStore.changeRankAddFans粉丝数量1000,排名往前1000/button运行后如下效果 8.5 getters
pinia中的getters类似计算属性如下代码所示在user.ts中添加getters配置项
getters:{changeRank(state){if (state.user.rank 10000){return Math.floor(state.user.rank / 10000) 万}else{return state.user.rank}}
}在组件页面中调整显示内容如下
span排名{{ userStore.changeRank }}/span 运行后效果如下排名超过1万时显示1万在1万名以内显示具体数字 8.6 $subscribe
pinia中的$subscribe可以对pinia仓库中的数据进行监视与watch类似如下代码
userStore.$subscribe((){console.log(userStore中的数据发生变化了)
})$subscribe是一个函数需要调用调用的同时要传入一个函数可以用箭头函数。上面代码运行后当数据发生变化的时候这句话就会打印如下图示意 $subscribe函数传入的函数中可以接收两个参数第一个参数是数据变化的信息第二个参数是变化后的数据。如下代码示意
userStore.$subscribe((mutate,state){console.log(userStore中的数据发生变化了)console.log(mutate)console.log(state)
})8.7 store组合式写法
前面8.3小节提到过store有两种写法一种是选项式一种是组合式前面演示的均为选项式下面代码为组合式写法。
export const useUserStore defineStore(user,(){let user reactive({id:m0_63165331,name:m0_63165331,gender:男,rank:19102,fans:1040,thoto:https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg})function changeRankAddFans(){if (user.fans10000){user.fans 1000};if (user.rank10000){user.rank - 1000}}let changeRank computed((){if (user.rank 10000){return Math.floor(user.rank / 10000) 万}else{return user.rank}})return {user,changeRankAddFans,changeRank}
})8.8 小结
pinia是符合直接的vue.js的状态管理工具。
Store是一个保存状态、业务逻辑 的实体每个组件都可以读取、写入它。
它有三个概念state、getter、action相当于组件中的 data、 computed 和 methods。
它可以按照选项式风格来编写代码也可以按照组合式风格来编写代码。
以下是完整代码
src/main.ts中代码如下
import {createApp} from vue
import App from ./App.vue//引入createPinia用于创建pinia
import {createPinia} from pinia// 创建应用
const app createApp(App)// 创建pinia
const pinia createPinia()// 安装pinia插件
app.use(pinia)// 挂载应用
app.mount(#app)src/store/user.ts中代码如下
//引入defineStore,用于创建store
import {defineStore} from pinia
import {reactive,computed} from vue//创建seUserStore并暴露
//选项式写法
// export const useUserStore defineStore(user,{
// //状态
// state() {
// return {
// user:{
// id:m0_63165331,
// name:m0_63165331,
// gender:男,
// rank:19102,
// fans:1040,
// thoto:https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg
// }
// }
// },
// //动作
// actions:{
// changeRankAddFans(){
// if (this.user.fans10000){
// this.user.fans 1000
// };
// if (this.user.rank10000){
// this.user.rank - 1000
// }
// }
// },
// //计算
// getters:{
// changeRank(state){
// if (state.user.rank 10000){
// return Math.floor(state.user.rank / 10000) 万
// }else{
// return state.user.rank
// }
// }
// }// })//组合式写法
export const useUserStore defineStore(user,(){let user reactive({id:m0_63165331,name:m0_63165331,gender:男,rank:19102,fans:1040,thoto:https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406111305778.jpg})function changeRankAddFans(){if (user.fans10000){user.fans 1000};if (user.rank10000){user.rank - 1000}}let changeRank computed((){if (user.rank 10000){return Math.floor(user.rank / 10000) 万}else{return user.rank}})return {user,changeRankAddFans,changeRank}
})Father组件中代码
templatediv classfatherHeader/Content//div
/templatescript setup langts nameIndex
import Header from ./Header.vue;
import Content from ./Content.vue;/scriptstyle scoped.father{height: 300px;width: 800px;background-color: rgb(169, 169, 169);margin: 5px;padding: 0;}
/styleHeader组件代码
templatediv classheaderimg classthoto :srcuserStore.user.thoto alt头像span{{ userStore.user.name }}/span/div
/templatescript setup langts nameHeader
// 引入对应的useXxxxxStore
import { useUserStore } from ../store/user;
// 调用useXxxxxStore得到对应的store
const userStore useUserStore()/scriptstyle scoped.header{height: 50px;background-color: #3b818c;border-bottom: 1px solid rgb(255, 255, 255);overflow: hidden;font-size: 0;text-align: center;line-height: 50px;}span{font-size: 16px;vertical-align: middle;color: #fff;}.thoto{height: 35px;border-radius: 50%;vertical-align: middle;margin-right: 5px;}
/styleContent组件
templatediv classouterdiv classuser img classthoto :srcuserStore.user.thoto alt头像 brdiv classuser-infspan账号{{ userStore.user.id }}/span brspan昵称{{ userStore.user.name }}/spanbrspan性别{{ userStore.user.gender }}/spanbrspan排名{{ userStore.changeRank }}/span brspan粉丝{{ userStore.user.fans }}/span /div /divdiv classcontenttextarea namemsg 评论内容/textareabutton clickaddFans粉丝数量1/buttonbutton clickchangeInf更改数据/buttonbutton clickuserStore.changeRankAddFans粉丝数量1000,排名往前1000/button/div/div
/templatescript setup langts nameContent
import {ref,reactive,toRefs} from vue
// 引入对应的useXxxxxStore
import { useUserStore } from ../../store/user;
// 调用useXxxxxStore得到对应的store
const userStore useUserStore()userStore.$subscribe((mutate,state){console.log(userStore中的数据发生变化了)console.log(mutate)console.log(state)
})//修改数据第一种方式
function addFans(){userStore.user.fans 1
}//修改数据第二种方式
function changeInf(){userStore.$patch( {user : {name:一叶知秋,rank:5000,fans:10000,}})
}/scriptstyle scoped.outer{height: 250px;overflow: hidden;}.user{height: 240px;width: 150px;margin: 5px;background-color: #c6e6e8;text-align: center;float: left;border-radius: 5px;box-shadow: 0 0 5px black;}.user-inf{width: 150px;text-align: left;padding: 0 20px;}span{font-size: 16px;color: #000;font-size: 10px;font-family: 微软雅黑;font-weight: 600; }.thoto{height: 50px;border-radius: 40%;margin: 10px;}.content{width:630px;height: 240px;/* background-color: #c7d2d4; */float: right;margin: 5px; }textarea{width:630px;height: 200px;background-color: #c6e6e8;border: none;padding: 5px;border-radius: 5px;box-shadow: 0 0 5px black;}button{background-color: #c6e6e8;height: 30px;margin-top: 5px;margin-right: 10px;border-radius: 5px;padding: 5px;box-shadow: 0 0 5px black;border: none;transition: box-shadow 0.3s;}button:hover{box-shadow: 0 0 8px rgb(132, 132, 132);}button:active{box-shadow: 0 0 10px rgb(255, 255, 255);}
/style9. 插槽
插槽分为三种默认插槽具名插槽作用域插槽。
9.1默认插槽
先准备两个组件一个父组件一个是子组件Category组件父组件中的代码如下
templatediv classfatherdiv classcontent!-- 组件可以复用 --Category title热门游戏列表/ Category title今日美食推荐/ Category title今日影视推荐/ /div/div
/templatescript setup langts nameFather
import Category from ./Category.vue
import {ref,reactive } from vue
//游戏列表数据
let games reactive([{id:afsdf01,name:王者荣耀},{id:afsdf02,name:和平精英},{id:afsdf03,name:我的世界},{id:afsdf04,name:原神}
])
//图片url
let imgUrl ref(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406161328882.gif)
//电影url
let movieUrl ref(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406161519334.mp4)
/scriptstyle scoped.father{width: 800px;height: 400px;background-image: url(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406161029992.gif);background-size: cover;padding: 20px; }.content{margin-top: 30px;display: flex;justify-content: space-evenly;}
/style子组件Category中的代码如下
templatediv classcategoryh2{{title}}/h2/div
/templatescript setup langts nameCategory//接收propsdefineProps([title])
/scriptstyle scoped.category{height: 300px;width: 200px;padding: 10px;background-color:rgba(255, 255, 255, 0.1);border-radius: 5px;border: 1px solid white;box-shadow: 0 0 5px white;color: #fff;transition: box-shadow 0.3s,transform 0.5s;}.category:hover{box-shadow: 0 0 10px white;box-shadow: 0 0 20px white;transform:translateY(-5px)}h2{text-align: center;border-bottom: 1px solid white;font-size: 18px;font-weight: 800;}
/style以上代码是把子组件复用三次并利用props传递title属性然后在子组件中接收props并在页面呈现本次写一些CSS样式效果如下 接下来需要把父组件中的游戏列表、图片、视频分别呈现在子组件中。
首先要在子组件中写slot标签用来站位标签中夹着的内容为默认内容如果父组件没有传递内容则会显示默认内容如果父组件传递内容则显示传递的内容。如下代码
slot这是默认内容/slot此时页面呈现效果如下 在父组件中首先要把组件标签由单标签改成双标签如下代码
div classcontent!-- 组件可以复用 --Category title热门游戏列表/CategoryCategory title今日美食推荐/CategoryCategory title今日影视推荐/Category
/div然后在两个标签中添加页面元素添加的内容便会呈现在子组件插槽的位置如下代码
div classcontent!-- 组件可以复用 --Category title热门游戏列表ulli v-forg in games :keyg.id{{ g.name }}/li/ul/CategoryCategory title今日美食推荐div classslotimg :srcimgUrl alt/div/CategoryCategory title今日影视推荐div classslotvideo :srcmovieUrl controls/video/div/Category
/div再给一些样式
.slot{height: 240px;width: 180px;opacity:0.2;transition:opacity 0.3s
}
.slot:hover{opacity:1
}
img,video{text-align: center;width: 100%;
}最终页面呈现的效果如下 以上便是默认插槽的用法。
以下是完整代码 父组件
templatediv classfatherdiv classcontent!-- 组件可以复用 --Category title热门游戏列表ulli v-forg in games :keyg.id{{ g.name }}/li/ul/CategoryCategory title今日美食推荐div classslotimg :srcimgUrl alt/div/CategoryCategory title今日影视推荐div classslotvideo :srcmovieUrl controls/video/div/Category/div/div
/templatescript setup langts nameFather
import Category from ./Category.vue
import {ref,reactive } from vue
//游戏列表数据
let games reactive([{id:afsdf01,name:王者荣耀},{id:afsdf02,name:和平精英},{id:afsdf03,name:我的世界},{id:afsdf04,name:原神}
])
//图片url
let imgUrl ref(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406161328882.gif)
//电影url
let movieUrl ref(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406161519334.mp4)
/scriptstyle scoped.father{width: 800px;height: 400px;background-image: url(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406161029992.gif);background-size: cover;padding: 20px; }.content{margin-top: 30px;display: flex;justify-content: space-evenly;} .slot{height: 240px;width: 180px;opacity:0.2;transition:opacity 0.3s}.slot:hover{opacity:1} img,video{text-align: center;width: 100%;}
/style子组件
templatediv classcategoryh2{{title}}/h2!-- 插槽 --slot这是默认内容/slot/div
/templatescript setup langts nameCategory//接收propsdefineProps([title])
/scriptstyle scoped.category{height: 300px;width: 200px;padding: 10px;background-color:rgba(255, 255, 255, 0.1);border-radius: 5px;border: 1px solid white;box-shadow: 0 0 5px white;color: #ffffff;transition: box-shadow 0.3s,transform 0.5s;}.category:hover{box-shadow: 0 0 10px white;box-shadow: 0 0 20px white;transform:translateY(-5px)}h2{text-align: center;border-bottom: 1px solid white;font-size: 18px;font-weight: 800;}
/style9.2 具名插槽
具名插槽顾名思义就是具有名称的插槽在前一小节中我们在使用插槽的时候没有指定名称为默认插槽。
使用具名插槽可以使用多个插槽前面小节中的title数据是用props传递的有了具名插槽就可以不使用props全采用插槽传递。子组件中代码改成如下
templatediv classcategory!-- 插槽1 --slot nametitle这是默认内容/slot!-- 插槽2 --slot namecontent这是默认内容/slot/div
/template父组件中需要传递的数据要用template标签包一下并添加v-slot属性。如下代码示意
templatediv classfatherdiv classcontent!-- 组件可以复用 --Category!-- v-slot后面是冒号冒号后面对应插槽名称 --template v-slot:titleh2热门游戏列表/h2/templatetemplate v-slot:contentulli v-forg in games :keyg.id{{ g.name }}/li/ul/template/CategoryCategorytemplate v-slot:titleh2今日美食推荐/h2/templatetemplate v-slot:contentdiv classslotimg :srcimgUrl alt/div/template/CategoryCategory title今日影视推荐template v-slot:titleh2今日影视推荐/h2/templatetemplate v-slot:contentdiv classslotvideo :srcmovieUrl controls/video/div/template/Category/div/div
/template注意由于不用props传递数据子组件中需要删除defineProps代码并且由于h2标签由原来的在子组件中挪到了父组件代码中了所以CSS样式也要同时粘贴过去。
注意v-slot:有个小的语法糖可以简写为#。
以上便是具名插槽的用法完整代码如下
父组件
templatediv classfatherdiv classcontent!-- 组件可以复用 --Category!-- v-slot后面是冒号冒号后面对应插槽名称 --template v-slot:titleh2热门游戏列表/h2/templatetemplate v-slot:contentulli v-forg in games :keyg.id{{ g.name }}/li/ul/template/CategoryCategory!-- v-slot:可以简写为# --template #titleh2今日美食推荐/h2/templatetemplate #contentdiv classslotimg :srcimgUrl alt/div/template/CategoryCategorytemplate #titleh2今日影视推荐/h2/templatetemplate #contentdiv classslotvideo :srcmovieUrl controls/video/div/template/Category/div/div
/templatescript setup langts nameFather
import Category from ./Category.vue
import {ref,reactive } from vue
//游戏列表数据
let games reactive([{id:afsdf01,name:王者荣耀},{id:afsdf02,name:和平精英},{id:afsdf03,name:我的世界},{id:afsdf04,name:原神}
])
//图片url
let imgUrl ref(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406161328882.gif)
//电影url
let movieUrl ref(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406161519334.mp4)
/scriptstyle scoped.father{width: 800px;height: 400px;background-image: url(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406161029992.gif);background-size: cover;padding: 20px; }.content{margin-top: 30px;display: flex;justify-content: space-evenly;} .slot{height: 240px;width: 180px;opacity:0.2;transition:opacity 0.3s}.slot:hover{opacity:1} img,video{text-align: center;width: 100%;}h2{text-align: center;border-bottom: 1px solid white;font-size: 18px;font-weight: 800;}
/style子组件
templatediv classcategory!-- 插槽1 --slot nametitle这是默认内容/slot!-- 插槽2 --slot namecontent这是默认内容/slot/div
/templatescript setup langts nameCategory/scriptstyle scoped.category{height: 300px;width: 200px;padding: 10px;background-color:rgba(255, 255, 255, 0.1);border-radius: 5px;border: 1px solid white;box-shadow: 0 0 5px white;color: #ffffff;transition: box-shadow 0.3s,transform 0.5s;}.category:hover{box-shadow: 0 0 10px white;box-shadow: 0 0 20px white;transform:translateY(-5px)}/style9.3 作用域插槽
作用域插槽与前面的默认插槽和具名插槽有很大的不同默认插槽和具名插槽都是用于父传子数据在父组件中。作用域插槽用于子传父数据在子组件中但是数据生成的结构由父组件决定。
如下代码在子组件中定义游戏列表数据但是数据的呈现方式在组件中可以是无序列表也可以是有序列表也可以是普通文本。
如下代码是子组件的数据
script setup langts nameGames
import {reactive } from vue
//游戏列表数据
let games reactive([{id:afsdf01,name:王者荣耀},{id:afsdf02,name:和平精英},{id:afsdf03,name:我的世界},{id:afsdf04,name:原神}
])
/script使用slot标签来传递数据此处用法与props用法相同也可以同时传递多个数据。
templatediv classgamesh2游戏列表/h2!-- 给slot组件传递props --slot :gamesgames/slot/div
/template在父组件中接收数据用v-slot“XXX”接收数据接收的数据是一个对象。
Games!-- v-slot用来接收props --template v-slotparamsulli v-forg in params.games :keyg.id{{ g.name }}/li/ul/template
/Games作用域插槽也可以用带有名称,如果插槽没有命名默认的名字为default包括前面小节的默认插槽它的名字也是default。
Games!-- default为插槽的名称未命名的插槽默认名称是default --template v-slot:defaultparamsolli v-forg in params.games :keyg.id{{ g.name }}/li/ol/template
/Gamesv-slot:也可以用简写的形式在UI组件库中会大量应用作用域插槽所以经常会看到#default
Games!-- #是 v-slot: 的语法糖--template #defaultparamsh4 v-forg in params.games :keyg.id{{ g.name }}/h4/template
/Games在接收数据的时候也可以解构赋值如下
Games!-- 在接收的时候进行了解构赋值--template #default{games}h5 v-forg in games :keyg.id{{ g.name }}/h5/template
/Games最终呈现的效果如下 完整代码如下
父组件
templatediv classfatherdiv classcontentGames!-- v-slot用来接收props --template v-slotparamsulli v-forg in params.games :keyg.id{{ g.name }}/li/ul/template/GamesGames!-- default为插槽的名称未命名的插槽默认名称是default --template v-slot:defaultparamsolli v-forg in params.games :keyg.id{{ g.name }}/li/ol/template/GamesGames!-- #是 v-slot: 的语法糖--template #defaultparamsh4 v-forg in params.games :keyg.id{{ g.name }}/h4/template/GamesGames!-- 在接收的时候进行了解构赋值--template #default{games}h5 v-forg in games :keyg.id{{ g.name }}/h5/template/Games/div/div
/templatescript setup langts nameFather
import Games from ./Games.vue;/scriptstyle scoped.father{width: 800px;height: 400px;background-image: url(https://xyyhxxx.oss-cn-beijing.aliyuncs.com/picGoImg/202406161029992.gif);background-size: cover;padding: 20px; }.content{margin-top: 30px;display: flex;justify-content: space-evenly;}
/style子组件
templatediv classgamesh2游戏列表/h2!-- 给slot组件传递props --slot :gamesgames/slot/div
/templatescript setup langts nameGames
import {reactive } from vue
//游戏列表数据
let games reactive([{id:afsdf01,name:王者荣耀},{id:afsdf02,name:和平精英},{id:afsdf03,name:我的世界},{id:afsdf04,name:原神}
])
/scriptstyle scoped
.games{height: 300px;width: 180px;padding: 10px;background-color:rgba(255, 255, 255, 0.1);border-radius: 5px;border: 1px solid white;box-shadow: 0 0 5px white;color: #fff;transition: box-shadow 0.3s,transform 0.5s;
}
.games:hover{box-shadow: 0 0 10px white;box-shadow: 0 0 20px white;transform:translateY(-5px)
}
h2{text-align: center;border-bottom: 1px solid white;font-size: 18px;font-weight: 800;
}
/style总结
以上便是vue3中常见的组件通信不同的通信方式应用场景不同。在使用的时候需要结合自己的习惯选用。