preface
Recently, I read this article by a leader in our community summarizing the communication mode and application scenarios of THE Vue component. I feel that the communication mode and application scenarios of the VUE2 are well summarized. Therefore, I would like to refer to the salute😄. I’ve been studying VUe3 recently, so I thought about summarizing the way components communicate in VUe3.
We know that vue3’s Composition Api is one of its best features, so the following sections demonstrate the implementation of the code in Setup. Developing a few simple Form components will demonstrate this later.
Basic operation
Here is a simple development of a VInput box component. A component is like a function that handles inputs and outputs. Vue3 provides two arguments on the setup function, props, and the EMIT method under context, to handle input and output, respectively.
props
Now that the VInput is a child component, I need it to be able to accept a value passed by the parent so that it can do the following logical processing for me before returning it to the parent. So, here we need some basic parent-child communication methods, V-bind, props.
Parent component
<template>
// Pass data to child components via v-bind
<VInput :value="valueRef" />
</template>
const valueRef = ref(' ')
Copy the code
In the VInput
<template>
<input :value="value" type="text" />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'VInput'.props: {
value: String
},
setup(props) {
// Other logic
// Accept this value
console.log(props.value)
return{}}})</script>
Copy the code
emit
When we accept parameters in the component and do some logical processing, we need to return the value to the external, and the external needs to implement an event function to accept it. At this point I can use the emit method
Suppose we want the VInput component to return a string of limited length. In this case, we need to implement a corresponding event function to receive this value, and then VInput internal emit event, return the value as a parameter.
VInput
<template>
<input :value="value" type="text" @input="onInput" ref="inputRef" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'VInput'.props: {
value: String.maxLength: Number
},
setup(props, { emit }) {
// Vue3 is a way to get components or DOM instances
const inputRef = ref()
// Limit the length of text
const limitLength = (value: string, maxLength: number) = >
value.slice(0, maxLength)
// Input control
const controlled = (value: string) = > {
inputRef.value.value = value
}
const onInput = (e: any) = > {
let value = e.target.value
if (typeof props.maxLength === 'number' && props.maxLength >= 0) {
value = limitLength(value, props.maxLength)
}
controlled(value)
// Return a processed value externally
emit('onInput', value)
}
return {
onInput,
inputRef
}
}
})
</script>
Copy the code
The parent component
<template>
// Pass a function to the child component via V-ON, and the user accepts the return value
<VInput :value="valueRef" :maxLength="10" @onInput="onInput" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import VInput from '@/components/VInput.vue'
export default defineComponent({
name: 'Demo'.components: {
VInput
},
setup() {
const valueRef = ref(' ')
const onInput = (value: string) = > {
// Accepts the value returned by the child component VInput
console.log(value)
// Change the corresponding value
valueRef.value = value
}
return {
valueRef,
onInput
}
}
})
</script>
Copy the code
For this type of input component, I guess you don’t want to have to go through the hassle of receiving and changing a value in a parent component, so VUE provides a V-model for faster input and output.
v-model
The Vue3 documentation shows that the usage of this directive has changed somewhat. In the past, to achieve a custom non-form component bidirectional binding, we need to use xxxx.sync syntax to achieve, now this directive has been abolished, but the unified use of v-model directive.
The parent component
The new V-Model can also support bidirectional binding of multiple data.
<template>
<VBtn v-model:value="valueRef" v-model:keyword="keywordRef" />
</template>
Copy the code
Custom non-form components
<template>
<button @click="clickHandle">click</button>
</template>
export default defineComponent({
name: 'VBtn'.props: {
value: String.keyword: String
},
setup(props, { emit }) {
// omit other code
// The user clicks the button
const clickHandle = (e: any) = > {
// omit other code
// Modify the data of the corresponding props
emit('update:value', value)
emit('update:keyword', value + '123')}return {
// ...}}})Copy the code
This is an introduction to some of the basic communication apis in Vue3. Vue3 is usually developed using the Composition Api, so you’ll find that you can’t use this.$XXX to call a function or property on an instance. This.$parent, this.$children, this.$on, this.$emit, etc.
What about communication between components in Vue3? Let’s go through the scenarios one by one, from simple to complex.
Let’s take a look at the three form components we developed together and see how they work in practice:
<template>
<ValidateForm ref="validateFormRef1" :model="state" :rules="rules">
<ValidateFormItem label="Username" prop="keyword">
<ValidateInput
placeholder="Please enter"
required
v-model:modelValue="state.keyword"
/>
</ValidateFormItem>
<ValidateFormItem label="Password" prop="password">
<ValidateInput
placeholder="Please enter"
required
type="password"
v-model:modelValue="state.password"
/>
</ValidateFormItem>
</ValidateForm>
<button class="btn btn-primary" @click="submit(0)">submit</button>
</template>
Copy the code
The functionality of all components is modeled after the Element UI.
The father the son
A parent component can pass data to a child component in two ways:
- v-bind
- Refs gets a function inside the child component and calls the parameter directly.
Refs way
We won’t go into details about v-bind, but we’ve already covered how to use it in the basic Operations section. This section describes how Vue3 takes a subcomponent instance from ref and calls a function on it to pass a value to the subcomponent.
Child components
<template>
// Render the value received from the parent
<div>Son: {{ valueRef }}</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'Son'.setup() {
const valueRef = ref(' ')
// This function can accept a parent passing an argument and changing the value of valueRef
const acceptValue = (value: string) = > (valueRef.value = value)
return {
acceptValue,
valueRef
}
}
})
</script>
Copy the code
The parent component
<template>
<div>sonRef</div>
<button @click="sendValue">send</button>
// Where ref accepts a string, the variable of type ref to be returned by setup has the same name
<Son ref="sonRef" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import Son from '@/components/Son.vue'
export default defineComponent({
name: 'Demo'.components: {
Son
},
setup() {
// If the ref initial value is null, it can be used to accept an instance
// The way to get an instance in vue3 is slightly different from vue2
const sonRef = ref()
const sendValue = () = > {
// You can take the son component instance and call all the information returned by its setup
console.log(sonRef.value)
// Pass data to the son component instance by calling its methods
sonRef.value.acceptValue('123456')}return {
sonRef,
sendValue
}
}
})
</script>
Copy the code
Here’s a flow chart:In fact, this approach is similar toVue2
The use ofthis.$refs
.this.$children
By taking the child component instance and calling the function directly on the child component. The method is the same, but inVue3
In nothis
The black box.
Here we can take a look at the console and see what sonRef.value is.
As you can see, all variables and methods returned by Setup can be retrieved from a subcomponent instance retrieved from ref, as well as other internal attributes. Take a look at the official documentation for the description of the Vue composite API.
In the Virtual DOM Patch algorithm, if a REF of a VNode corresponds to a REF in a rendering context, the element or component instance corresponding to that VNode will be assigned to that REF. This is done during the mount/patch process of the Virtual DOM, so the template ref is only accessible after rendering initialization.
Summary of REF method
Advantages:
-
A parent component can get data to be passed quickly to a child component that is determined to exist
-
The transmission parameters are not limited, and the transmission mode is flexible
Disadvantages:
- The subcomponent obtained by ref must be determined to exist (if it is not determined to exist, such as a subcomponent on a slot,
v-if
Subcomponents of control) - The child component also needs to implement methods that accept parameters
The father passes on further offspring
In general, there are two ways to pass values to the depth level:
- provide / inject
- vuex
provide / inject
When you see the word “deep”, the provide/inject option in Vue2 is definitely the first thing that comes to mind. Yes, the same logic applies to VUE3, where the two options become two methods.
Provide allows us to pass a piece of data to all descendants of the current component. All descendants can inject this data or not to accept it.
The general schematic diagram is as follows:
Actual Application Scenarios
There are two main application scenarios, one is when passing a parameter or a function deeply, the other is when passing a parameter to an uncertain component on the slot.
Focus on passing parameters to components on slots. Implement an outermost ValidateForm component that accepts the entire form data and validates the entire form data. It provides a slot inside for some uncertain components. There is also a ValidateFormItem component that accepts a field name to know exactly which field needs to be validated.
Component-based development requires decoupling of parameters and functions, so we design it as follows:
ValidateForm
:model
.rules
, just accept the data and validation rules for the entire formValidateFormItem
:prop
, just accept the field name, just know which field you need to validate
<template>
<ValidateForm ref="validateFormRef" :model="formData" :rules="rules">
<ValidateFormItem label="Username" prop="keyword">
<! -- Field component -->
</ValidateFormItem>
<ValidateFormItem label="Password" prop="password">
<! -- Field component -->
</ValidateFormItem>
</ValidateForm>
</template>
Copy the code
If the ValidateFormItem component needs to validate a field with prop, it needs to retrieve the form’s data. FormData [Prop] is used to retrieve the field’s value. Where does the formData come from? First, it is impossible to pass a ValidateFormItem component every time you write one. In practice, we can’t determine how many ValidateFormItem components to write under ValidateForm. If we manually pass a copy of the form’s data for each one, it would be a lot of redundant code and cumbersome to write. So, the ValidateForm component accepts and distributes it independently.
ValidateForm
So we need ValidateForm to distribute the data down.
<template>
<form>
<slot></slot>
</form>
</template>
<script lang="ts">
import { defineComponent, provide } from 'vue'
export const modelKey = Symbol(a)export const rulesKey = Symbol(a)export default defineComponent({
name: 'ValidateForm'.props: {
model: {
type: Object
},
rules: {
type: Object}},setup(props) {
// Send data to future generations
provide(modelKey, props.model)
provide(rulesKey, props.rules)
return{}}})</script>
Copy the code
ValidateFormItem
ValidateFormItem accepts the data passed above.
<script lang="ts">
import { defineComponent, reactive, inject, provide } from 'vue'
import { modelKey, rulesKey } from './ValidateForm.vue'
export default defineComponent({
name: 'ValidateFormItem'.props: {
label: String.required: {
type: Boolean.default: false
},
prop: String
},
setup(props) {
// Accept data from ValidateForm
const model = inject<any>(modelKey, ref({}))
const rules = inject<any>(rulesKey, ref({}))
// get props. Prop on model and rules, respectively
console.log(model[props.prop])
console.log(rules[props.prop])
// Data verification logic
return {
/ /...
}
}
})
</script>
Copy the code
Provide/Inject summary
In this article, the pros and cons of Vue component communication and its application scenarios are well summarized. The disadvantage is that it does not solve sibling communication.
vuex
Vuex has long been an excellent solution in the VUE ecosystem for data sharing between components at different levels. Not only does it work in parent-to-child, but it’s a great solution for parent-to-parent, or ancestor-to-descendant, progenitor to progenitor, or sibling components. Because it is a centralized state management model. Its implementation is also reactive in nature. Here is a brief mention of how it is used in Vue3.
To create astore
import { createStore } from 'vuex'
export enum Mutarions {
SET_COUNT = 'SET_COUNT'
}
export default createStore({
state: {
count: 231
},
getters: {
count: state= > state.count
},
mutations: {
[Mutarions.SET_COUNT]: (state, num: number) = > (state.count = num)
}
})
Copy the code
The parent component
<template>
<div>father</div>
<Son ref="sonRef" />
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import Son from '@/components/Son.vue'
import { useStore } from 'vuex'
import { Mutarions } from '@/store/index'
export default defineComponent({
name: 'Father'.components: {
Son
},
setup() {
const valueRef = ref(100)
const store = useStore()
store.commit(Mutarions.SET_COUNT, valueRef.value)
return{}}})</script>
Copy the code
Child components
<template>
<div>Son: {{ count }}</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex'
export default defineComponent({
name: 'Son'.setup() {
const store = useStore()
const count = computed(() = > store.getters.count)
return {
count
}
}
})
</script>
Copy the code
Child the parent
There are three ways that a child can pass data to its parent:
- v-on
- Refs way
- Event center
Refs way
It is also true to pass a data to the parent through a REF. The subcomponent implements a function that returns a value. The parent component calls this method after the parent component gets the child component instance through ref to get the required return value.
To look at the actual application scenario, we want the ValidateForm component to validate all of the following form items, and then return one of the validation states within the component via a function.
The parent component
<template>
<ValidateForm ref="validateFormRef" :model="formData" :rules="rules">
<ValidateFormItem label="Username" prop="keyword">
<! -- Field component -->
</ValidateFormItem>
<ValidateFormItem label="Password" prop="password">
<! -- Field component -->
</ValidateFormItem>
</ValidateForm>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'demo'.setup() {
// Omit some code
const validateFormRef = ref()
// Get the validation status inside the ValidateForm component with validate
if (this.validateFormRef.validate()) {
// After the form validates successfully, do the following operations
}
return {
validateFormRef
}
}
})
</script>
Copy the code
ValidateForm
<template>
<form>
<slot></slot>
</form>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'ValidateForm'.setup() {
const validate = async() = > {let result = false
// Call ValidateFormItem inside all ValidateFormItem components in the slot
// (Tips: For how to call, the event center will focus on later)
Return false if one of the checksum methods returns false
// Return true if both are true
return result
}
return {
validate
}
}
})
</script>
Copy the code
Here’s a rough flow chart:
This method also provides access to the internal data of child components, just as closure functions do.
Event center
Why is this kind of communication brought here? Because I think it would be very appropriate to use the event center in the next actual case. In the previous section, we left a hole in the fact that in order for the ValidateForm component to validate the entire form, it had to find a way for each ValidateFormItem to return its internal validation results to it.
There are two problems first
ValidateForm
The following components are mounted through slots, so they will not passref
To get an instance of each subform item, so you can’t get eachValidateFormItem
The validation status of the.- There is a picture in the chapter above that shows the passage
ref
Get the component instance. You can find it. You can find it$parent
Attributes, but none$children
Properties. This is awkward. We can’t be likeVue2
As in theValidateForm
Through the$children
Get an instance of each child component.
solution
Since there is no way to get the component instance on the slot, let’s bypass it and do it through an event center. Here’s the idea:
- in
ValidateForm
When the instance is initialized, create an event centerEmitter
Instance, which registers an event, accepts a function when the event is executed, and stores it in a queue. - Will this
Emitter
throughprovide
Pass to future generations to ensure that the event center is in a differentValidateForm
Components are all independent. In other words, if you write more than oneValidateForm
Their event centers don’t interfere with each other. - in
ValidateFormItem
The use ofinject
That receives its own form fieldEmitter
At mount time, executeEmitter
On the event, will own the internalvalidate
Function, pass to send toValidateForm
And caches the method to the queue. ValidateForm
When the checksum is executed, all the checksum functions in the queue can be executed and the checksum result can be obtained.
Specific code implementation:
Let’s implement an Emitter event center class
import { EmitterHandles } from '@/type/utils'
export class Emitter {
// Store the event function
private events: EmitterHandles = {}
// Used to register events
on(eventName: string, eventHandle: Function) {
this.events[eventName] = eventHandle
}
// Delete the event
off(eventName: string) {
if (this.events[eventName]) {
delete this.events[eventName]
}
}
// Trigger the event
emit(eventName: string. rest:any[]) {
if (this.events[eventName]) {
this.events[eventName](... rest) } } }Copy the code
Once the event center is in place, let’s refine the ValidateForm code
<script lang="ts">
import { defineComponent, nextTick, provide } from 'vue'
import { Emitter } from '@/utils/emitter'
type ValidateFunc = () = > boolean
export const emitterKey = Symbol(a)export const modelKey = Symbol(a)export const rulesKey = Symbol(a)export default defineComponent({
name: 'ValidateForm'.props: {
model: {
type: Object
},
rules: {
type: Object}},setup(props) {
// Pass the form data and validation rules to descendants
provide(modelKey, props.model)
provide(rulesKey, props.rules)
// Create an instance of the event center
const emitter = new Emitter()
// Pass the event center to future generations
provide(emitterKey, emitter)
// Accepts the validation function returned by the formItem component
// And save it
emitter.on('acceptValidate'.(validateFunc: ValidateFunc) = > {
validateList.push(validateFunc)
})
// The validation method used to accept the return of saved offspring
const validateList: ValidateFunc[] = []
// Verify the status of all data
const validate = () = > {
// Execute the validation method sent by each subform
return validateList.map(fn= > fn()).every(valid= > valid)
}
return {
validate
}
}
})
</script>
Copy the code
Ok, now that we’ve implemented the logic for validateForm, let’s write the logic for validateFormItem
<template>
<div class="form-group">
<label v-if="label" class=" col-form-label">{{ label }}</label>
<slot></slot>
<small v-if="error.isError" class="invalid-feedback">
{{ error.errorMessage }}
</small>
</div></template> <script lang="ts"> import { Emitter } from '@/utils/emitter' import { defineComponent, reactive, inject, onMounted, provide } from 'vue' import { emitterProviderKey, modelKey, rulesKey } from './ValidateForm.vue' export default defineComponent({ name: 'ValidateFormItem', props: { label: String, required: { type: Boolean, default: false }, prop: String }, Setup (props) {// Accept Emitter event center const Emitter = Inject <Emitter>(emitterProviderKey) const model = inject<any>(modelKey) const rules = inject<any>(rulesKey) const error = reactive({ isError: false, errorMessage: Const prop = props. Prop if (prop & model & rules && rules[prop]) {const prop = props. Prop if (prop & model & rules && rules[prop]) { const result = rules[prop].some((item: any) => { if (! item.validator(model[prop])) { console.warn(`${prop}:${item.message}`) error.isError = true error.errorMessage = item.message return true } }) return ! Result} return true} // When the component is mounted, OnMounted (() => {emitter && emitter. Emit ('acceptValidate', validateField) }) return { error } } }) </script>Copy the code
To understand the above process in more detail, here is a diagram:
- Register events, distribute event centers
- Execute the event and send the validation function
The whole process is summed up by the top-level component creating and distributing the event center and registering the event listener functions. The descendant component executes the event and then sends the message, and the top-level component reclaims the message.
Tips
Once again, when using the Emitter event center, we created and sent it in the Setup of the ValidateForm, rather than using a global event center. Like bosses this article Vue components communication mode and application scenario summary concluded that, in the form of the event bus is a fatal weakness, if there is a more common components on a page, as long as we transfer data to one of them, but every method of common components are bound to accept the data that will be a chaotic situation. However, our event bus is not a global one, but rather an event center within a single scope.
Because the event center is created inside the current component and published downward using provide, only descendants of the current component can use the event center. So, even if multiple ValidateForms are written to a surface, their validateForms are independent of each other.
<template>
<ValidateForm ref="validateFormRef1" :model="formData1" :rules="rules">
<ValidateFormItem label="Username" prop="keyword">
<! -- Field component -->
</ValidateFormItem>
<ValidateFormItem label="Password" prop="password">
<! -- Field component -->
</ValidateFormItem>
</ValidateForm>
<ValidateForm ref="validateFormRef2" :model="formData2" :rules="rules">
<ValidateFormItem label="Username" prop="keyword">
<! -- Field component -->
</ValidateFormItem>
<ValidateFormItem label="Password" prop="password">
<! -- Field component -->
</ValidateFormItem>
</ValidateForm>
</template>
Copy the code
Schematic diagram:
Summary of Incident Center
Advantages:
- Can be solved
Vue3
You can’t usethis.$children
The problem of - It can be used flexibly and is not restricted by the component level
- This type of communication is not constrained by the framework
Disadvantages:
- You need to control the scope of the event center
- You need to control the specification of event names
Event center advanced
Vue’s feature API is more granular in Vue3’s Composition API. We can modify the event center with a custom requirement.
Reactive and Ref can be introduced to help maintain a responsive data inside the event center, so that the corresponding view can be updated when the event center conducts certain communication behaviors. You can also use computed to compute attributes.
import { reactive, ref, computed } from 'vue'
export class Emitter {
// A responsive data center
private state = reactive({})
private events: EmitterHandles = ref({})
// Record the number of events in the current event center
private eventLength = computed(() = > Object.keys(events.value).length)
// Omit some code
}
Copy the code
Watch is added to realize the function of data monitoring to perform certain logical behaviors. I think the Composition API and the React Hooks API are very powerful because they allow us to use function functions as building blocks to assemble any application we want.
The deep offspring communicate to the top, and the brothers communicate
I think in fact, other scenes, their communication methods are almost the same, the so-called cookie-cutter. Descendants can pass values to ancestors, or sibling components can pass values using vuEX or event center. Sibling levels, or adjacent levels, can use ref,$parent, etc.
The last
Personally, I am very optimistic about Vue3. At present, FOR some old projects of the company, I also think how to reconstruct them with Vue3. Then I also happened to read the community leader’s article is absolutely very interesting, so I was interested to write a Vue3 communication summary 😂, there may be a lot of places said is not very in place.
If there is a mistake or is added, welcome to leave a message in the comment area, if you think it is helpful to you please point a thumbs-up ha ha, 🙏 thank you pretty girls.