Use render function to encapsulate highly scalable components

Use render function to encapsulate highly scalable components

need:

In background management, there are often data display requirements with the following layout:

It looks like a table but not a table, and like a form but not a form. In fact, it looks like a table, and the data presented is an object, which is the same as the bound value of the form. I call it a form-style table.

The columns with deep styles are titles, and the columns with shallow styles are the values ​​corresponding to the titles. The data is often returned by the server, and the titles are often of fixed width. The values ​​may vary. For example, when displaying a picture, the value is 01, and you need to display yes or no. Sometimes you need to add a modification button so that users can modify certain values, and you also need to set a column to span several columns.

Let's first look at an implementation based on element ui

Bad implementation:

I saw an implementation in the project I took over. Let’s see how to use it first.

<FormTable :data="lessonPackageArr" :fleldsInfo="lessonPackageInfo" :maxColumn="3" label-width="120px">
  <template #presentedHours="{ data }">
    <div class="flex-box between">
      <span>
        {{ data.presentedHours }}
      </span>
      <span class="column-btn" @click="editPresentedHours(data)">Edit</span>
    </div>
  </template>
  <template #gifts="{ data }">
    <div class="flex-box between">
      <span>
        {{ data.gifts }}
      </span>
      <span class="column-btn" @click="editPresentedHours(data)">Edit</span>
    </div>
  </template>
</FormTable>


The lessonPackageInfo object has the following structure:

// An object used to configure the title column and the fields corresponding to the title column // type specifies the type of value. Now the component internally sets which types of values ​​may be displayed // For services, it returns 1 0. If a number needs to be displayed, provide a map_data to map // column attribute settings across columns // Need to customize the display content to provide slot
lessonPackageInfo: {
    orderType: { type: 'option', desc: 'Course package category', map_data: { 1: 'First order', 2: 'Renewal', 5: 'Free course' } },
    combo: { type: 'text', desc: 'Package name' },
    presentedHours: { type: 'text', desc: 'Free class hours', slot: true },
    price: { type: 'text', desc: 'Standard price' },
    gifts: { type: 'text', desc: 'Gift', column: 3, slot: true },
  }


  • Props is not intuitive enough and has many configuration items
  • Not completely data-driven

Why is it bad to have too many configuration items for a component?

This requirement is very fixed. The component input, i.e. props , should be minimized, the component function should be maximized, and default values ​​should be provided for props as much as possible, so as to improve the team's development efficiency.

Why is it bad to be not completely data-driven?

This component is not completely data-driven. If you need to customize the display columns, you need to write a template.

If there are many columns that need to be customized, you have to write a lot of template codes. If you want to extract them again, you can only encapsulate the components again. If you don’t extract them, the template codes may expand. You may often see template with 500 lines per line? The expanded template code makes component maintenance difficult, requiring switching back and forth between template and js code. Furthermore, to add a column of custom data, you need to modify at least two places.

Why do we need to be fully data-driven?

Although there are slots to extend components, we should use them less when writing business components and try to use data-driven templates instead. Because the data is js code, when the component code expands, it is easy to extract the js code into a separate file. If you want to extract the slot code, you can only encapsulate the component again.

The design concept of the three major front-end frameworks is data-driven templates, which is an important feature that distinguishes them from jQuery and is also the principle we follow first when encapsulating business components.

After looking at the issues with component usage, let's look at the component code:

<template>
  <div v-if="tableData.length" class="form-table">
    <div v-for="(data, _) in tableData" :key="_" class="table-border">
      <el-row v-for="(row, index) in rows" :key="index">
        <el-col v-for="(field, key) in row" :key="key" :span="getSpan(field.column)">
          <div v-if="(field.disabled && data[key]) || !field.disabled" class="column-content flex-box between">
            <div class="label" :style="'width:' + labelWidth">
              <span v-if="field.required" class="required">*</span>
              {{ field.desc }}
            </div>
            <div class="text flex-item" :title="data[key]">
              <template v-if="key === 'minAge'">
                <span>{{ data[key] }}</span>
                -
                <span>{{ data['maxAge'] }}</span>
              </template>
              <template v-else-if="key === 'status'">
                <template v-if="field.statusList">
                  <span v-if="data[key] == 0" :class="field.statusList[2]">{{ field.map_data[data[key]] }}</span>
                  <span v-else-if="data[key] == 10 || data[key] == 34" :class="field.statusList[1]">
                    {{ field.map_data[data[key]] }}
                  </span>
                  <span v-else :class="field.statusList[0]">{{ field.map_data[data[key]] }}</span>
                </template>
                <span v-else>{{ field.map_data[data[key]] }}</span>
              </template>

              <slot v-else :name="key" v-bind:data="data">
                <TableColContent
                  :dataType="field.type"
                  :metaData="data[key]"
                  :mapData="field.map_data"
                  :text="field.text"
                />
              </slot>
            </div>
          </div>
        </el-col>
      </el-row>
    </div>
  </div>
  <div v-else class="form-table empty">No data</div>
