This article is the second in a series of articles on Vue 2.0 component library development. After the first “Ramble on Vue 2 component library”, let’s talk about the experience and thinking of developing a Vue 2.0 mobile UI component library based on webpack scaffolding.
The book picks up where it left off. Vue component library development.
Our Vue component library development experience mainly comes from the development practice of NutUI component library, which is a mobile UI component library based on Vue 2.0 extracted from many projects, which brings great convenience to our subsequent project development work. Friends can scan (long press in wechat to identify) the following TWO-DIMENSIONAL code to experience:
NutUI currently has nearly 50 components, which are constantly being expanded and polished. In addition to dialog box, calendar, rotation, tabs, light prompt, lazy loading and other general components, there are also many e-commerce features of the components, such as commodity price, scoring, commodity quantity selection, regional selection panel and so on. You can use it if you need it in your project. For specific installation and use methods, please refer to the relevant page of the official website [1].
That’s it. Welcome back. We got down to business.
Let’s start with the basics of Vue 2.0 component development.
Vue component development core method
The essence of Vue components is reusable Vue instances that receive the same options as new Vue, such as Data, computed, Watch, methods, lifecycle hooks, and so on.
Vue.component and vue.extend are the main methods for component development in Vue. What are the differences between them?
Vue.extend first. The Vue 2.0 documentation gives a passing description of Vue. Extend, but it is the most critical method for Vue component development. Vue.extend creates a “subclass” using the base Vue constructor. The parameter is an object that contains component options. It returns an “extended instance constructor,” which is the instance constructor for the Vue with some preset options. The component instance can be obtained by instantiating this extended Instance Constructor.
// Get an "extended instance constructor"
let cptConstructor = Vue.extend({
// Default options
});
// Get a component instance by instantiating
let cpt = new cptConstructor({
// Other options
});
Copy the code
Besides, Vue.com ponent. Vue.com ponent and Vue) filter and Vue) directive, is only a registered components method, put through the Vue. The extend to the extension of the instance constructor (component constructor) associated with a given string ID. In subsequent Vue template parsing, once this custom component ID is encountered, the corresponding extension instance constructor is instantiated to get an instance of the component and mount it.
// Register the component, passing in an extended constructor
Vue.component('my-component', Vue.extend({ /* ... */ }))
Copy the code
In the example in the component section of the official Vue 2.0 tutorial, the second argument passed to the Vue.component method is an option object for the component, not an extension instance constructor, and does not mention vue.extend at all. In fact, this is a syntactic sugar. If the second argument passed is not an extension instance constructor, vue.componentautomatically calls vue.extend internally to generate the extension instance constructor, so this is functionally the same as the above.
// Register the component, passing in an option object. Vue.extend is automatically called inside vue.component
Vue.component('my-component', { /* ... */ })
Copy the code
Vue.extend can also be used alone, by instantiating the extended instance constructor generated by vue.extend with the new operator to obtain an instance of a component that can then be manually mounted as needed. The NutUI component library does this, such as the Toast component, which is an instance method that hangs on the vue. prototype property and is not registered with Vue.com Ponent.
// Get "extended instance constructor"
let ToastConstructor = Vue.extend(require('./toast.vue'));
let instance;
Vue.prototype.$toast = function (... params) {
// Get the component instance
instance = new ToastConstructor();
/ / a mount
instance.vm = instance.$mount();
/ / insert the DOM
document.body.appendChild(instance.$el);
};
Copy the code
Bidirectional data binding of parent and child components
The data flow of the Vue component is one-way; updates to the parent prop flow down to the child component, but not the other way around. The main purpose of this design is to prevent accidental changes in the state of the parent component from the child component, resulting in an application data flow that is difficult to understand and maintain.
Take Mask, a basic component in NutUI:
<nut-mask
:visible="maskShow">
</nut-mask>
Copy the code
Prop visible binds the parent component’s maskShow property. Changes in the parent component’s maskShow value are automatically updated to the Mask component. However, changes in the visible state within the Mask component (such as when the user clicks to close the mask layer) are not automatically reflected in the parent component’s maskShow. According to the design of Vue 2.0 components, child components pass messages to their parents through custom events. In this case, we need to call the built-in $emit method inside the component to throw the new value of the internal visible. Then we need to listen for this custom event in the parent component, receive the value from the component, and assign it to maskShow.
this.$emit('close-mask',newVisibleValue);
Copy the code
<nut-mask
:visible="maskShow"
@close-mask="onMaskClose">
</nut-mask>
Copy the code
methods: {
onMaskClose: function (newVisibleVal) {
this.maskShow = newVisibleVal;
}
}
Copy the code
For normal Vue applications, this approach is acceptable. But there is a real need for “bidirectional binding” of Prop in a generic component library. As the developer of the component library, we hope that the user can use the component more conveniently, instead of looking at the API or the code, looking for the event name and parameter of each corresponding prop thrown by the component, writing the corresponding event handler to receive the thrown value, and then updating the property value of the prop binding. This is especially cumbersome when there are a large number of prop bindings.
The authors of the Vue framework have also taken note of the demand for bidirectional binding on Prop and added the “.sync “modifier to vue.js in version 2.3.0 to address this issue. To achieve “bidirectional binding” of a prop, add the “.sync “modifier.
<nut-mask
:visible.sync="maskShow">
</nut-mask>
Copy the code
So, does this conflict with the “one-way data flow” principle mentioned earlier? The answer is no. Because what you’re doing with the “.sync “modifier isn’t really” bidirectional binding “, it’s just syntactic sugar written like this:
<nut-mask
:visible="maskShow"
@update:visible="maskShow = $event">
</nut-mask>
Copy the code
There is no need for component users to manually listen for events and assignments. Inside the component, you still need to explicitly throw new values via events.
this.$emit('update:visible',newVisibleValue);
Copy the code
Interestingly, the “.sync “modifier has been around since Vue 1.0, removed in Vue 2.0, and returned in Vue 2.3. The new “.sync “modifier meets the basic needs of component library developers and does not violate the” one-way data flow “principle. It is a good design and has many applications in NutUI components.
SVG Sprite
As mentioned last time, we think SVG is a better icon solution for the mobile component library, and SVG ICONS can be reused through the symbol element. Now, let’s continue this conversation for another $10.
There is more than one so-called “SVG Sprite” solution, with different references, some based on location, some based on ID. Personally, I prefer to use the ID reference based approach, mainly because it is convenient and saves the trouble of caring about specific location information. The “SVG Sprite” approach, which uses the Symbol element, is based on ID references.
The Symbol element provides a way to combine SVG elements by defining SVG icon elements for future use using the Symbol element, which is not displayed by itself.
<svg>
<symbol id="icon1">
.
</symbol>
<symbol id="icon2">
.
</symbol>
<symbol id="icon3">
.
</symbol>
</svg>
Copy the code
Where SVG ICONS are needed, we can use SVG’s use element to refer directly to the corresponding Symbol element by ID, and also support customization of dimensions such as size and style.
<! -- Reference the corresponding symbol element by ID and set the size and fill color -->
<svg>
<use xlink:href="#icon1" width="30" height="30" style="fill:#000;" ></use>
</svg>
Copy the code
In WebPack, we can use SVG-sprite-loader to automatically generate SVG Sprite, which not only simplifies the use of SVG Sprite, but also solves the problem of on-demand packaging of SVG ICONS. Use SVG -sprite-loader to configure SVG files in webpack configuration file:
//webpack.config.js
{
test: /\.svg$/,
loader: 'svg-sprite-loader',
options: { ... }
}
Copy the code
Introduce the required SVG ICONS in the component Vue file.
import arrowTopIcon from '.. /svg/arrow-thin-up.svg';
Copy the code
The SVG icon can then be referenced by ID in the TEMPLATE of the Vue file. For this use, the ID defaults to the file name.
<svg>
<use xlink:href="#arrow-thin-up"/>
</svg>
Copy the code
When the project runs, all imported SVG ICONS are automatically converted into symbol elements and inserted into the page for use.
This loader also has other uses, interested partners can refer to the relevant documents [2], not repeated here.
Display of mobile components on PC
NutUI is positioned as a mobile component library. The official website (including documents and DEMO pages) needs to support PC and mobile access, so we responded to the page based on CSS3 media query to solve the adaptation problem. The real headache, however, is how to present these mobile component examples on the PC side. Earlier, we referenced some mobile component libraries and nested iframe on the page to present the mobile component DEMO page. To make it look more like a phone, you can also set the background image to a phone image and position the IFrame on the “screen” of the phone (as shown below).
This way, we can operate the DEMO page from the “phone screen”, which looks exactly like it’s on the phone. However, PC and mobile browsers are still different, and even when placed in an IFrame, it doesn’t change anything except the window size. Components still render differently from mobile browsers. In addition, differences at the event level, especially the absence of touch related events, also seriously affect the experience of mobile components on the PC side. After much consideration, we decided that the way to ensure the user experience was to guide users to view the DEMO on the mobile side, while the DEMO on the PC side was mainly about the presentation of documents. Therefore, we modified the scaffolding. When the document page is generated, a TWO-DIMENSIONAL code of the DEMO page entrance of the corresponding component is automatically dynamically generated at the page head. Users can scan the TWO-DIMENSIONAL code to view the DEMO page of the component on their mobile phones. The sample of each code on the DOCUMENT page of the PC side should be shown in screenshots. If you want to experience the live DEMO, please scan the code with your mobile phone and move to the DEMO page.
Admittedly, there are scenarios where viewing a component’s DEMO page on the PC is necessary, such as during a component’s development debugging phase. So we later added a less obvious entry to the PC documentation page to open the DEMO page of the corresponding component. Where exactly? Here to sell a mystery, interested partners can look.
Release NPM package
You are advised to install the NutUI component library in NPM mode.
npm install @nutui/nutui --save
Copy the code
In case you haven’t noticed, NutUI’s NPM package name is “@nutui/ NutUI”, which is influenced by the new package naming rules released by NPM some time ago. The new rules are much stricter than in the past. When a new package is issued, it will remove all punctuation marks in the name of the package and compare it with the existing package. If there are similarities, it will not be allowed to publish. On the other hand, the new rule recommends scope, which determines the existence of the “@nutui” part of our package name.
We also encountered some problems when releasing the NPM package, such as the total error 403 and so on, which was finally checked out as a third party NPM image problem. The NPM official repository is not accessible fast in China, so many Chinese developers usually use third-party images. These third-party images regularly synchronize the official NPM repository data, and there is usually no problem with the installation of NPM packages using third-party images. However, when publishing NPM packages, remember to check the official Registry first, because most third-party images are read-only and do not support publishing new packages, we certainly do not need to submit new packages to third-party images, do you agree?
Let’s stop talking here today, the product manager has come to change the requirements again…
For more content, please follow our team’s public account “Full Stack Exploration”.
Further reading
[1] http://nutui.jd.com/index.html#/intro
[2] https://www.npmjs.com/package/svg-sprite-loader