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 中找到答案。

相關文章
相關標籤/搜索