Communication between non-parent and child components
This is the 14th day of my participation in the August Text Challenge.More challenges in August
During development, after we build the component tree, there will be communication between non-parent components as well as between parent components
The main ways are
- Dojo.provide and inject
- vuex
- Event bus
Dojo.provide and inject
Provide/Inject share data between non-parent and child components, mainly those components with deep nesting level transfer data to each other.
For example, if you have some deeply nested component, the child component wants to get part of the parent component
No matter how deep the hierarchy, a parent component can act as a dependent provider for all its children
The parent component has a provide option to provide data
The child component has an Inject option to start using this data
Throughout the process:
The parent component does not need to know which child components use the property it provides
Child components don’t need to know where inject’s property comes from
Dependency provider - parent component
<template> <div> <Middle /> </div> </template> <script> import Middle from './components/Middle.vue' export default { name: 'App', components: { Middle }, provide: {// This can't be used directly because this will find this in script and this is undefined. If this is needed, it needs to be set as a function MSG: 'message in App' } } </script>Copy the code
Dependent consumer - descendant components
<template>
<div>
{{ msg }}
</div>
</template>
<script>
export default {
name: 'Child',
inject: ['msg']
}
</script>
Copy the code
In order for us to use our this keyword correctly in provide, we need to change the corresponding value of provide to a functional form
<template> <div> <Middle /> </div> </template> <script> import Middle from './components/Middle.vue' export default { name: 'App', components: {Middle}, // provide does not use this keyword, so we cannot use arrow function provide() {return {// When vue calls a function, call is automatically used to correct this pointing to // note: The data in provide is not responsive, which means that when this. MSG changes, the MSG attribute in // provide does not change accordingly in real time: Data: () => ({MSG: 'message in App'})} </script>Copy the code
The data assignment in provide is one-time, that is, non-responsive. If we want to actually listen for changes in the corresponding state and modify the corresponding state in the state consumer in real time, we need to use a computed method
Status provider -- app.vue
<template> <div> <Middle /> <button @click="counter += 1">+1</button> </div> </template> <script> import Middle from './components/Middle.vue' import { computed } from 'vue' export default { name: 'App', components: {Middle}, provide() {return {// Computed is vue3's compositeAPI that converts this.counter to responsive data, Counter: computed(() => this.counter)}}, data: () => ({counter: 0})} </script>Copy the code
State consumer - Descendant component
<template> <div> <! -- Counter is a ref object, and if we need to get the actual value, Value {{counter.value}} </div> </template> <script> export default {name: 'Child', inject: ['counter'] } </script>Copy the code
Global event bus
Provide and inject are mainly used for data transfer between grandparent and grandchild components, while Vuex is used for data transfer between multiple components that are far related.
If we want to fire events in one component and listen for the corresponding events in a distant component and act accordingly, we need to use the global event bus
npm install mitt
Copy the code
@/utils/emitter.js
import mitt from 'mitt'
// Multiple event dispatchers can be created using the mitt method
// The same event dispatcher must be used when firing and events
export const emitter = mitt()
Copy the code
Event triggering component
<template> <div> <Middle /> < button@click ="handleClick"> </button> </div> </template> <script> import Middle from './components/Middle.vue' import { emitter } from './utils/emit' export default { name: 'App', components: { Middle }, methods: {handleClick() {// Emit (event name, parameter list) // Emit (emitEvent name, parameter list) // Emit (emitEvent name, parameter list) 'Klaus' }) } } } </script>Copy the code
Event response component
<template> <div></div> </template> <script> import { emitter } from '@/utils/emit.js' export default { name: 'Child', created() {emitEvent ('emitEvent', param => {console.log(param1)})} </script>Copy the code
// Clear all event listeners
emitter.all.clear()
// If a specific event listener needs to be removed, the listener and the removed function must be the same
Parameter 2 must be a reference address to a specific function
function onFoo() {}
emitter.on('foo', onFoo) // listen
emitter.off('foo', onFoo) // unlisten
Copy the code
// * indicates that all events are monitored
// Parameter 1 is the event name, and parameter 2 is the parameter passed in
// The event will be executed several times if several arguments are fired
emitter.on(The '*'.(type, param) = > {
console.log(type, param)
})
Copy the code
emitter.emit('emitEvent', { name: 'Klaus' })
emitter.emit('foo', { name: 'foo' })
emitter.on(The '*'.(type, param) = > {
console.log(type, param)
// => emitEvent {name: "Klaus"}
// => foo {name: "foo"}
})
Copy the code
slot
Earlier we passed some data to the component via props so that the component could present it
However, in order to make this component more versatile, we prefer that some of the structure of our component can also be customized by users, rather than just the data
Take the JD search box as an example:
- The component is divided into three areas: left – center – right, and the contents of each area are not fixed
- The left area might show a menu icon, it might show a back button, it might show nothing at all
- The middle area might display a search box, a list, a title, and so on
- It could be a text, it could be an icon, it could be nothing, right
In the encapsulated component, the special element
is used to open a slot for the encapsulated component
is essentially a placeholder element that lets the outside world decide what elements and content to display
If the external passes in the contents of the slot, the external incoming contents are displayed
If no external data or elements are passed in, do no render at all
In slot, we can store anything from custom components to element structures to simple data displays
Users of the slot.
<template> <div> <Child> <! -- use slot --> <p>App Component</p> </Child> </div> </template> <script> import Child from './components/ child.vue 'export default { name: 'App', components: { Child } } </script>Copy the code
Slot declarant
<template> <div> <! -- use slot as placeholder --> <! <slot /> </div> </template> <script> export default {name: 'Child'} </script>Copy the code
A slot without a name carries the implied name default
<slot /> <! -- equivalent to --> <slot name="default" />Copy the code
<Cpn> <span> Contents in the default slot </span> </Cpn> <! -- Equivalent to --> <Cpn> <template v-slot:default> <span> </span> </template> </Cpn>Copy the code
The default value
Sometimes we want to display a **** default if there is no content to insert when using a slot
The default content will only be displayed if no content is provided for insertion
<slot> default value </slot>
Copy the code
A named slot
Before, our slots didn’t have any names. This slot is then called the default slot or default slot
Now if we have multiple default slots in our interface
The caller
<Child>
<p>App Component</p>
</Child>
Copy the code
The user
<div>
<slot />
<slot />
<slot />
</div>
Copy the code
At this point, each slot gets what we inserted and displays it
Because all of them are default slots, all of them match up
At this point, we need to give a name to our slot, which is called a named slot
A named slot, as its name implies, gives a slot a name, and the
element has a special attribute:name
A slot without a name carries the implied name default
The caller
<Child>
<template v-slot:header>
<p>Header</p>
</template>
<template v-slot:main>
<p>Main</p>
</template>
<template v-slot:footer>
<p>Footer</p>
</template>
</Child>
Copy the code
The definer
<div>
<slot name="header" />
<slot name="main" />
<slot name="footer" />
</div>
Copy the code
Like V-ON and V-bind, V-slot has an abbreviation
That is, replace everything before the argument (v-slot:) with the character #
<Child>
<template #header>
<p>Header</p>
</template>
<template #main>
<p>Main</p>
</template>
<template #footer>
<p>Footer</p>
</template>
</Child>
Copy the code
Dynamic slot name
Currently the slot names we use are fixed, but sometimes we want the slot names to be externally specified as well
In this case, you can use v-slot:[dynamicSlotName] to dynamically bind a name
Slot caller
<template> <div> <! --> <Child :slotName="slotName"> <! --> <template #[slotName]> <p>Main</p> </template> </Child> </div> </template> <script> import Child from './components/Child.vue' export default { name: 'App', components: { Child }, data() { return { slotName: 'main' } } } </script>Copy the code
Slot caller
<template> <div> <! <slot :name="slotName" /> </div> </template> <script> export default {name: 'Child', props: {slotName: String } } </script>Copy the code
Render scope
In Vue there is the concept of render scope:
- Everything in the parent template is compiled in the parent scope
- Everything in a subtemplate is compiled in a subscope
This means that the content in the slot is used in another component, but the content in the slot is compiled in the component that calls the slot
So variables in a slot can only be variables that exist in the scope of the component that calls the slot
Scope slot
Since a render scope exists, variables used in a slot can only be variables that exist in the component scope of the calling slot
But the slot is displayed in another component, which means we need to use variables that don’t exist in the scope of the component calling the slot
At this point you can use the scope slot
Slot caller
<template> <div> <Child> <! SlotScope {name: 'Msg in Child Cpn'} -- All data passed to the slot caller is stored in slotScope as key-value pairs. SlotScope is just the name of a variable, {{slotscope.msg}}</p> </template> </Child> </div> </template> <script> import Child from './components/Child.vue' export default { name: 'App', components: { Child } } </script>Copy the code
Slot definer
<template> <div> <! <slot: MSG =" MSG "/> </div> </template> <script> export default {name: 'Child', data() { return { msg: 'Msg in Child Cpn' } } } </script>Copy the code
Exclusive default slot abbreviation
If our slot only has the default slot, we can use v-slot directly on the component, thus omitting the template tag
<template>
<div>
<Child v-slot="slotScope">
<p>{{ slotScope.msg }}</p>
</Child>
</div>
</template>
Copy the code
However, if we have a default slot and a named slot, we write the full template
If the slot is written directly to the component, vUE does not know which slot the data should be assigned to
The data required by multiple slots may be different
<template>
<div>
<Child >
<template v-slot="slotScope">
<p>{{ slotScope.msg }}</p>
</template>
<template v-slot:foo="{ msg }">
<p>{{ msg }}</p>
</template>
</Child>
</div>
</template>
Copy the code