Component Design Requirements

In our daily code development process, the use of components is essential, and we will also encapsulate components. However, there are various styles of writing components, and there is no unified guideline. The best solution to reduce access costs is to write good documentation when designing components to ensure a single responsibility and not to couple business, which reduces costs to a large extent.

Next, let's talk about the specific requirements of component design.

Component Has a Single Responsibility

For example, the product list filter condition component provides a filter function and outputs a selected form to provide the external component to request list data.

In order to ensure the single function of the component, other modules are not allowed to add filter conditions to this component. This component can be used directly without modifying the component. Examples are as follows.

<template>
  <!--filter-->
  <el-form
    style="padding-right: 20px"
    inline
    class="goods-list-filter"
    :model="filter"
    ref="filter"
    label-position="right"
    label-width="120px"
  >
    <!-- keyword search -->
    <el-form-item label="Item title:" prop="itemMainTitle">
      <el-input
        class="w220"
        placeholder="Please enter"
        size="mini"
        v-model.trim="filter.itemMainTitle"
        @input="checkFilterParam('itemMainTitle', 80)"
      />
    </el-form-item>

    <!-- barcode barcode search -->
    <el-form-item v-if="showBarcode" label="Barcode:" prop="barcode">
      <el-input
        class="w220"
        placeholder="Barcode Search"
        prefix-icon="el-icon-search"
        size="mini"
        v-model.trim="filter.barcode"
        @input="checkFilterParam('barcode', 20)"
      />
    </el-form-item>

    <el-form-item label="Product Category:" prop="categoryId">
      <goods-category-cascader
        class="w220"
        :active-goods-type="goodsType"
        :with-all="true"
        size="mini"
        v-model="filter.categoryId"
        :reset-selecte="resetSelecte"
        @changeRestStatus="changeRestStatus"
      />
    </el-form-item>

    <el-form-item label="Sale Type:" prop="saleType">
      <el-select class="w220" v-model="filter.saleType" size="mini" placeholder="Sale Type">
        <el-option label="All types" :value="null"></el-option>
        <el-option label="Normal Sale" :value="SaleChannel.Normal"></el-option>
        <el-option label="Activity Sale" :value="SaleChannel.Activity"></el-option>
      </el-select>
    </el-form-item>

    <!-- Unloading status -->
    <el-form-item v-if="showStatus" prop="itemActiveStatus" label="Status:">
      <el-select class="w220" v-model="filter.itemActiveStatus" size="mini">
        <el-option label="All status" :value="null"></el-option>
        <el-option label="Enable" :value="GoodsStatus.PullShelves"></el-option>
        <el-option label="disable" :value="GoodsStatus.RemoveShelves"></el-option>
      </el-select>
    </el-form-item>

    <el-form-item label="Whether refund is supported:" prop="notAllowRefund">
      <el-select class="w220" v-model="filter.notAllowRefund" size="mini" placeholder="Whether refund is supported">
        <el-option label="All status" :value="null"></el-option>
        <el-option label="Support" :value="AllowRefund.Yes"></el-option>
        <el-option label="Not supported" :value="AllowRefund.No"></el-option>
      </el-select>
    </el-form-item>

    <el-form-item v-if="showPerformance" label="Performance subject:" prop="performanceBuId">
      <slot></slot>
    </el-form-item>

    <el-button type="primary" @click="onFilter">Search</el-button>
    <el-button v-if="!showLabel" plain @click="handleReset" style="margin-top: 2px">Reset</el-button>
  </el-form>
</template>

Small in Size

In order to prevent components from decoupling business, it is necessary to ensure the purity of components as much as possible. As mentioned above, the filter condition component only needs to ensure the correctness of the output filter field, and the value of the filter condition can be provided by external components (like product classification data).

Why does the data need to be passed in externally

This is because of the filter conditions that exist in general, the list will certainly have. Sometimes the backend only returns the id when returning data, and we need to parse the corresponding name through the id. This requires the use of corresponding data. This is where data can be shared. Coupling and bulk can also be further reduced.

Follow Usage Habits

Many components have their idiomatic methods. For example, form components will use the v-model to bind form values. For the modal box, we will use the visible or show() method to control the display.

Following these idiomatic methods can reduce the mental burden on the developer and maintain the uniformity of the interface. Also, this is easier to combine/integrate. For example, Form.Item in Ant-design depends on this protocol). Let's use the avatar selector as an example.

export default {
  name: 'AvatarSelect',
  props: ['avatar'],
	methods: {
    handleSelect() {
			// ...
			this.$emit('avatar-change', value)
		}
  }
}

<avatar-select :avatar="form.avatar" @avatar-change="form.avatar = $event" />

Beware of Style Pollution

Each component should have a namespace. And this namespace is best to avoid conflicts with other components.

<template>
<div class="my-component">
     ...
   </div>
</template>

<style lang="scss">
   .my-component {
// Subordinate component styles
}
<style>

Always turning on Scoped CSS is the easiest way to prevent pollution.

<style scoped>
.example {
  color: red;
}
</style>

<template>
  <div class="example">hi</div>
</template>

Don't nest CSS classes more than two levels. Otherwise, readability and maintainability will be greatly compromised.

.foo {
   .bar {
.baz {
// Hundreds of thousands of lines of code
}
// Hundreds of thousands of lines of code
}
}

It is recommended to use BEM as a CSS class name to associate CSS semantics with component structure.

.my-component {
  &__head {/*...*/}
  &__footer {/*...*/}
}

Customize the style of the component by class or style. Don't use it to style the label directly.

<el-input />
<style>
   .el-input {
// style override
}
</style>

<el-input class="my-input" />
<style>
.my-input {
// style override
}
</style>

Component Extension

Since we are a basic product, the components provided should be as rich as possible. Enrichment does not mean adding various judgments to components but weighing the pros and cons between single responsibility, volume, and coupling. You can also create the same type of components and export them in the same file index.js.

Unidirectional Data Flow

Only pass state down to the lower level via props. Don't procedurally affect the state of subordinate components by means of refs.

Subordinate components communicate to the superior only through events.

Subordinate components are not allowed to modify props source data.

Follow the v-model, update:* protocol.

v-model follows the principle of data immutability. That is, you cannot modify the value directly, but should create a new value.

// Assume value is a list
handleRemove(item) {
   const idx = this.value.indexOf(item)
   if (idx !== -1) {
     const newValue = [...this.value]
newValue.splice(idx, 1)
     this.$emit('input', newValue)
}
}

Use computed to derive data, keeping the source data pure.

Separate business state and view state. For example, the activation state of a list item is the view state, and the data returned by the backend is the business state.

// Recommend
<item :checked="item.id in checked" @click="checked.add(item.id)" />

// Not recommended: Add a field directly to the source data, polluting the original data.
<item :checked="item.checked" @click="item.checked = true" />


Leave a reply



Submit