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