子组件向父组件数据传递
+ 技术实现:事件绑定,事件触发
+ 详细描述:在子组件标签上以事件绑定的方式定义父组件方法,在子组件对象中 以属性 emits 方式进行 绑定事件的拦截,
并在对应的执行区通过 $emit 实例方法进行触发,并传递事件源参数
+ 核心功能的实现是通过 方法定义(形参)+方法调用(实参) 完成
==> 事件绑定:借助事件绑定可以定义事件的规则,为子组件构建多个可以用于数据传递的特殊事件名称,
事件所对应的回调执行方法,可由父组件提供,并完成形参接收和赋值操作,而被绑定的回调方法
必然可以接收一个事件源对象参数 $event , 该参数为子组件调用时回传的数据值
==> 事件触发:子组件标签上绑定的自定义事件,在子组件解析时被记录于 $attrs 中,所以用过 $attrs可以直接访问;
也可以通过 emits 配置属性实现方法的拦截加载,加载后的事件可以作为 $emit 方法的调用参数使用,
并且$attrs将删除拦截后的事件
<div id="app">
<h4>父组件</h4>
<p>info(通过子组件传递赋值的):{{ info }}</p>
<p>text(通过子组件传递赋值的):{{ text }}</p>
<!-- v-bind v-on 在构建规则中只有第一个:表示参数,后续表达式中的:均为名称的一部分 -->
<comp-a v-on:update:text="updateText('定义在页面的参数')"></comp-a>
<hr>
<!--
子组件绑定的事件在定义时,不建议定义带()回调,因为带()回调可能会导致回传数据丢失
-->
<comp-a v-on:update:text="updateText"></comp-a>
<hr>
<!--
如果子组件绑定事件在定义时必须传递页面中构建的相关参数(例如循环的临时变量),可以以$event关键字代替子向父回传的数据
-->
<comp-a v-on:update:text="updateText($event,'定义在页面的参数')"></comp-a>
</div>
<script type="text/x-template" id="compA">
<div class="box">
<h4>子组件-CompA</h4>
<p>msg:{{ msg }}</p>
<input type="text" :value="msg" @input="setMsg($event.target.value)">
<hr>
<p>desc:{{ desc }}</p>
<input type="button" value="传递desc数据" @click="sendDesc()">
</div>
</script>
<script type="module">
import { createApp } from "../../assets/vue/3.0/vue.esm-browser.js";
const app = createApp({
data() {
return {
info:"",
text:""
}
},
methods: {
updateInfo(params){
console.log("父组件:",params);
this.info = params;
},
updateText(nv,arg){
console.log("父组件-updateText:",nv,arg);
this.text = nv;
},
updateText2(){}
},
})
app.component("CompA",{
template:"#compA",
data(){
return {
msg:"子组件数据变量-msg",
desc:"子组件数据变量-desc"
}
},
emits:["update:text"],
methods:{
setMsg(nv){
this.msg = nv;
this.$parent.updateInfo( this.msg )
},
sendDesc(){
console.log(this.$attrs);
// this.$attrs["onUpdate:text"]( this.desc )
// this.$emit(typeName,params)
// typeName 组件中被 emits 拦截的事件名
// params 是触发时的回调方法
// 同时该方法会在调试工具中产生日志
this.$emit("update:text",this.desc)
}
},
created() {
// vue每个实例都一个对应的取值的 $parent 属性,该属性是当前vue实例运行所在的父组件实例对象
console.log(this.$parent);
this.$parent.updateInfo( this.msg )
},
})
app.mount("#app")
</script>
vue2子传父
+ 技术实现:事件绑定,事件触发
+ 详细描述:在子组件标签上以事件绑定的方式定义父组件方法,在子组件对象中对应的执行区通过 $emit 触发
=> 子组件标签上绑定的自定义事件,在子组件解析时被记录于 this.$listeners 中 ,
$emit方法只会调用其中的事件,不做删除
<div id="app">
<h4>父组件</h4>
<p>info(通过子组件传递赋值的):{{ info }}</p>
<comp-a @update:msg="updateInfo"></comp-a>
</div>
<script type="text/x-template" id="compA">
<div class="box">
<h4>子组件-CompA</h4>
<p>msg:{{ msg }}</p>
<input type="text" :value="msg" @input="setMsg($event.target.value)">
</div>
</script>
<script type="module">
import Vue from "../../assets/vue/2.0/vue.esm.browser.js";
Vue.component("CompA",{
template:"#compA",
data(){
return {
msg:"子组件数据变量-msg",
}
},
methods:{
setMsg(nv){
this.$emit("update:msg",this.msg)
}
}
})
new Vue({
el:"#app",
data() {
return {
info:"",
}
},
methods: {
updateInfo(nv){
console.log("父组件方法updateInfo:",nv);
this.info = nv;
}
},
})
</script>
事件穿透
<!-- html的 on事件名 本质上就是 DOM的 一个特殊属性 -->
<!-- <input type="button" value="事件绑定" v-bind:οnclick="updateMsg"> -->
<!--
Vue3中子组件标签上定义没有被拦截的事件,将转为 “on事件名” 格式存储于$attrs 中,此时这些事件同时具有属性穿透功能
场景1:子组件有且仅有一个根元素
=> 未被拦截的事件将被自动的绑定于当前组件的根元素上(自动排除非w3c规范的事件属性)
场景2:子组件有多个根元素
=> 事件的传递和属性的传递一样,需要开发者自行描述绑定位置
-->
<div id="app">
<h4>父组件</h4>
<comp-a @update:msg="updateMsg" @click="updateMsg" @focus="updateMsg"></comp-a>
</div>
<script type="text/x-template" id="compA">
<div class="box" v-bind="$attrs">
<h4>子组件-CompA-1</h4>
<input type="text" @click.stop @focus="$emit('focus')">
</div>
<div class="box" v-bind="$attrs">
<h4>子组件-CompA-2</h4>
</div>
</script>
<script type="module">
import { createApp } from "../../assets/vue/3.0/vue.esm-browser.js";
createApp({
data(){
return {
}
},
methods: {
updateMsg(){
console.log("父组件:updateMsg")
}
},
})
.component("CompA",{
template:"#compA",
// emits:["update:msg"]
emits:["focus"]
})
.mount("#app")
</script>
非父子组件数据传递
1、借助共有顶级组件
vm.$root 用于执行当前vue程序运行时 createApp 构建启动根组件(ROOT组件)
==> 小范围可以使用,但不要大量使用
+ 根组件仓库臃肿
+ 组件数据独立性消失
+ 可能会产生副作用的响应式
<div id="app">
<h4>父组件</h4>
<p>msg: <span class="data">{{ msg }}</span> </p>
<hr>
<comp-a></comp-a>
<comp-b></comp-b>
</div>
<script type="text/x-template" id="compA">
<div class="box">
<h4>子组件-CompA</h4>
<p>msg: <span class="data">{{ $root.msg }}</span> </p>
</div>
</script>
<script type="text/x-template" id="compB">
<div class="box">
<h4>子组件-compB</h4>
<p>msg: <span class="data">{{ $root.msg }}</span> </p>
<input type="text" v-model="$root.msg">
</div>
</script>
<script type="module">
import { createApp } from "../../assets/vue/3.0/vue.esm-browser.js";
const CompA = {
template:"#compA",
methods: {
aaa(){
this.$root.$on("aaa",function(){})
}
},
}
const CompB = {
template:"#compB",
created() {
this.$root.msg = "组件B赋值的初始数据"
},
}
createApp({
components:{
CompA,CompB
},
data(){
return {
msg:""
}
}
})
.mount("#app")
</script>
2、中央事件总线(event bus)
<div id="app">
<h4>父组件</h4>
<hr>
<comp-b></comp-b>
<comp-a></comp-a>
</div>
<script type="text/x-template" id="compA">
<div class="box">
<h4>子组件-CompA</h4>
<p>msg: <span class="data">{{ msg }}</span> </p>
</div>
</script>
<script type="text/x-template" id="compB">
<div class="box">
<h4>子组件-compB</h4>
<p>msg: <span class="data">{{ msg }}</span> </p>
<input type="text" v-model="msg">
</div>
</script>
<script src="../../assets/miit/mitt.umd.js"></script>
<script type="module">
import { createApp } from "../../assets/vue/3.0/vue.esm-browser.js";
const eventBus = mitt();
console.log(eventBus);
const CompA = {
template:"#compA",
data() {
return {
msg:""
}
},
created() {
eventBus.on("update:msg",(nv)=>{
this.msg = nv;
})
},
}
const CompB = {
template:"#compB",
data(){
return {
msg:"组件B的msg"
}
},
watch:{
msg(){
eventBus.emit("update:msg",this.msg)
}
},
mounted() {
console.log("compB====>");
console.log(eventBus.all);
eventBus.emit("update:msg",this.msg)
console.log("<====compB");
},
}
createApp({
components:{
CompA,CompB
}
})
.mount("#app")
</script>
3、组件的生命周期执行顺序
单向数据流&组件双向数据共享
1、单向数据流
2、计算属性的双向共享操作
3、组件自定义v-model
Vue的v-model绑定在组件标签上和绑定在表单标签上功能一致,用于完成属性绑定+事件绑定
对组件标签而言:
属性绑定 针对的属性 是v-model指令 : 所描述的属性
事件绑定 v-model的:后面的变量名, 和固定关键字update:, 组成自定义事件名 update:变量名,
在vue组件标签上进行绑定
例如 <itany-test v-model:title="取值变量" >
==> <itany-test :title="取值变量" @update:title="取值变量=$event" >
v-model 在组件标签上进行定义时,也可以不用定义 :变量名 【注意:一个组件标签只能定义一个】
v-model无:参数定义在组件标签上,属于默认属性 modelValue 的双向操作
<div id="app">
<h4>父组件</h4>
<p>info:{{ info }}</p>
<input type="text" v-model="info">
<p>text:{{ text }}</p>
<input type="text" v-model="text">
<!-- v-model: 语法只在组件标签有效 vue3+ -->
<comp-a v-model:msg="info" v-model="text"></comp-a>
</div>
<script type="text/x-template" id="compA">
<div class="box">
<h5>组件</h5>
<p>msg:{{ msg }}</p>
<input type="text" v-model="argMsg">
<hr>
<p>modelValue:{{ modelValue }}</p>
<input type="text" v-model="argModelValue">
</div>
</script>
<script type="module">
import { createApp } from "../../assets/vue/3.0/vue.esm-browser.js";
createApp({
data(){
return {
info:"父组件数据info",
text:"父组件数据text"
}
}
})
.component("CompA",{
template:"#compA",
props:["msg","modelValue"],
emits:["update:msg","update:modelValue"],
computed:{
argMsg:{
get(){
return this.msg
},
set(nv){
this.$emit("update:msg",nv)
}
},
argModelValue:{
get(){
return this.modelValue
},
set(nv){
this.$emit("update:modelValue",nv)
}
}
}
})
.mount("#app")
</script>
4、JS引用类型的双向共享操作
实际开发中请小心使用
+ 基于JS引用地址指向的特性,可以在不该变props变量的情况下,修改引用地址指向的堆中数据,
但这类操作是违背单向数据流的
+ 在vue继承的UI组件库中,为了实现UI组件库的高效应用,UI组件库存在大量基于JS引用方式得回传数据