Vue is one of the hottest front-end frameworks out there, and its popularity is largely due to being developer-friendly. In particular, the SFC(single-file component) model is popular. Developers can complete the packaging of components by writing templates, JS logic and styles in a file at the same time. Compared with other methods, components are more cohesive and easy to maintain.

Introduction to render functions

After Vue 2.0, Vue added features like vDOM and the render function. Instead of directly generating the real DOM, the Template template is compiled into the render function, which then generates the virtual DOM. So instead of using the “traditional” template to build the UI, you can also use the render function, which gives you more control over the UI thanks to JavaScript’s power.

Vue recommends using templates to create your HTML in most cases. In some scenarios, however, you really need the full programming power of JavaScript, which is the Render function, which is closer to the compiler than template.

The Render function is familiar to developers of the React & React Native stack (after all, React only uses the render function to declare UI). Does that mean we can implement Vue components in a similar way? Before you get too excited, let’s take a look at some examples from the website.

What the hell is this? Instead of the familiar JSX function, render calls the very primitive createElement function. There is nothing wrong with this function. After all, this function was used when we first started React, and it works well for simpler UIs. Let’s look at a slightly more complex render function that doesn’t use JSX.

See such render function, there is no miss concise readable JSX, no words, it is probably a god or masochism 😂.

Embrace the JSX

While Vue doesn’t offer JSX support out of the box, it does offer the Babel plugin that allows us to build uIs using JSX just like React (90% similar). Blah blah blah, it’s time to start the VUE JSX journey. Before we start, let’s compare the two forms.

Table in Element UI

<el-table :data="tableData" style="width: 100%"> <el-table-column prop="date" label=" date" width="180"></el-table-column> <el-table-column prop="name" label=" name" Width ="180"></el-table-column> <el-table-column prop="address" label=" address" ></el-table-column> </el-table>Copy the code

The Table in the iview

 <Table :columns="columns1" :data="data1"></Table>
Copy the code

Intuitively, the second is more data-driven. Of course, we’re not comparing the two approaches here, but that’s a matter of opinion. Is there a way to make tables in element’s UI support the second way as well? There must be a way. JSX is the best way to write components. Let’s use JSX to encapsulate el-Table.

Objective the results

<table-panel
    showHeaderAction
    :data="taskList"
    :totalSize="totalSize"
    :columns="tableColumns"
    :onPageChange="handlePageChange"
    :onHeaderNew="handleOpenEditPage"
  />
Copy the code

JSX configuration

npm install\
  babel-plugin-syntax-jsx\
  babel-plugin-transform-vue-jsx\
  babel-helper-vue-jsx-merge-props\
  babel-preset-env\
  --save-dev
Copy the code
npm i babel-plugin-jsx-v-model -D
Copy the code

This module is optional and allows JSX to support V-Model directives

  • Configure the Babel plug-in (in the root directory.babelrcFile)
{
  "presets": ["env"]."plugins": ["jsx-v-model"."transform-vue-jsx"]}Copy the code

render with jsx

If we use inheritAttrs on the parent component, all attributes that are not inherent in the parent component fall back to the attr attribute on the DOM. If we use this.$attrs, we can use this. Pass to ElTable to inherit from ElTable.

In the render function above, we used three functions to return Header, Body, and Footer to compose our new component. So let’s take a look at what’s in the renderPanelBody function

renderPanelBody(h) { const props = { props: this.$attrs, }; const on = { on: this.$listeners, }; const { body } = this.$slots; if (body) return body.map(item => item); return ( <el-table ref="table" {... props} {... on} > { this.columns.map(item => this.renderTableColumn(h, item)) } </el-table> ); },Copy the code

So first we created oneattributesTo save on a non-parent componentpropsAs well asEvents, and then enable the expansion operator to convertattributesInjected into theel-tableComponent, via this.Each key in the Slots object isArrayType, so map is required to return. Returns if the user has no custom contentel-table. Pay attention to theel-tableThe content we passmapIt came in from the outsidecolumnsProperty to generateel-tableThe built-inel-table-column.

RenderTableColumn (h, colOptions) {/ / compatible iview form part of the configuration of colOptions. Prop = colOptions. Key | | colOptions. Prop. colOptions.label = colOptions.title || colOptions.label; const props = { props: colOptions, }; const { render } = colOptions; const slotScope = { scopedSlots: { default(scope) {return typeof render === 'function'? render(h, scope) : scope.row[colOptions.prop]; ,}}};return( <el-table-column {... props} {... slotScope} > </el-table-column> ); },Copy the code

Colums Table column configuration

[
  {
      title: 'Operator',
      key: 'operatorId', minWidth: 120, render? },]Copy the code

RenderPanelBody (renderTableColumn) renderPanelBody (renderTableColumn) renderPanelBody (renderTableColumn) renderPanelBody (renderTableColumn) renderPanelBody (renderTableColumn) For those of you who are familiar with this, native custom content is implemented by slot-scope, and by configuration, we can only configure a render function for each column to implement custom content. How do you make these two equivalent?

<el-table-column prop="enableStatus" label="State of goods"  min-width="140">
      <template slot-scope="scope">
        <span>{{getDisplayName(statusDropdown, scope.row.enableStatus)}}</span>
      </template>
</el-table-column>
Copy the code

A review of the VUE documentation shows that there is a $scopedSlots attribute on each Vue instance that has access to the scope slots. And that goes hand-in-hand with what we’re trying to accomplish.

ScopedSlots is particularly useful when developing a component using render functions.

So we can customize the contents of the default scope slot based on the render function configured outside.

const slotScope = { scopedSlots: { default(scope) { return typeof render === 'function' ? render(h, scope) : scope.row[colOptions.prop]; ,}}};Copy the code

At this point, our jSX-based component secondary encapsulation is complete. Overall, the following points need to be noted

  1. Each key in the this.$slots object is of type Array;
  2. The template in thev-ifInstructions can be usedifConditional statement implementation;
  3. The template in thev-forInstructions can use arraysmapLoop to achieve;
  4. In the templatev-modelInstructions can be usedbabel-plugin-jsx-v-modelPlug-in;
  5. Data binding in template (:key="value") to usekey={this.value}To achieve

Other Vue JSX related questions can be answered in babel-plugin-transform-VUe-jsx and its related issues.