</template>

<script>
  import TableColContent from '@/components/TableColContent'
  export default {
    name: 'FormTable',
    components:
      TableColContent,
    },
    props: {
      // data: {
        required: true,
        type: [Object, Array, null],
      },
      // Field information fleldsInfo: {
        required: true,
        type: Object,
        // className: { type: "text", desc: "Class Name", column: 3 },
      },
      // Maximum number of columns to display maxColumn: {
        required: false,
        type: Number,
        default: 2,
      },
      labelWidth: {
        required: false,
        type: String,
        default: '90px',
      },
    },
    data() {
      return {}
    },
    computed: {
      tableData() {
        if (!this.data) {
          return []
        }
        if (this.data instanceof Array) {
          return this.data
        } else {
          return [this.data]
        }
      },
      rows() {
        const returnArray = []
        let total = 0
        let item = {}
        for (const key in this.fleldsInfo) {
          const nextTotal = total + this.fleldsInfo[key].column || 1
          if (nextTotal > this.maxColumn) {
            returnArray.push(item)
            item = {}
            total = 0
          }
          total += this.fleldsInfo[key].column || 1
          item[key] = this.fleldsInfo[key]
          if (total === this.maxColumn) {
            returnArray.push(item)
            item = {}
            total = 0
          }
        }
        if (total) {
          returnArray.push(item)
        }
        return returnArray
      },
    },
    methods: {
      getSpan(column) {
        if (!column) {
          column = 1
        }
        return column * (24 / this.maxColumn)
      },
    },
  }
</script>

What are the questions?

  • The template has too many conditional judgments, which is not elegant
  • To customize the display columns, you also need to introduce TableColContent , which increases the complexity of the component

TableColContent still performs conditional judgment on the type of configuration item

Part of the code:

<span v-else-if="dataType === 'image' || dataType === 'cropper'" :class="className">
  <el-popover placement="right" title="" trigger="hover">
    <img :src="metaData" style="max-width: 600px;" />
    <img slot="reference" :src="metaData" :alt="metaData" width="44" class="column-pic" />
  </el-popover>
</span>


After analyzing the above implementation problems, let's look at the good implementation:

Good implementation:

First look at how to use:

<template>
  <ZmFormTable :titleList="titleList" :data="data" />
</template>
<script>
  export default {
    name: 'Test',
    data() {
      return {
        data: {}, // Get titleList from the server: [
          { title: 'Name', prop: 'name', span: 3 },
          {
            title: 'Classroom Works',
            prop: (h, data) => {
              const img =
                (data.workPic && (
                  <ElImage
                    style='width: 100px; height: 100px;'
                    src={data.workPic}
                    preview-src-list={[data.workPic]}
                  ></ElImage>
                )) ||
                ''
              return img
            },
            span: 3,
          },
          { title: 'Work Comments', prop: 'workComment', span: 3 },
        ],
      }
    },
  }
</script>


Component description: titleList is the column configuration of the component, an array. The element title attribute is the title, prop specifies the field to be taken from data, and span specifies the number of rows spanned by this column value.

prop supports string and function, which is a way to achieve custom display. When this function is very large, it can be extracted into a separate js file, or the entire titleList can be extracted into a separate js file.

How are the parameters h and data passed in? Or where is this function called?

h is the createElement function, data is the data from inside the component, and it is the same value as the data passed in by the parent component.

When the first argument of a normal function is h, it is a render function.

