Practice and Design of Vue Component Library

Keywords: Attribute Vue github less

Now the rapid development of the front-end has made the component pattern more important. For component libraries on the market, although they can satisfy most of the projects, some small details and usage aspects, or some bug s in the UI inventory, can cause a headache.

Then how should we face these problems? As the saying goes, you can eat and clothe yourself. Some components don't have to be built on purpose. Consideration should be given to how to build a fast, compatible and fully functional component library.

  1. Let's go to github and some big companies'open source component libraries to see demo examples of the component libraries you need, and what interfaces Prop and Evet expose.

  2. There are three comparisons. Others have used which model design, which model is the simplest and more reasonable.

  3. Generally mature UI library compatibility is the result of a large number of tests and user use without problems. This part is omitted to draw lessons from the style.

  4. Don't be too eager to build a large number of wheels for components. Because a man's battle is limited. According to the needs and scenarios of the project positioning one by one, the product of less into more.

Crib-zk is also an additional component for my current project. Although it can not be used in the open source market, it can be used for everyone's learning. Use and learning are two modes. Using it doesn't mean you understand, once some requirements are not within the scope of open source project components. If you can't understand the internal principle at this time, you will be caught by surprise. Next step by step analysis.

Outline: Analysis Components

  1. alert plug-ins/components

  2. backtop component

  3. sms-countDown component (SMS countdown)

  4. search component

  5. infinitscroll component

  6. Action Sheet component

  7. Accordion component (accordion)

Note: It's best to view this article in a comparative way with the components published on my github.

github: https://github.com/ziksang/crib-ziksang

demo: http://39.108.49.237:3000/dist/

I. alert components/plug-ins

If in the pop-up component of alert, some background layout s should be animated first, which can be referred to as dialog animation for a package.

Where is the differentiated usability of alert components / plug-ins?

Generally speaking, the alert template of A. vue file is defined first.


enter image description here
<template>
    <div class="vux-alert">
        <x-dialog :value='alertShow' :isClose='false'>
            <div class="crib-confirm_hd" :style='[titleStyle]'>
                <strong class="crib-confirm_title">{{title}}</strong>
            </div>
            <div class="crib-confirm_bd">
                <slot>
                    <div v-html="content"></div>
                </slot>
            </div>
            <div class="crib-confirm_ft">
                <a href="javascript:;" class="crib-confirm_btn crib-confirm_btn_primary" @click="_onSubmit" :style='[buttonStyle]'>{{buttonText}}</a>
            </div>
        </x-dialog>
    </div>
</template>

Look carefully at the template above.

  1. The only special thing we found was the definition of a solt in the content body. This slot is the distinction between component mode and plug-in mode. If we want to define an additional presentation template or other component insertion in slot, we can only use component mode at this time.

  2. Plug-in mode is also the most convenient if we just fill in the content data.

Exposure of props interface

  1. value displays messages

  2. Title (title)

  3. Content content is best supported in html format

  4. Button Text at the bottom of buttonText

  5. Title Style Heading Style

  6. buttonStyle button style

Evet interface exposure

  1. Exposure of events when onsubmit clicks

  2. Display events exposed when onshow is displayed

  3. onhide Displays Time-to-Time Exposure Events

 data() {
        return {
            alertShow: this.value
        }
    },
    watch: {
        value(val) {
            this.alertShow = val
        }
    },
    methods: {
        _onSubmit() {
            this.alertShow = false
            this.$emit('update:value', false)
            this.$emit('on-submit')
        }
    }

For the value, you can use. sync for easy operation. Notification is not required through $emit. Using $on to listen for events when declaring components eliminates the developer's step.

Plug-in mode

First, the original alert's. vue template should be introduced, and then inherited with Vue.extend.

  $vm = new Alert({
                el: document.createElement('div')
            })

We have to create a mount point manually.

//This method is used to merge the prop attribute on confirm onto the parameters at call time.
const mergeOptions = function($vm,options) {
    //Declare a default object, which is the default value of the props attribute on comfirm
    const defaults = {}
    //The props value on the circular confirm attribute
    for (let i in $vm.$options.props){
       //Instead of calculating the value, it shows that changes are monitored through watch or by changing the attributes of the data proxy.
       if(i !== 'value'){
          defaults[i] = $vm.$options.props[i].default
       }
    }
    //Merge the original value of the confrim component with the options passed in by the plug-in
    const _options = Object.assign({},defaults,options)
    //Replace the actual object generated by confirm component with the merged attribute again
    for(let i in _options) {
        $vm[i] = _options[i]
    }
}

At the same time, except for the default defined attributes of value display operation, the default attributes should be overwritten and displayed after self-definition.

