Vue.js is a set of progressive frameworks for building user interfaces. We can use simple apis to implement reactive data binding and composite view components.

From maintaining views to maintaining data, vue.js lets us develop applications quickly. However, as the business code becomes larger and larger, there are more and more components, and the serious coupling of component logic makes the code maintenance very difficult.

At the same time, the vue.js interface and syntax are very free, and there are several ways to implement the same function. Everyone has a different way to solve problems, so the code is different, and there is no norm within the team.

This paper aims to enumerate reasonable solutions from different aspects of component development as a reference for establishing component specifications.

navigation

  • A component
  • Intercomponent communication
  • The business has nothing to do
  • The namespace
  • Context-free
  • Data flattening
  • Two-way binding of data using custom events
  • Optimize DOM manipulation with custom Watcher
  • Project skeleton

A component

Component is a module that has certain functions and functions are relatively independent among different components. Components can be a button, an input field, a video player, and so on.

Reusable components, high cohesion, low coupling.

So, what constitutes a component. Take video, a native component of the browser, as an example to analyze its components.

<video
  src="example.mp4"
  width="320"
  height="240"
  onload="loadHandler"
  onerror="errorHandler">
  Your browser does not support the video tag.
</video>Copy the code

As you can see from the examples, components consist of states, events, and nested fragments. The state is the current data or properties of the component, such as SRC, Width, and height in video. Event refers to the behavior that the component triggers some operations at a specific time. For example, when video resources fail to load results or trigger corresponding events to perform processing. Snippets are content nested in component tags that can be displayed under certain conditions, such as a prompt message when the browser does not support the video tag.

In the Vue component, the state is called props, the event is called Events, and the fragment is called slots. The components of a component can also be understood as interfaces to the component. A good reusable component should define a clear public interface.

  • Props allows external environments to pass data to components
  • Events allows a component to trigger side effects in an external environment
  • Slots allows external environments to combine additional content into components.

Using VUE to expand the video component, construct a playlist supporting component myVideo:

<my-video
  :playlist="playlist"
  width="320"
  height="240"
  @load="loadHandler"
  @error="errorHandler"
  @playnext="nextHandler"
  @playprev="prevHandler">
  <div slot="endpage"></div>
</my-video>Copy the code

The myVideo component has a clear interface to receive playlists, player width and height status, trigger loading success or failure, play the previous or the next event, and can customize the end of the playback page, can be used to insert ads or display the next video message.

Intercomponent communication

In vue. js, the parent-child component relationship can be summarized as props Down, events up. The parent component passes data down to the child component through props, and the child component sends messages to the parent component through Events. See how they work.





The business has nothing to do

named

Component names should be business-neutral. Components should be named according to their functionality.

For example, a list showing the company’s departments, with each item as a component, and named DepartmentItem. At this point, there is a requirement to display a list of team members in the same style as the department list. Obviously, DepartmentItem is not the right name.

Therefore, reusable components should be named after their roles and functions instead of being associated with services. Item, ListItem, Cell. Consider naming UI frameworks like Bootstrap and ElementUI.

Business data independence

Reusable components are only responsible for UI presentation and some interaction and animation. How to get the data is irrelevant to them, so do not get the data inside the component, or anything that interacts with the server. Reusable components implement only UI-related functions.

The component functions

By constraining the responsibilities of components, they can be better decoupled, and they can know what functions they do and don’t need to do.

Components can be divided into general purpose components (reusable components) and business components (disposable components).

Reusable components implement common functions (which do not vary depending on the location or scenario in which the component is used) :

  • The UI display
  • Interactions with users (events)
  • Animation effects

Business components implement business-oriented functions:

  • To get the data
  • Operations related to VUEX
  • Buried point
  • Reference reusable components

Reusable components should minimize dependence on external conditions and all vuEX related operations should not occur in reusable components.

The component should avoid dependencies on its parent, and do not manipulate the parent example with this.$parent. The parent component also does not refer to the child’s example through this.$children, but interacts with it through the child’s interface.

The namespace

In addition to defining a clear public interface, reusable components also need namespaces. Namespaces avoid conflicts with browser reserved tags and other components. In particular, namespaces can avoid many naming conflicts when projects reference external UI components or components migrate to other projects.

<xl-button></xl-button>
<xl-table></xl-table>
<xl-dialog></xl-dialog>.Copy the code

Business components can also have command space, distinguished from generic components. Business components are represented here by st (Section).

<st-recommend></st-recommend>
<st-qq-movie></st-qq-movie>
<st-sohu-series></st-sohu-series>Copy the code

Context-free