This method is much simpler to use.

Take a look at the internal implementation:

 <template>
  <div class="form-table">
    <ul v-if="titleList.length">
      <!-- titleInfo is the transformed titleList-->
      <li
        v-for="(item, index) in titleInfo"
        :key="index"
        :style="{ width: ((item.span || 1) / titleNumPreRow) * 100 + '%' }"
      >
        <div class="form-table-title" :style="`width: ${titleWidth}px;`">
          <Container v-if="typeof item.title === 'function'" :renderContainer="item.title" :data="data" />
          <span v-else>
            {{ item.title }}
          </span>
        </div>
        <div class="form-table-key" :style="`width:calc(100% - ${titleWidth}px);`">
          <Container v-if="typeof item.prop === 'function'" :renderContainer="item.prop" :data="data" />
          <span v-else>
            {{ ![null, void 0].includes(data[item.prop] && data[item.prop]) || '' }}
          </span>
        </div>
      </li>
    </ul>
    <div v-else class="form-table-no-data">No data</div>
  </div>
</template>

<script>
  import Container from './container.js'
  export default {
    name: 'FormTable',
    components:
      Container,
    },
    props: {
      titleWidth: {
        type: Number,
        default: 120,
      },
      titleNumPreRow: {
        type: Number,
        default: 3,
        validator: value => {
          const validate = [1, 2, 3, 4, 5, 6].includes(value)
          if (!validate) {
            console.error('titleNumPreRow indicates that a row has a title field pair, which can only be an even number from 1 to 6, the default is 3')
          }
          return validate
        },
      },
      titleList: {
        type: Array,
        default: () => {
          return []
        },
        validator: value => {
          const validate = value.every(item => {
            const { title, prop } = item
            return title && prop
          })
          if (!validate) {
            console.log('The element of the passed titleList attribute must contain the title and prop attributes')
          }
          return validate
        },
      },
      data: {
        type: Object,
        default: () => {
          return {}
        },
      },
    },
  }
</script>
<!-- Style is not critical, omitted-->

The way to implement custom display is not using dynamic slots, but using a functional component Container , which receives a render function as prop .

export default {
  name: 'Container',
  functional: true,
  render(h, { props }) {
    return props.renderContainer(h, props.data)
  },
}

Call the function passed in by titleList inside Container .

Summarize:

  • Prioritize data-driven when packaging components
  • The first parameter of a normal function is h, which is the rendering function.
  • Some people may not be used to writing JSX, but both writing methods are compatible.

This is the end of this article about using render function to encapsulate highly scalable components. For more relevant content about using render function to encapsulate highly scalable components, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • VUE render function usage and detailed explanation
  • Vue's Render function
  • Detailed explanation of the use of Vue.js render function
  • Set ref operation for subcomponents through render function in Vue
  • Vue render function dynamically loads img's src path operation
  • Vue Render function principle and code example analysis
  • Detailed explanation of the render function in Vue

<<:  Code for implementing simple arrow icon using div+CSS in HTML

>>:  The process of installing Docker in Linux system

Recommend

Implementing a simple carousel based on JavaScript

This article shares the specific code of JavaScri...

Linux tac command implementation example

1. Command Introduction The tac (reverse order of...

Detailed explanation of Windows time server configuration method

Recently, I found that the company's server t...

Basic operations of mysql learning notes table

Create Table create table table name create table...

Binary Type Operations in MySQL

This article mainly introduces the binary type op...

WeChat applet realizes the effect of swiping left to delete list items

This article shares the specific code for WeChat ...

How to run MySQL using docker-compose

Directory Structure . │ .env │ docker-compose.yml...

Html easily implements rounded rectangle

Question: How to achieve a rounded rectangle usin...

Detailed explanation of hosts file configuration on Linux server

Linux server hosts file configuration The hosts f...

How to recover deleted MySQL 8.0.17 root account and password under Windows

I finished learning SQL by myself not long ago, a...

4 solutions to mysql import csv errors

This is to commemorate the 4 pitfalls I stepped o...

Analysis of MySQL Aborted connection warning log

Preface: Sometimes, the session connected to MySQ...

The connection between JavaScript and TypeScript

Table of contents 1. What is JavaScript? 2. What ...