Previous notes - "Vue3 | Definition and reusability of components, local components, global components, components Intermediate value and verification, single data flow, Non-props attribute", the concept of one-way data flow,Parent and child components can communicate through events
Parent and child components can communicate through events
Carry Parameter event sending and listening callback
Use the component's
emis
section to organize component eventsUse the component
emis
Object
form to verify the passed parameter valueCombine
$emit
,v-bind
andv-model
to achieve parent-child component communication (two-way data binding)Combine
$emit
,v-bind
andv-model
to realize parent-child component communication (application cases of multiple fields)Set your own modifiers
##Experimentthis.modelModifiers
The role of
is in the click callback handleClick()
of the subcomponent, through
this.modelModifiers. [Set the modifier name by yourself]Implement the logic of setting the modifier by yourself
Slot【slot】【Example of passing component】
Note that events (modifiers) cannot be added directly to the slot tag. If necessary, you can wrap a tag outside the <slot>, and then Add event
slot【Example of passing string】
slot【 Example of setting up subcomponents by yourself】
Slot scope problem
Slot UI default value
Flexible splitting and application of slots [Named slots]
v-slot
Abbreviation of command
Commonv-for
Example for list rendering
v-for
combination
v-bind、
v-slot、
Do list rendering
Use deconstruction
concept for abbreviation
Dynamic components
Conventional use of two-way binding features
, switching UI writing methods through click events
How to write dynamic components
- ##Asynchronous components
means that child components cannot modify the data fields from the parent component,
If you really want to modify , you can communicate in the following way:
First, in the UI click callback method of the subcomponent, call
this.$emit('[Set the event name yourself]')
, Send an
event
to the outside world; Then parent components at all levels will receive this event,
then call the child component label in the parent component,
ends with
@[Event name] = "Callback method name" in the form of
, Listen to
the event and configure Callback method
; Callback method
You can modify the data fields of the parent component that the child component intends to modify;
Note thattrigger events are named using camel case
Code:
( As shown below heHeDa);To name the listening event, use the
horizontal bar interval method (as shown below he-he-da)
.
<!DOCTYPE html><html><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hello World! heheheheheheda</title> <script src="https://unpkg.com/vue@next"></script></head><body> <div id="heheApp"></div></body><script> const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent() { this.count += 1; } }, template: ` <div> <counter :count="count" @he-he-da="handleItemEvent"/> </div>` }); app.component('counter', { props: ['count'], methods: { handleItemClick() { this.$emit('heHeDa'); } }, template:` <div @click="handleItemClick">{{count}}</div> ` }); const vm = app.mount('#heheApp');</script></html>
Run, click on the component:
Event sending and listening callbacks with parameters
this.$emit()You can add parameter bits. In the listening callback of the parent component,
you can add formal parameter bits to receive parameters (such as
handleItemEvent(param )
in param
); Code:
<script> const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent(param) { this.count += param; } }, template: ` <div> <counter :count="count" @add-count="handleItemEvent"/> </div>` }); app.component('counter', { props: ['count'], methods: { handleItemClick() { this.$emit('addCount', 8); } }, template:` <div @click="handleItemClick">{{count}}</div> ` }); const vm = app.mount('#heheApp');</script>
The sub-component can also send multiple parameters, just add parameter bits as needed in this.$emit()
, In the listening callback of the parent component, Just add the corresponding formal parameters to receive them:
<script> const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent(param1, param2, param3) { this.count = this.count + param1 + param2 + param3; console.log(this.count); } }, template: ` <div> <counter :count="count" @add-count="handleItemEvent"/> </div>` }); app.component('counter', { props: ['count'], methods: { handleItemClick() { this.$emit('addCount', 8, 2, 6); } }, template:` <div @click="handleItemClick">{{count}}</div> ` }); const vm = app.mount('#heheApp');</script>
Of course, the calculation after the parent component receives the child component parameters Logic,
can be calculated when the sub-component passes parameters and then passed to this.$emit()
! When the parent component receives it, just accept the value directly (
handleItemEvent(count)
); <script> const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent(count) { this.count = count; console.log(this.count); } }, template: ` <div> <counter :count="count" @add-count="handleItemEvent"/> </div>` }); app.component('counter', { props: ['count'], methods: { handleItemClick() { this.$emit('addCount', this.count + 16); } }, template:` <div @click="handleItemClick">{{count}}</div> ` }); const vm = app.mount('#heheApp');</script>
emits
板块 整理组件事件实际开发场景中,我们一个组件自己设置的触发事件可能会很多,
我们不可能一个一个去梳理核实,
这个时候即可以使用 组件的emits
板块 来整理组件的事件;
可以把组件中 自己设置到的事件
都写在这里,方便梳理
,提高可读性
,
或者者把 想要定义的事件
写在这里,
如此一来,假如不记得
编写对应的自己设置事件,
Vue系统会在运行时 给予警告
:
<script> const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent(count) { this.count = count; console.log(this.count); } }, template: ` <div> <counter :count="count" @add-count="handleItemEvent"/> </div>` }); app.component('counter', { props: ['count'], emits: ['hehehe'], methods: { handleItemClick() { this.$emit('addCount', this.count + 16); } }, template:` <div @click="handleItemClick">{{count}}</div> ` }); const vm = app.mount('#heheApp');</script>
假如不记得
编写对应的自己设置事件,Vue系统会在运行时 给予警告
:
emits
板块的 Object
形式 校验外传的参数值可以根据需要,使用 组件emits
板块的 Object
形式 校验外传的参数值,
如下,子组件的emits
板块,
‘key’值定义对应的事件名,‘value’值定义一个校验函数,
返回true
表示同意数值外传,
返回false
表示不同意,会给出警告;
<script> const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent(count) { this.count = count; console.log(this.count); } }, template: ` <div> <counter :count="count" @add-count="handleItemEvent"/> </div>` }); app.component('counter', { props: ['count'], emits: { addCount: (count) => { if (count < 0) { return true; } return false; } }, methods: { handleItemClick() { this.$emit('addCount', this.count + 16); } }, template:` <div @click="handleItemClick">{{count}}</div> ` }); const vm = app.mount('#heheApp');</script>
运行,点击效果:
$emit
、v-bind
与v-model
实现 父子组件通信(数据双向绑定)v-model可以实现数据字段与DOM节点内容的双向绑定,
也可以实现数据字段与数据字段之间的双向绑定;
而v-bind
只能是实现单向数据流
;
若不自己设置承接的字段名
,则需要用modelValue
作为默认的承接字段名
;
同时,$emit()
的一参默认为update:modelValue
,二参为绑定的数据;
如下代码,
子组件 的承接变量modelValue
同父组件的count
字段 双向绑定,
(实际上就是v-model
的特性 —— 将 子组件的内容即modelValue
同 父组件的数据字段
双向绑定)
而后显示在子组件的DOM中({{modelValue}}
):
<script> const app = Vue.createApp({ data() { return {count: 1} }, template: ` <counter v-model="count"/>` }); app.component('counter', { props: ['modelValue'], methods: { handleItemClick() { this.$emit('update:modelValue', this.modelValue + 16); console.log(vm.$data.count); } }, template:` <div @click="handleItemClick">{{modelValue}}</div> ` }); const vm = app.mount('#heheApp');</script>
效果:
当然也可以自己设置字段名
,
这种方式需要给v-model
字段接一个字段名,
同时将这个字段名替代子组件中所有modelValue
的位置:
<script> const app = Vue.createApp({ data() { return {count: 1} }, template: ` <counter v-model:testField="count"/>` }); app.component('counter', { props: ['testField'], methods: { handleItemClick() { this.$emit('update:testField', this.testField + 16); console.log(vm.$data.count); } }, template:` <div @click="handleItemClick">{{testField}}</div> ` }); const vm = app.mount('#heheApp');</script>
实现效果与上例相同;
$emit
、v-bind
与v-model
实现 父子组件通信(多个字段的应用案例)如下代码,
父组件的count
与子组件承接的testField
字段,
父组件的count1
与子组件承接的testField1
字段,
分别实现了双向绑定:
<script> const app = Vue.createApp({ data() { return { count: 1, count1: 1 } }, template: ` <counter v-model:testField="count" v-model:testField1="count1"/>` }); app.component('counter', { props: ['testField','testField1'], methods: { handleItemClick() { this.$emit('update:testField', this.testField + 16); console.log("vm.$data.count", vm.$data.count); }, handleItemClick1() { this.$emit('update:testField1', this.testField1 + 8); console.log("vm.$data.count1", vm.$data.count1); } }, template:` <div @click="handleItemClick">{{testField}}</div> <div @click="handleItemClick1">{{testField1}}</div> ` }); const vm = app.mount('#heheApp');</script>
效果:
机制:在父组件调用处,在
v-model
后 使用自己设置修饰符
,
在实现修饰符逻辑
的地方,如点击事件中,
通过this.modelModifiers.[自己设置修饰符名]
返回的布尔值
,
判断客户能否使用了修饰符,
进而分别对使用与否做相应的解决;
另外'modelModifiers'
板块中可以指定默认值
(下代码指定为一个空对象{}
);
this.modelModifiers
的作用首先下面是一个空的解决,'modelModifiers'
板块中指定默认值
(下代码指定为一个空对象{}
),mounted
函数中打印 子组件modelModifiers
属性的内容,
代码如下,
运行后,可以见打印了一个对象{captalize: true}
,
正是我们传入的自己设置修饰符.captalize
(这里未做解决)
【假如这里v-model
不接修饰符,console.log(this.modelModifiers);
将打印一个空对象{}
】:
<script> const app = Vue.createApp({ data() { return { char: 'a' } }, template: ` <counter v-model.captalize="char"/>` }); app.component('counter', { props: { 'modelValue': String, 'modelModifiers': { default: () => ({}) } }, mounted() { console.log(this.modelModifiers); }, methods: { handleClick() { this.$emit('update:modelValue', this.modelValue + 'h'); console.log("vm.$data.count", vm.$data.char); } }, template:` <div @click="handleClick">{{modelValue}}</div> ` }); const vm = app.mount('#heheApp');</script>
handleClick()
中,通过this.modelModifiers.[自己设置修饰符名]
实现自己设置修饰符逻辑实现效果即 点击之后使得对应的字符串 全变大写;
<script> const app = Vue.createApp({ data() { return { testString: 'a' } }, template: ` <counter v-model.heheda="testString"/>` }); app.component('counter', { props: { 'modelValue': String, 'modelModifiers': { default: () => ({}) } }, mounted() { console.log(this.modelModifiers); }, methods: { handleClick() { let newValue = this.modelValue + 'h'; if(this.modelModifiers.heheda) { newValue = newValue.toUpperCase(); } this.$emit('update:modelValue', newValue); console.log("vm.$data.count", vm.$data.testString); } }, template:` <div @click="handleClick">{{modelValue}}</div> ` }); const vm = app.mount('#heheApp');</script>
效果:
使用关键 主要分两个部分:
自己设置子组件:
在需要 被父组件插入组件
的位置,
使用<slot></slot>
标签对临时占位;
父组件:
在调用子组件标签对
时,
往子组件标签对
间
写上 要替换子组件标签对
中<slot></slot>
位置的组件
【slot】的出现,
方便父子组件之间数据的传递,
方便DOM的传递;
<!DOCTYPE html><html><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hello World! heheheheheheda</title> <script src="https://unpkg.com/vue@next"></script></head><body> <div id="heheApp"></div></body><script> const app = Vue.createApp({ template: ` <myform> <div>提交</div> </myform> <myform> <button>提交</button> </myform>` }); app.component('myform', { template:` <div> <input /> <slot></slot> <br><br> </div> ` }); const vm = app.mount('#heheApp');</script></html>
运行效果:
<script> const app = Vue.createApp({ template: ` <myform> <div>提交</div> </myform> <myform> <button>提交</button> </myform>` }); app.component('myform', { methods: { handleClick() { console.log("heheda!!==="); } }, template:` <div> <input /> <span @click="handleClick"> <slot></slot> </span> <br><br> </div> ` }); const vm = app.mount('#heheApp');</script>
运行,点击提交文本或者按钮:
<script> const app = Vue.createApp({ template: ` <myform> 66666 </myform> <myform> 88888 </myform>` }); app.component('myform', { methods: { handleClick() { console.log("heheda!!==="); } }, template:` <div> <input /> <span @click="handleClick"> <slot></slot> </span> <br><br> </div> ` }); const vm = app.mount('#heheApp');</script>
<script> const app = Vue.createApp({ template: ` <myform> <test /> </myform> <myform> 88888 </myform>` }); app.component('test', { template: `<div>test component</div>` }) app.component('myform', { methods: { handleClick() { console.log("heheda!!==="); } }, template:` <div> <input /> <span @click="handleClick"> <slot></slot> </span> <br><br> </div> ` }); const vm = app.mount('#heheApp');</script>
运行:
尽管,
父组件
中 往子组件
标签间 插入的组件 会替换子组件
的插槽位,
但是父组件
中 往子组件
标签间 插入的组件,
其所使用的数据字段,依然是父组件
的,而非子组件
;
父组件的template中 调用的数据是 父组件中的 data;
子组件的template中 调用的数据是 子组件中的 data;
<script> const app = Vue.createApp({ data() { return { text: '提交' } }, template: ` <myform> <div>{{text}}</div> </myform> <myform> <button>{{text}}</button> </myform>` }); app.component('myform', { methods: { handleClick() { console.log("heheda!!==="); } }, template:` <div> <input /> <span @click="handleClick"> <slot></slot> </span> <br><br> </div> ` }); const vm = app.mount('#heheApp');</script>
可以在子组件的
插槽<slot>标签
间 编写默认值
,
假如父组件没有使用 组件 注入插槽
,
则对应位置 会显示默认值
:
<script> const app = Vue.createApp({ data() { return { text: '提交' } }, template: ` <myform> <div>{{text}}</div> </myform> <myform> <button>{{text}}</button> </myform> <myform> </myform>` }); app.component('myform', { methods: { handleClick() { console.log("heheda!!==="); } }, template:` <div> <input /> <span @click="handleClick"> <slot>default value</slot> </span> <br><br> </div> ` }); const vm = app.mount('#heheApp');</script>
效果:
使得插槽的 父组件注入部分 和 子组件占位部分,能够更加灵活的布局,
可以通过v-slot:[插槽名]
来对一组插槽命名,
父组件定义之后 插槽名
及其对应的组件
之后,
子组件只要要在要占位的地方,
配合name
属性 使用对应命名的<slot>标签,
就可将对应的父组件插槽组件占用过来;
父组件 的插槽注入部分的组件,
需要用标签组包裹起来,
使用v-slot:[插槽名]
命名一组插槽;
子组件使用<slot name="[插槽名]"></slot>
的形式,进行插槽组件块的临时占用;
<script> const app = Vue.createApp({ template: ` <layout> <template v-slot:header> <div>头部</div> </template> <template v-slot:footer> <div>尾部</div> </template> </layout>` }); app.component('layout', { template:` <div> <slot name="header"></slot> <div>content</div> <slot name="footer"></slot> </div> ` }); const vm = app.mount('#heheApp');</script>
效果:
v-slot
指令的简写v-slot:[插槽名]
可以简写成 #[插槽名]
<script> const app = Vue.createApp({ template: ` <layout> <template #header> <div>头部</div> </template> <template #footer> <div>尾部</div> </template> </layout>` }); app.component('layout', { template:` <div> <slot name="header"></slot> <div>content</div> <slot name="footer"></slot> </div> ` }); const vm = app.mount('#heheApp');</script>
实现的效果同上例;
v-for
例子 进行 列表渲染下面在子组件中,
使用v-for
指令 循环 子组件的数据,创立DOM组件:
<script> const app = Vue.createApp({ template: ` <test-list />` }); app.component('test-list', { data(){ return { list: ["heheda", "xixi" , "lueluelue"] } }, template:` <div> <div v-for="item in list">{{item}}</div> </div> ` }); const vm = app.mount('#heheApp');</script>
运行效果:
v-for
结合v-bind
、v-slot
、<slot>
做列表渲染作用:给
数据
由子组件
提供,
但列表UI实现
由父组件
调用处提供,
相似于回调接口
的设计逻辑!!!
子组件使用v-for
循环获取数据,
每一轮迭代 取得的子项数据,
通过v-bind
设置到占位的<slot>标签
中,
父组件中,在引用的 子组件标签上,
使用v-slot
承接 子组件通过v-bind
传来的所有数据字段,
同时将这些字段打包成一个相似JSONObject
的结构 字段
,
并为这个字段
指定一个形参名
(如下代码中的mySlotProps
);
【注意!
前面是,
使用v-slot
命名父组件中 拟填充插槽的组件,
子组件在<slot>标签
上,通过name=
使用 父组件的命名,灵活填充插槽;
而这里是,
的slot
反而是起到了相似props
的作用,而非之前的命名组件作用!】
在 拟填充插槽
的DOM组件中,
使用方才 v-slot
指定的形参,用于开箱取数据
:
<script> const app = Vue.createApp({ template: ` <test-list v-slot="mySlotProps"> <div>{{mySlotProps.item}}</div> </test-list>` }); app.component('test-list', { data(){ return { list: ["heheda", "xixi" , "lueluelue"] } }, template:` <div> <slot v-for="item in list" :item="item" /> </div> ` }); const vm = app.mount('#heheApp');</script>
运行效果同上例;
解构
概念进行简写使用v-slot="{item}"
替代前面的props
的结构逻辑形式;
意义是,把mySlotProps
这个承接属性的字段,
里面的item
属性直接解构 剥取
出来,直接拿来用;
<script> const app = Vue.createApp({ template: ` <test-list v-slot="{item}"> <div>{{item}}</div> </test-list>` }); app.component('test-list', { data(){ return { list: ["heheda", "xixi" , "lueluelue"] } }, template:` <div> <slot v-for="item in list" :item="item" /> </div> ` }); const vm = app.mount('#heheApp');</script>
运行效果同上例;
双向绑定特性
,通过点击事件切换UI的写法:<script> const app = Vue.createApp({ data() { return { currentItem: 'input-item' } }, methods: { handlerClick() { this.currentItem === 'input-item'? this.currentItem = 'div-item': this.currentItem = 'input-item' } }, template: ` <input-item v-show="currentItem === 'input-item'" /> <div-item v-show="currentItem === 'div-item'" /> <button @click="handlerClick">切换DOM组件</button>` }); app.component('input-item', { template:` <input />` }); app.component('div-item', { template:`<div>heheda</div>` }); const vm = app.mount('#heheApp');</script>
运行效果:
语法:
一般在父组件中,
使用占位标签<component :is="[需显示的 子组件名]" />
,
效果即 占位位置,会显示 is
属性 指定组件名的子组件;
另外,
使用<keep-alive>
标签,包裹<component :is="[需显示的 子组件名]" />
,
可以是切换组件的时候,能够缓存组件的数据,
如一个有输入数据
的 切换成一个其余组件 再切换 回来的时候,
可以保留一开始的输入数据
:
<script> const app = Vue.createApp({ data() { return { currentItem: 'input-item' } }, methods: { handlerClick() { console.log("handlerClick ----- "); this.currentItem === 'input-item'? this.currentItem = 'div-item': this.currentItem = 'input-item' } }, template: ` <keep-alive> <component :is="currentItem" /> </keep-alive> <button @click="handlerClick">切换DOM组件</button>` }); app.component('input-item', { template:` <input />` }); app.component('div-item', { template:`<div>heheda</div>` }); const vm = app.mount('#heheApp');</script>
运行效果:
初始为有输入数据的输入框:
点击切换为文本组件:再次点击,切换为有输入数据的输入框
,
因为<keep-alive>
的作用,数据缓存下来,没有丢失,
假如没加<keep-alive>
,这里会是空的输入框:
首先,
本文在此案例之前的所有案例,都是同步组件
,
即随即渲染,一个线程运行;
下面是异步(自己设置子)组件
,
可以设定在某个时刻开始,推迟一个时延后,再执行渲染:
<script> const app = Vue.createApp({ template: ` <div> <div-item /> <my-async-item /> </div>` }); app.component('div-item', { template:`<div>heheda heheda</div>` }); app.component('my-async-item', Vue.defineAsyncComponent(() => { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ template: `<div>this is my async component</div>` }) }, 4000) }) })) const vm = app.mount('#heheApp');</script>
关键代码【异步(自己设置子)组件
】:
app.component('my-async-item', Vue.defineAsyncComponent(() => { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ template: `<div>this is my async component</div>` }) }, 4000) }) }))
运行效果:
The above is the detailed content of Methods for communication between Vue3 parent and child components and two-way binding between components. For more information, please follow other related articles on the PHP Chinese website!