This article pure technical dry goods, the first in nuggets, reproduced please indicate the source and author. In recent months, I have been devoted to the open source work of iView, and completed more than 30 UI components, both large and small. I have accumulated a lot of experience in Vue component development. There are also a lot of technical and technical components, some of these features are mentioned in the Vue documentation but easy to ignore, and some are not written in the documentation, today talk about the advanced gameplay of the Vue component.

Writing in the front

Most of what this article talks about is used in the iView project, you can go to follow, and with the source code to explore the mystery. Project address: github.com/iview/iview

directory

  • Recursive components
  • Custom component usev-model
  • use$compile()Manually compile components in the specified context
  • Inline templateinline-template
  • Implicitly create Vue instances

Recursive components

Recursive components are described in the documentation, and you can call yourself recursively on a component by giving it a name field, for example:

var iview = Vue.extend({
  name: 'iview'.template:
    '<div>' +
      // Call itself recursively
      '<iview></iview>' +
    '</div>'
})Copy the code

This usage is not common in business and is used in the cascading selection component of the iView (github.com/iview/iview…). The effect is shown below:



caspanel.vue
v-for

<ul v-if="data && data.length" :class="[prefixCls + '-menu']">
    <Casitem
        v-for="item in data"
        :prefix-cls="prefixCls"
        :data.sync="item"
        :tmp-item="tmpItem"
        @click.stop="handleClickItem(item)"
        @mouseenter.stop="handleHoverItem(item)"></Casitem>
</ul><Caspanel v-if="sublist && sublist.length" :prefix-cls="prefixCls" :data.sync="sublist" :disabled="disabled" :trigger="trigger" :change-on-select="changeOnSelect"></Caspanel>Copy the code

The two key components are data and sublist, the current column data and the subset data. Because we don’t know how many children there are in advance, we just need to pass the child data to the component itself. If it is empty, the recursion ends. Note: This method is supported in both Vue 1.x and Vue 2.x.

Custom component usev-model

We know that v-Model is used for bidirectional binding on form class elements, such as:

<template>
    <input type="text" v-model="data">
    {{ data }}
</template>
<script>
    export default {
        data () {
            return {
                data: ' '}}}</script>Copy the code

In this case, data is bidirectional and the input is displayed on the page in real time. In Vue 1.x, custom components can use the.sync bidirectional binding for props, such as:

<my-component :data.sync="data"></my-component>Copy the code

In Vue 2.x, you can use v-Models directly on custom components, such as:

<my-component v-model="data"></my-component>Copy the code

In the my-component, change the value of data by calling this.$emit(‘input’). This is not possible in Vue 1.x, but it can be used if your component’s template contains input, select, textarea, and other elements that support the binding of v-Model features. For example, my-component:

<template>
    <input type="text">
</template>Copy the code

That could also be written in the 2.x notation above.

use$compile()Manually compile components in the specified context

Note: This method is introduced in Vue 1.x, the official documentation does not give any description of this method, do not rely too much on this method. Components can be manually compiled in any given context (Vue instance) using the $compile() method used in iView’s newly released Table component: github.com/iview/iview… Because the column configuration of the table is passed to props through an Object, it is not possible to automatically compile parts with Vue code like slot does, because all that is passed in are strings, such as:

{
    render (row) {
        return `<i-button>${row.name}</i-button>`}}Copy the code

The render function returns a string containing a custom component called i-button, which is not compiled if shown with {{{}}}, and uses the $compile() method to support rendering custom components in cells. For example, we compile at the parent level of the component:

// Code snippet
const template = this.render(this.row);    // Get the string from the render function above
const div = document.createElement('div');
div.innerHTML = template;
this.$parent.$compile(div);    // Compile the component in the parent context
this.$el.appendChild(cell);    // Inserts the compiled HTML into the current componentCopy the code

This way, i-Button is compiled. There are benefits to using $compile() some of the time, but there are a number of issues worth thinking about:

  • It’s easy to get confused about scopes when compiling, so know which Vue instance you’re compiling on;
  • After manual compilation, you also need to use it when appropriate$destroy()Manual destruction;
  • Sometimes it is easy to double compile, so remember to save the id of the current compiled instance, which can be found in the Vue component_uidEach Vue instance will have an incremented ID that can be passedthis._uidGet)

In Vue 2.x, there is a Vue.compile() method that compiles template strings in the render function.

Inline templateinline-template

Inline templates are nothing new. They are documented, but they are rarely used, so they are easy to ignore. A short explanation is to use the slot of the component as the template for the component, which is more flexible:

<! Parent component: -->
<my-component inline-template>
    {{ data }}
</my-component>

<! -- Subcomponent -->
<script>
    export default {
        data () {
            return {
                data: ' '}}}</script>Copy the code