$watcher = $vm.$watch('alertShow', (val) => {
                    if (!val && options && options.onHide) {
                        options.onHide()
                    }
                })

At the same time, alertshow is monitored. When you click submit, it triggers the event automatically, then changes the value of alertshow, and then performs the operation you want.

2. backtop components

For backtop components, there are several attributes to understand.

  1. scrollTop

  2. offsetHeight

let offsetTop = this.scrollview == window ? document.body.scrollTop : this.scrollview.scrollTop
            let offsetHeight = this.scrollview == window ? document.body.offsetHeight : this.scrollview.offsetHeight

scrollTop is the height from the top.

The height of the offsetHeight element includes the border.

How do you decide when to display the return top button?

this.show = offsetTop >= offsetHeight / 2;

As long as the height of the scroll is greater than / 2 of the height of the scroll element, it is best to make an adaptation.

For how to optimize that aspect.

Function throttling is possible. What is throttling? Because scroll events trigger too often when scroll monitoring is performed. This can affect the overall performance issues. If you scroll up and down, listen frequently. Node is not suitable for anti-jitter operation.

 throttle(func, wait) {
            var context, args;
            var previous = 0;

            return function () {
                var now = +new Date();
                context = this;
                args = arguments;
                if (now - previous > wait) {
                    func.apply(context, args);
                    previous = now;
                }
            }
        },

Function throttling is achieved by comparing time stamps. But one thing to note is that while throttling, don't throttle the season too long. Because if the throttle rolls on the mobile, it has a self-sliding time.

const getScrollview = function (el) {
    //Get the current node
    let currentNode = el;
    //If there is a node, and the node is not equal to html, body and the node type is an element node
    while (currentNode && currentNode.tagName !== 'HTML' && currentNode.tagName !== 'BODY' && currentNode.nodeType === 1) {
       //Get the overflow property of the node
        let overflowY = document.defaultView.getComputedStyle(currentNode).overflowY;
        //If the attribute is scroll or atuo at this time, it returns to this node
        if (overflowY === 'scroll' || overflowY === 'auto') {
            return currentNode;
        }
        //Otherwise, continue searching for the parent node
        currentNode = currentNode.parentNode;
    }
    //Once the while statement is false, it returns directly to the window object
    return window;
};


export {getScrollview}

In the outer layer, a package is to be wrapped, and the overflow attribute direction is used to speculate whether it is a global roll or a roll under window s. The recursion is judged by which to find the corresponding elements.

3. sms-countDown SMS Countdown Component


enter image description here

Cognition of Text Countdown

The most important point for sms countdown is to notify the sms component of a prop parameter that starts with the countdown from the parent component and replace it with start.

  watch : {
        start (value) {
          if(value === true){
            this.countDown()
          }
        }
      }

start is also monitored internally. Once the import begins from the outside, the countdown is made internally.

countDown () {
            this.myTime = 60;
            let time = setInterval(()=>{
                this.myTime --;
                if(this.myTime === 0){
                  this.$emit('update:start', false);
                  this.myTime = this.initText;
                  this.flag = true;
                  clearInterval(time)
                }
            },100)
        }

Here is also an outward notification after the countdown stops. Or use. sync's two-way binding method for easy operation.

In the first countdown and the second countdown, we also need to set up a text aspect.

 firstCkText : {
            type : String,
            default : ''
          },
          secondCKText : {
            type : String,
            default : 'Re-acquisition'
          },

The first click and the second click of the button is also a design of the main copy, so the change of the copy should also be concerned.

search component

What functions and ideas do search components usually come up with? For example, when you first come in, you need to automatically get the focus of Input. At the same time, we need to expose whether we want to get Prop: autoFocus of Input. At the same time, it should be noted that the dom element must be obtained at the time of the Moonted.

  mounted() {
        this.autoFocus && this.$refs.input.focus()
    }

When you want to know whether the value in input has changed, you usually use keydown or keyup events. However, it is not necessary to expose the value from time to time so that the outer parent component can watch to perform the required event operations.

watch: {
        inputValue (val) {
           if(val == ''){
               this.value = ""
           }
        },
        value: {
            handler(val, oldvalue) {
                //Trigger events when values change
                this.$emit('update:inputValue', val)
                this.$emit('change-val')

            },
            immediate: true

        }
    },

At the same time, sync was used here, and it was executed immediately when the page was loaded. immediate makes the value immediately monitored.

5. infinite-scroll component (infinite scroll component)

There are three key points in rolling indefinitely. The first is scrolling the bottom trigger event; the second is loading if there is a secondary load; and the third is how to end the text without a secondary load.

import { getScrollview } from '../../libs/getScrollview.js';

