vue jsx 不彻底指北

Vue 做为日前最为火热的前端框架之一,其流行程度很大部分得益于对开发者友好。尤为是SFC(单文件组件)的模式深得人心,开发者经过在一个文件里同时书写模板,JS 逻辑以及样式,就能完成组件的封装,相比其余方式,组件更加内聚,便于维护。html

render 函数简介

在 Vue 2.0 版本以后,Vue 增长了 vdom 以及 render函数等新特性, template 模板并不会直接生成真实的 dom, 而是先编译为 render 函数, 再由 render函数 生成虚拟DOM。 所以除了使用"传统"的模板来构建 UI,也可使用 render 函数,借助于 JavaScript 的 power,开发者能够更灵活的控制 UI。前端

Vue 推荐在绝大多数状况下使用 template 来建立你的 HTML。然而在一些场景中,你真的须要 JavaScript 的彻底编程的能力,这就是 render 函数,它比 template 更接近编译器。vue

于 React 栈(React & React Native)的开发者而言, render 函数再熟悉不过了(毕竟 React 中只能经过 render 函数来声明 UI) 。那是否是说明,咱们能够用相似的方式来实现 Vue 组件。别高兴太早,咱们先来看看官网给的例子吧。git

image

这是什么鬼?render 函数里竟然不是熟悉的 jsx,而是调用了很是原始的 createElement函数。这个本没有什么毛病,毕竟刚入门 React 时,是用的这个函数,且对于比较简单的UI ,这个函数足以胜任。咱们再来看一个稍微复杂一些的没有使用 jsxrender 函数。github

image

看到这样的 render 函数, 有没有很怀念简洁可读的jsx,没有的话,那大概是个大神或者自虐狂吧😂。npm

拥抱 JSX

虽然Vue并无提供开箱即用的jsx 支持,但其提供了babel 插件,让咱们也能像 React 同样(90%类似)使用 jsx 来 构建 UI。废话说了一堆,是时候开始 vue jsx 之旅了。开始以前先对比下两种表格的使用方式。编程

element ui 中的 Table数组

<el-table
  :data="tableData"
  style="width: 100%">
  <el-table-column prop="date" label="日期" width="180"></el-table-column>
  <el-table-column prop="name" label="姓名" width="180"></el-table-column>
  <el-table-column prop="address" label="地址"></el-table-column>
</el-table>
复制代码

iview 中的 Tablebash

<Table :columns="columns1" :data="data1"></Table>
复制代码

直观的看来,第二种更符合数据驱动的思想。固然咱们在这里并不比较这两种方式孰优孰劣,仁者见仁智者见智。那么是否有办法也让 element ui 中的 table 也支持第二种方式呢?办法确定是有的。用jsx 写组件应该再合适不过了,下面咱们就以 jsx 的方式来对 el-table 进行二次封装,前端框架

目标结果

<table-panel
    showHeaderAction
    :data="taskList"
    :totalSize="totalSize"
    :columns="tableColumns"
    :onPageChange="handlePageChange"
    :onHeaderNew="handleOpenEditPage"
  />
复制代码

jsx 相关配置

npm install\
  babel-plugin-syntax-jsx\
  babel-plugin-transform-vue-jsx\
  babel-helper-vue-jsx-merge-props\
  babel-preset-env\
  --save-dev
复制代码
npm i babel-plugin-jsx-v-model -D
复制代码

这个模块是可选的,可让 jsx 支持 v-model指令

  • 配置Babel 插件(根目录下.babelrc文件)
{
  "presets": ["env"],
  "plugins": ["jsx-v-model", "transform-vue-jsx"]
}
复制代码

render with jsx

注,在实现该组件过程当中,并无直接继承 ElTable,而是利用inheritAttrs特性,父组件上非父props 的属性都会回退到 dom 上的 attr 属性,组件内能够经过 this.$attrs获取到这些属性,并传递给ElTable 达到"继承"ElTable 的效果。

image

在上面的render 函数中,咱们用了三个函数分别来返回 Header, Body, 以及 Footer 来组合咱们的新组件。咱们先来看下renderPanelBody函数中究竟是什么东西

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>
      );
    },
复制代码

首先咱们先建立了一个attributes来保存非父组件上的 props 以及Events,而后使展开运算符就能够将 attributes注入到el-table组件内,经过this.slots.body 拿到 `<div slot='body'></div>`名字为 body 的具名 `slot`,若是定义了这个 `slot`,则直接返回用户定义的内容,因为 this.slots对象中的每一个key均是Array类型,所以须要 map 再返回。若是用户没有自定义内容,则返回el-table。注意到 el-table的内容咱们经过map 外面传递进来的 columns属性来生成el-table 中内置的 el-table-column

renderTableColumn(h, colOptions) {
      // 兼容 iview 表格的部分配置
      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>
      );
    },
复制代码

colums 表格列配置

[
  {
      title: '操做人',
      key: 'operatorId',
      minWidth: 120,
      render?
    },
]
复制代码

renderPanelBody中能够知道,renderTableColumn中的第二个参数为用户传进来的表格列配置数组中的一项,它应该由 el-table-column的 props 构成。对于熟悉的同窗可能知道原生的自定义内容是经过 slot-scope 的方式实现的,而经过配置的方式,咱们只能给每一列配置一个render函数来实现自定义内容。怎么才能让这二者等价呢?

<el-table-column prop="enableStatus" label="商品状态"  min-width="140">
      <template slot-scope="scope">
        <span>{{getDisplayName(statusDropdown, scope.row.enableStatus)}}</span>
      </template>
</el-table-column>
复制代码

查看vue文档得知,每一个vue 实例上存在一个$scopedSlots的属性,它能够访问做用域插槽。这和咱们要实现的需求不谋而和。

scopedSlots, 用来访问做用域插槽。对于包括 默认 slot 在内的每个插槽,该对象都包含一个返回相应 VNode 的函数。**vm.scopedSlots 在使用渲染函数开发一个组件时特别有用**。

所以咱们能够根据 外面配置的 render 函数来自定义默认做用域插槽的内容了。

const slotScope = {
        scopedSlots: {
          default(scope) {
            return typeof render === 'function' ? render(h, scope)
              : scope.row[colOptions.prop];
          },
        },
      };
复制代码

写到这里,咱们基于 jsx 的 组件二次封装也就完成了。总体下来,如下几点须要注意

  1. this.$slots 对象中的每一个 key,均是 Array 类型的;
  2. template 中 的 v-if 指令能够用 if 条件语句实现;
  3. template 中 的 v-for 指令能够用 数组map 循环来实现;
  4. template 中的 v-model指令可使用 babel-plugin-jsx-v-model插件;
  5. template 中的 数据绑定(:key="value")使用 key={this.value}的方式来实现

其余 Vue jsx 相关的问题均可以在babel-plugin-transform-vue-jsx及其相关 issue 中找到答案。

相关文章
相关标签/搜索