Again, reusable components should minimize their dependence on external conditions. Do not break a single functional component into smaller parts without special requirements and without the individual component being too heavy.

<table-wrapper>            
  <table-header slot="header" :headers="exampleHeader"></table-header>            
  <table-body slot="body" :body-content="exampleContents"></table-body>          
</table-wrapper>Copy the code

The TableHeader component and the TableBody component depend on the context in which the TableWrapper component is nested. You can have a better solution:

<xl-table :headers="exampleHeader" :body-content="exampleContents"></xl-table>Copy the code

The context-free principle lowers the barrier to component usage.

Data flattening

When defining a component interface, try not to pass the entire object as a prop.

<! - counter example -- -- >
<card :item="{ title: item.name, description: item.desc, poster: item.img }></card>Copy the code

Each prop should be a simple type of data. This has the following advantages:

  • Clear component interfaces
  • Props verification convenience
  • There is no need to reconstruct an object when the key name in the object returned by the server is different from the component interface
<card
  :title="item.name"
  :description="item.desc"
  :poster="item.img">
</card>Copy the code

The flat props gives us a more intuitive understanding of the interface to the component.

Two-way binding of data using custom events

Sometimes a state needs to be changed both inside and outside the component.

For example, for modal box show and hide, the parent component can initialize the modal box display, and the close button inside the modal box component can make it hide. A good way to do this is to change the value in the parent component using a custom event:

<modal :show="show" @input="show = argument[0]"></modal>Copy the code
<! -- Modal.vue -->

<template>
  <div v-show="show">
    <h3>The title</h3>
    <p>content</p>
    <a href="javascript:;" @click="close">Shut down</a>
  </div>
</template>

<script>
  export default {
    props: {
      show: String
    },
    methods: {
      close () {
        this.$emit('input'.false)}}}</script>Copy the code

When the user clicks the close button, the Modal component sends an Input custom event to the parent component. When the parent listens for an input event, it sets show as the first argument to the event callback.

You can use the syntax sugar V-model:

<modal v-model="show"></model>Copy the code

For a component’s V-Model to work, it must:

  • Accept a value attribute
  • The input event is raised when a new value is present

Note: Since input events per component can only be used to bidirectionally bind one data, do not use the V-model when there are multiple data that need to be synchronized upward. Instead, use multiple custom events and synchronize new values in the parent component.

Optimize DOM manipulation with custom Watcher

In development, some logic cannot use data binding to avoid the need to manipulate the DOM. For example, the playback of a Video requires synchronization between the playback operation of the Video object and the playback state within the component. You can use custom Watcher to optimize DOM operations.

<! -- MyVideo.vue -->

<template>
  <div>
    <video ref="video" src="src"></video>
    <a href="javascript:;" @click="togglePlay">{{ playing ? 'Pause' : 'play'}}</a>
  </div>
</template>

<script>
  export default {
    props: {
      src: String // Play the address
    },
    data () {
      return {
        playing: false // Whether it is playing}},watch: {
      // Perform the corresponding operation when the playback status changes
      playing (val) {
        let video = this.$refs.video
        if (val) {
          video.play();
        } else{ video.pause(); }}},method: {
      // Switch the playback state
      togglePlay () {
        this.playing = !this.playing
      }
    }
  }
</script>Copy the code

In the example, the custom Watcher performs a play or pause operation when it listens for changes in the playing state. When dealing with the playing state of a video, you only need to focus on the playing state.

Project skeleton

A single component should be as simple as possible under the premise of independent function. The simpler the component, the stronger the reusability. When your code for implementing a component, not including CSS, is hundreds of lines long (depending on the business), consider breaking it up into smaller components.

When components are simple enough, they can be freely combined into a larger business component to implement our business functions. So, ideally, there are only two levels of reference to a component. Business components reference generic components.

We can get a flat structure.





In a large project, the reference relationships between components are more complex. If a single page application has multiple routes, each route component is overweight and the module needs to be split. The component structure should look like the following figure.





Building our project along these lines, the final source directory structure (excluding the build flow files) :

│ App. Vue# Top-level components│ client - entry.js  # front-end entry fileconfig.js        # config file
│  main.js          Main entry file│ ├ ─ API# interface API├ ─ assets# static resources├ ─ components# Generic components├ ─directives        # Custom instruction├ ─ the mock# the Mock data├ ─ plugins# Custom plug-ins├ ─ the router# Route configuration├ ─ sections# Business Components├ ─ store# Vuex Store├ ─ utils# Tools module└ ─ views# Route page componentCopy the code

Generic components can also be distinguished between container components, layout components, and other functional components.