Needless to say, keep looking for elements that need to scroll.

 data() {
        return {
            isLoading: false, //Is it loading?
            isDone: false,  //Whether loaded or not
        }
    },

data defines the states of the two patterns mentioned earlier. Looking down, what is the benefit of this definition for the future?

 this.$on('Load', () => {
                this.isLoading = false;
            });
            this.$on('loadDone', () => {
                    this.isLoading = false;
                    this.isDone = true;
            });

Two events need to be monitored:

  1. The load event is reloaded. Once the secondary loading is done, isloading immediately equals false to prevent repeated loading.

  2. Whether to listen through loadDone. If the load is complete, the same closing isloading makes true settings for isDone.

isloading and isDone correspond to the template part of the html, respectively.

 <div class="list-donetip" v-show='!isLoading&& isDone'>
            <slot name='doneTip'>No more data</slot>
        </div>
    
        <div class="list-loading" v-show='isLoading'>
            <slot name='loadingTip'>Loading...</slot>
        </div>

When isloading is true, it shows "loading..." when isloading is false, and when isDone is true, it shows "no more data", which is also a standard infinite scroll.

When are isloading and isDone set to true?

 scrollHandler() {
            if (this.isLoading || this.isDone) return;
            let baseHeight = this.scrollview == window ? document.body.offsetHeight : this.scrollview.offsetHeight
            let moreHeight = this.scrollview == window ? document.body.scrollHeight : this.scrollview.scrollHeight;
            let scrollTop = this.scrollview == window ? document.body.scrollTop : this.scrollview.scrollTop
            if (baseHeight + scrollTop + this.distance > moreHeight) {
                this.isLoading = true;

                this.onInfinite()
            }

            if (!this.scrollview) {
                console.warn('Can\'t find the scrollview!');
                return;
            }
        },

When scrolling to the bottom, set isloading to true. External components can call onInfinite for ajax requests and other operations.

How to call it outside?

this.$refs.infinite.$emit('loadDone')

ref the component and then trigger loadDone or load.

Compared with hungry components, it uses the pattern of instructions, and internal implementation is still too complex.

How to optimize

Function anti-shake is used here, as above, function throttling is not used, and anti-shake is used. What's the difference between shake-proof and throttling? Anti-shake is more performance-saving. If we have been sliding in the set time, it will not be loaded, only sliding to the designated place, then we can detect, through the timer to achieve.

 debounce  (func, wait) {
            var timeout;
            return function () {
                var context = this;
                var args = arguments;

                clearTimeout(timeout)
                timeout = setTimeout(function () {
                    func.apply(context, args)
                }, wait);
            }
        },

actionSheet components


enter image description here

The highlight of action Sheet here is its ingenuity. The call method changes the direction of this. What's the advantage of this? Look down.

prpps: Model I pass the data through the model, change the copy, click the method that is executed and encapsulate it into the model data for operation.

If the parent component introduces this component, look at the following code.

 model: [
                { name: this.name, method(name, index) { location.href = 'tel:110' } }
            ],

If you do this. pointing, you point to the parent component. You can't declare it directly in data here. If it's asynchronous, how do you declare it in the asynchronous ajax request and pass in the value?

 ff (item,index,method) {
             this.$emit('update:show', false)
             method.call(this.$parent,item,index)
        }

Here, by pointing this. to the parent component through. call, it can be successfully and conveniently invoked.

VII. accordion accordion accordion components


enter image description here

The accordion component is defined as a pattern in which two components are merged into one component.

  1. An outermost package component.

  2. The second is the component of each item.

For each component in accordion-item, the

this.height = (this.show ? this.$refs.content.offsetHeight: 0) + 'px';

If you need to show it, let each item element calculate its height and show it.

Each item is identified by _uid.

  methods : {
           open(uid) {
               this.$children.forEach (item => {
                   console.log(item._uid)
                   if(item._uid == uid){
                       item.show = !item.show
                   }else{
                       if(!this.repeat){
                           item.show = false
                           item.height = 0;
                       }
                   }
               })
           }
       }

What can be retrieved is the item component, and then a transfer is made to the retrieved item component. Essentially, it is also possible to find items corresponding to sub-components through index. Because _uid is unique. This step also saves some easy operation.

Here, some outstanding components are used to broaden our thinking, to encapsulate other components, or to encapsulate the projects we need according to different requirements based on these components.

Finally, you said a big word, my favorite is to look at other people's code. Keep that in mind, your components can write fast and well.

This is an article I recently exchanged on Gitchat. If you are interested in Vue, you can join my Vue discussion group. What problems can be communicated in groups?


486198638691733315.png

Posted by echelon2010 on Thu, 06 Jun 2019 17:33:16 -0700