Because use the inline – template inline template, so child components don’t need < template > to declare the template, then the template directly from slot {{data}}, and the data in the context, is child components, not the parent component, therefore, One of the most common pitfalls when using inline templates is confusing scopes.

Implicitly create Vue instances

In Webpack, we develop with.vue single files, each of which is a component, using components where needed via Components: {}. For example, if we need a prompt box component, we might write this in the parent:

<template>
    <Message>This is the tip title</Message>
</template>
<script>
    import Message from '.. /components/message.vue';
    export default {
        components: { Message }
    }
</script>Copy the code

There’s nothing wrong with writing this, but from a usage point of view, we don’t really want to use it this way. Instead, the native window.alert(‘ This is the prompt title ‘) is more flexible, and many people might write a function using native JS string, which is fine, but if your prompt component is complicated, And multiplex, this method is not friendly, reflects the value of Vue. IView uses Vue internally when developing global prompt components (Message), Notice and dialog components (Modal), but JS implicitly creates these instances so we can use them like message.info (‘ title ‘), But it is managed internally through Vue. Related code address: github.com/iview/iview…

Let’s look at the implementation:


import Notification from './notification.vue';
import Vue from 'vue';
import { camelcaseToHyphen } from '.. /.. /.. /utils/assist';

Notification.newInstance = properties= > {
    const _props = properties || {};

    let props = ' ';
    Object.keys(_props).forEach(prop= > {
        props += ':' + camelcaseToHyphen(prop) + '=' + prop;
    });

    const div = document.createElement('div');
    div.innerHTML = `<notification${props}></notification>`;
    document.body.appendChild(div);

    const notification = new Vue({
        el: div,
        data: _props,
        components: { Notification }
    }).$children[0];

    return {
        notice (noticeProps) {
            notification.add(noticeProps);
        },
        remove (key) {
            notification.close(key);
        },
        component: notification,
        destroy () {
            document.body.removeChild(div); }}};export default Notification;Copy the code

Instead of $compile(), we use new Vue to create an instance of Vue globally (body). We only need to expose a few apis at the entrance:

import Notification from '.. /base/notification';

const prefixCls = 'ivu-message';
const iconPrefixCls = 'ivu-icon';
const prefixKey = 'ivu_message_key_';

let defaultDuration = 1.5;
let top;
let messageInstance;
let key = 1;

const iconTypes = {
    'info': 'information-circled'.'success': 'checkmark-circled'.'warning': 'android-alert'.'error': 'close-circled'.'loading': 'load-c'
};

function getMessageInstance () {
    messageInstance = messageInstance || Notification.newInstance({
        prefixCls: prefixCls,
        style: {
            top: `${top}px`}});return messageInstance;
}

function notice (content, duration = defaultDuration, type, onClose) {
    if(! onClose) { onClose =function () {}}const iconType = iconTypes[type];

    // if loading
    const loadCls = type === 'loading' ? ' ivu-load-loop' : ' ';

    let instance = getMessageInstance();

    instance.notice({
        key: `${prefixKey}${key}`.duration: duration,
        style: {},
        transitionName: 'move-up'.content: `
            <div class="${prefixCls}-custom-content ${prefixCls}-${type}">
                <i class="${iconPrefixCls} ${iconPrefixCls}-${iconType}${loadCls}"></i>
                <span>${content}</span>
            </div>
        `.onClose: onClose
    });

    // For manual elimination
    return (function () {
        let target = key++;

        return function () {
            instance.remove(`${prefixKey}${target}`);
        }
    })();
}

export default {
    info (content, duration, onClose) {
        return notice(content, duration, 'info', onClose);
    },
    success (content, duration, onClose) {
        return notice(content, duration, 'success', onClose);
    },
    warning (content, duration, onClose) {
        return notice(content, duration, 'warning', onClose);
    },
    error (content, duration, onClose) {
        return notice(content, duration, 'error', onClose);
    },
    loading (content, duration, onClose) {
        return notice(content, duration, 'loading', onClose);
    },
    config (options) {
        if (options.top) {
            top = options.top;
        }
        if (options.duration) {
            defaultDuration = options.duration;
        }
    },
    destroy () {
        let instance = getMessageInstance();
        messageInstance = null; instance.destroy(); }}Copy the code

At this point the component can be called directly from message.info (), but we can also extend it on Vue: vue.prototype. $Message = Message; We can call this.$message.info () without importing Message.

Afterword.

There are a lot of interesting techniques in Vue component development that can reduce unnecessary logic if used well, and can be self-defeating if not used well. When developing a more complex component, it is important to research and design the technical solution before coding it. IView also has a lot of development skills and interesting code, later we have time to continue to discuss it, recently released several versions have a large update, I hope you can pay attention to and promote iView 😝 :

github.com/iview/iview