Common communication mode of components

  • props

  • event

  • vuex

Edge cases

  • $parent

  • $root

  • $children

  • $refs

  • provide/inject

The characteristics of prop

  • $attrs

  • $listeners


p a r e n t / parent/
root

Communication between sibling components can be bridged by a common ancestor, parent or parent or parent or root.

Component 1 / / brother sayBai () {/ / this. $parent. $emit (' handle ', 'I am the boss'); $emit('handle', 'I am the boss '); $on('handle', message => {this.$root.$on('handle', message => {console.log(message); })}Copy the code

This approach, however, has the problem of too much coupling, because once the component hierarchy changes, it becomes problematic, especially with $parent.

For example, when we encapsulate our own components (encapsulate our own forms), many people will use $parent directly, but if the refactoring hierarchy changes, then a lot of logic will change, causing a lot of trouble. So what did the authorities do?

Element source address

Let’s take a look at what happens in element:

Function broadcast(componentName, eventName, params) {// componentName: componentName; // eventName: componentName; // eventName: componentName; Event name // params: parameter, which needs to be an array // iterate over all the child components: $children. ForEach (child => {var name = child.codes.codename; // If the child componentName is the same as the componentName passed in, then send the event. If (componentName === componentName) {child.$emit. Apply (child, $emit. [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); }}); } export default { methods: {/ / from bottom to top distributing events (bubble) similar to dispatch (the componentName, eventName, params) {var parent = this. $parent | | this. $root; var name = parent.$options.componentName; // Look up until you find a componentName with the same name as the componentName you passed in while (parent && (! name || name ! == componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.componentName; }} if (parent) {parent.$emit. Apply (parent, [eventName].concat(params)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); }}};Copy the code

$children

The parent component can communicate with the child component through $children.

ChangeChildren1Msg () {this.$children[1]. MSG =' change '; }Copy the code

Note:

  • Component, $children can only access custom components.
  • $children accesses an array and does not guarantee the order of the child elements. For example, if the component is loaded asynchronously, the order will change.

$attrs

(v−bind=”attrs “); (v−bind=”attrs “); (v−bind=”attrs “); This is passed to the child component via v−bind=”attrs”, which is useful when we create high-level components.

/ / parent component < Children2 name = 'pyy' / > / / child < span > sub components in the component 2 2: {{MSG}} - {{$attrs. Name}} < / span >Copy the code

$listeners

For example, when we wrap a component and have a callback function set in the parent component, the parent component is responsible for triggering the callback function, not the implementation logic, we can use $Listeners.

// <Children2 name='pyy' @click="onClickHandle" /> onClickHandle(){console.log(' in parent: onClickHandle'); This.$children[2]. MSG = $children; }Copy the code
/ / in the subcomponents < span v - on = "$listeners" > child component 2: {{MSG}} - {{$attrs. Name}} < / span >Copy the code

V – on = “$listeners” resolution: Listeners are themselves objects (which can be expanded using V − ON) in the form of key-value pairs where the keys are the names of all event listeners in the parent component. In this case, there is a click event on the parent component Children2, and in the child component Children2, Listeners are themselves objects (you can expand them with V-ON) in the form of key-value pairs where the keys are the names of all event listeners in the parent component. In this case, there is a click event on the parent component Children2, and in the child component Children2, The listeners themselves are objects (which can be expanded using v− ON) in the form of key-value pairs where the keys are the names of all event listeners in the parent component. In this case, the parent component Children2 has a click event and the child component Children2 has a key called click. The value is the callback function set in the parent component, that is, in the child component Children2, the span tag has a click event = the callback function set in the parent component. In this way, the child component does not need to care about the handling of the callback function, but only needs to bind and trigger it, which is commonly used when wrapping component libraries.

$refs

This is a common way to get a child node reference.

In the parent component:

<Children2 ref="CR2" /> changeChildren1Msg() {this.$children[1]. MSG =" change "; $refs.cr2. MSG = 'change 2'; $refs.cr2. MSG =' change 2'; },Copy the code

provide/inject

Provide/Inject provides the ability to pass values between ancestors and descendants. When we are not using VUex, Vue provides us with this native interface way to pass values between generations.

Such as:

// in app.vue: provide(){// provide(); // provide(){// provide();Copy the code
// inject: ['foo'], // inject the required attributeCopy the code

Note:

  • If you’re passing a basic data type, then this is not reactive. Only if the reference is to a datatype – object, and the object is responsive, will it be passed as responsive.

  • Properties in the child component only take effect if the same properties have already been declared in the child component data (proximity rule).

  • If the same properties are already declared in the child component data, how do you use the properties provided by provide? We need to transform Inject at this time

    Inject: {foo1: 'foo' // use alias foo1}Copy the code

code

// app.vue <template> <div id="app"> <Father /> </div> </template> <script> import Father './components/ father. vue' export default {provide(){// provide(){data return {foo: 'foo',}}, name: 'App', components: { Father } } </script>Copy the code
// father. vue <template> <div> <span> <span> <Children1 /> < button@click ="changeChildren1Msg"> </button> <Children2 name='pyy' @click="onClickHandle" /> <Children2 ref="CR2" /> </div> </template> <script> import Children1 from "./Children1"; import Children2 from "./Children2"; Export default {methods: {changeChildren1Msg() {this.$children[1]. MSG =' change '; $refs.cr2. MSG = 'change 2'; $refs.cr2. MSG =' change 2'; }, onClickHandle(){console.log(' in parent: onClickHandle'); This.$children[2]. MSG = $children; } }, components: { Children1, Children2 } }; </script>Copy the code
// children1.vue <template> <div> <button @click="sayBai"> </button> </div> </template> <script> export default { Methods: {sayBai () {/ / this. $parent. $emit (' handle ', 'I am the boss'); $emit('handle', 'I am the boss '); } }, mounted () { // this.$parent.$on('handle', message => { this.$root.$on('handle', message => { console.log(message); }) }, } </script>Copy the code
// children2. vue <template> <div> <! - $listeners / $attrs - > < span v - on = "$listeners" > child component 2: {{MSG}} - {{$attrs. Name}} < / span > <! -- provide/inject --> <span>------{{foo}} </span> </div> </template> <script> export default { // inject: Inject: {foo: 'foo'}, data() {return {MSG: 'MSG'}}, / / to monitor events mounted () {/ / this. $parent. $on (' handle 'message = > {$on this. $root. (' handle' message => { console.log(message); }) }, } </script>Copy the code

Filter filters

Function: Filter processing data format, can be used for some common text formatting.

Usage scenarios: filter can be used in two places: double braces interpolation and v – bind expression, pay attention to the filter should be added in the end of the expression, the “pipe” symbol | said.

Grammar:

<! - in a pair of curly braces - > < div > {{MSG | function name}} < / div > <! - in - bind ` ` v -- -- > < div v - bind: id = "MSG | function name" > < / div > / / filter filters: {(MSG)} {return filter to the result from the function name}Copy the code

Such as:

<div id="app"> <ul> <li v-for='item of goodList'> <! -- when not using a filter, but the currency symbol is fixed --> <! -- {{item.name}} - ${{item.price}} --> {{item.name}} - {{item.price | symbol}} </li> </ul> </div> <script New vue ({el: '#app', data() {return {goodList: [{name: 'peanut ', price: 10},{name: {name:' peanut ', price: 10},{name: {name: 'peanut ', price: 10},{name: {name:' peanut ', price: 10},{name: 'peanut ', price: 10}, }}, filter: {symbol: function(value) {return '$' + value; }}});Copy the code

To modify the above example slightly, symbols can be passed dynamically rather than written dead.

<div id="app"> <ul> <li v-for='item of goodList'> <! -- called as a method, . Pass parameters - > {{item name}} - {{item. The price | symbol (' selections)}} < / li > < / ul > < / div > < script SRC = "vue. Js" > < / script > < script > new Vue ({el: '# app, the data () {return {goodList: [{name:' peanuts' price: 10}, {name: 'seeds' price: 40}, {name:' beer, price: 90}], } }, filters: { symbol: Function (value, sym = '$') {$return sym + value; $return sym + value; }}}); </script>Copy the code

Custom instruction

In addition to the built-in directives for core functionality by default, Vue allows you to register custom directives. In cases where low-level manipulation of normal DOM elements is still needed, custom directives are used.

The official address

For example, the official input box automatically gets the focus example:

<div id="app"> <input type="text" v-focus> </div> <script SRC ="vue.js"></script> <script> // Register a global custom directive 'v-focus' Directive ('focus', {// when the bound element is inserted into the DOM... // insert: function (el, binding) {// insert el.focus()}}); new Vue({ el: '#app', }); </script>Copy the code

And then we’ll do another one from the definition, based on the level of the currently logged in user.

<div id="app"> <input type="text" v-focus> <! -- Special attention: In the instruction, "" is an expression, and if you need to pass a string, --> <button v-permission=" superAdmin ""> Delete </button> </div> <script SRC ="vue.js"></script> <script> // Const user = 'member'; // Register a global custom directive 'v-focus' vue. directive('focus', {// when the bound element is inserted into the DOM... // insert: function (el, binding) {// insert el.focus()}}); Directive ('permission', {inserted:}); // Insert the name of the directive, and add v-. function (el, binding) { console.log(binding); // Delete the element bound to the current directive if the specified user role does not match the current user role. == binding.value) { el.parentElement.removeChild(el) } } }); new Vue({ el: '#app', }); </script>Copy the code

Rendering function

The official address

Vue recommends using templates to create HTML in most cases. In some scenarios, however, the full programming power of JavaScript is really needed. At this point you can use the render function, which is closer to the compiler than the template.

Basis:

render: Function (createElement) {// return VNode (virtual DOM) return createElement(// receive three parameters tagname, // tagname data, // pass data to children // array of child nodes)}Copy the code

Examples based on the official website:

<div id="app"> <! -- Implement a component with Render: implement title --> <! - level refers to the need to generate the h1-6 which one label - > < my - head: level = '1' : the title = 'title' > {{title}} < / my - head > < my - head: level = '3' :title='title'> I'm another me </my-head> <! -- <h2 :title='title'> {{title}} </h2> --> </div> <script src="vue.js"></script> <script> Vue.component('my-head',{ props: ['level', 'title'], // the render function takes a createElement parameter, which is abbreviated to h h === createElement // because the underlying Vdom algorithm is snabbDOM, This algorithm to generate virtual dom render the name of the method is called h (h) {/ / attention here must have return, return the createElement method returns the Vnode. {attr: {title: this.title}},// parameter 2 this.$slots.default, // parameter 3: }}); new Vue({ el: '#app', data() { return { title: 'hello, vue! '}}}); </script>Copy the code

Then let’s take it a step further:

When a user uses a component,

<my-head :level='1' :title='title' icon='Food'>{{title}}</my-head>
Copy the code

We want to render as:

<! Vector diagram, ali use - > < h1: title = 'title' > < SVG class = "icon" > < use xlink: href = "# icon - iconfinder_Food_C_" > < / use > < / SVG > {{title}}  </h1>Copy the code

The final code is:

<div id="app"> <my-head :level='1' :title='title' icon='Food'>{{title}}</my-head> <! -- <h3 :title='title'> <svg class="icon"><use xlink:href="#icon-iconfinder_Food_C_"></use></svg> {{title}} </h3> --> </div> <script src="./iconfont.js"></script> <script src="vue.js"></script> <script> Vue.component('my-head',{ props: ['level', 'title', 'icon'], render(h){ let children = []; < span style =" box-sizing: border-box; color: RGB (93, 93, 93); line-height: 22px; font-size: 13px! Important; white-space: inherit! Important;" // Add the default slots.default to the children array. // Add the default slots.default to the children array. Const svgVnode = h(' SVG ', {class: 2 [h('use',{attrs: {"xlink:href": '#icon-iconfinder_${this.icon}_C_'}})] #icon-iconfinder_${this.icon}_C_ '}})] #icon-iconfinder_${this.icon}_C_ '}})] #icon-iconfinder_${this.icon}_C_ '}}) children = [svgVnode, ...this.$slots.default]; Return h('h'+this.level, // parameter 1: tag name {attrs: {title: this.title}},// parameter 2 children, // parameter 3: }}); new Vue({ el: '#app', data() { return { title: 'hello, vue! '}}}); </script>Copy the code

How is template syntax implemented

On the underlying implementation, Vue compiles the template into a virtual DOM rendering function. In combination with the response system, Vue is able to intelligently calculate the minimum number of components that need to be rerendered and minimize DOM operations.

In the previous example, the original code looks like this:

<! <div id="app"> <ul> <! <li v-for="item in goodList" :class="{active: (selected === item)}" @click="selected = item">{{item}}</li> <! -- style binding --> <! -- <li v-for="item in goodList" :style="{backgroundColor: (selected === item)? '# DDD' : 'transparent'} "@ click =" selectedCourse = item "> {{item}} < / li > -- > < / ul > < / div > < script SRC =" where vueJs path "> < / script > < script > const vm = new Vue ({el: '# app, the data () {return {goodList: [' peanuts' and' seeds', 'beer'], selected: "'}},}); </script>Copy the code

Then we output the render function that vue generated for us:

Execute code: console.log(vm.$options.render)

We see the output:

(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('ul',_l((goodList),function(item){return _c('li',{class:{active: (selected === item)},on:{"click":function($event){selected = item}}},[_v(_s(item))])}),0)])}
})
Copy the code

Then we rewrite the rendering function version based on this point.

<! New vue ({el: '#app', data() {return {goodList: [' peanut ',' melon ',' beer '], select: ', method: {}, render() { with(this){ return _c('div',{attrs:{"id":"app"}},[_c('ul',_l((goodList),function(item){return _c('li',{class:{active: (selected === item)},on:{"click":function($event){selected = item}}},[_v(_s(item))])}),0)])} } })Copy the code

As we can see, the result is the same.

Conclusion: Vue uses its compiler to compile the template into a render function, executes the render function again when the data changes, and compares the results of the two executions to determine the DOM operation to be done. The magic of the template is realized.

Functional component

The official address

It does not manage any state, it does not listen to any state passed to it, and there are no lifecycle methods. In fact, it’s just a function that takes some prop. In such a scenario, we can mark the component as functional, which means it has no state (no responsive data) and no instance (no this context).

Change the previous example to a functional component:

<div id="app"> <my-head :level='1' :title='title' icon='Food'>{{title}}</my-head> </div> <script src="./iconfont.js"></script> <script src="vue.js"></script> <script> Vue.component('my-head',{ functional: function.function.function.function.function.function.function.function.function.function.function.function.functions ['level', 'title', 'icon'], // in functional components, there is no this // Render (h, context){// take 'level', 'title', 'icon' from this, and change // 2. Const {level, title, icon} = context.props; let children = []; const svgVnode = h( 'svg', { class: 'icon' }, [h('use',{attrs: {"xlink:href": `#icon-iconfinder_${icon}_C_`}})] ); // 3. Add the context argument and update this.$slots.default to context.children, then update this.level to context.props. children = [svgVnode, ...context.children]; return h( 'h'+level, {attrs: { title: title }}, children, ) } }); new Vue({ el: '#app', data() { return { title: 'hello, vue! '}}}); </script>Copy the code

Mixed with

The official address

Mixins provide a very flexible way to distribute reusable functionality in Vue components. A mixin object can contain any component option. When a component uses a mixin object, all the options for the mixin object are “mixed” into the component’s own options.

Let myMixin = {created: function () {this.hello()}, methods: {hello: function () { console.log('hello from mixin! ')}}} Vue.component(' myComponent ', {mixins: [myMixin]})Copy the code

The plug-in

The official address

The vue. js plug-in should expose a install method. The first argument to this method is the Vue constructor, and the second argument is one

An optional option object.

const MyPlugin = { install (Vue, options) { Vue.component('my-head', {... }) } } if (typeof window ! == 'undefined' && window.Vue) { window.Vue.use(MyPlugin) }Copy the code

For example, encapsulate the above header component as a plug-in.

First create a new js file to store the plug-in code:

Install (Vue, options) {Vue.component('my-head',{functional: function.function.function.function.function.function.function.function.function.function.function.function.functions ['level', 'title', 'icon'], // in functional components, there is no this // Render (h, context){// take 'level', 'title', 'icon' from this, and change // 2. Const {level, title, icon} = context.props; let children = []; const svgVnode = h( 'svg', { class: 'icon' }, [h('use',{attrs: {"xlink:href": `#icon-iconfinder_${icon}_C_`}})] ); // 3. Add the context argument and update this.$slots.default to context.children, then update this.level to context.props. children = [svgVnode, ...context.children]; return h( 'h'+level, {attrs: { title: title }}, children, ) } }); Vue if (typeof window! == 'undefined' && window.Vue) { window.Vue.use(MyPlugin); }Copy the code

Then on the page, use the plug-in directly.

<div id="app"> <my-head :level='1' :title='title' icon='Food'>{{title}}</my-head> </div> <script src="./iconfont.js"></script> <script src="vue.js"></script> <script src="./plugins/head.js"></script> <script> new Vue({ el: '#app', data() { return { title: 'hello, vue! '}}}); </script>Copy the code

Code github address