Vue Render介紹和一些基本的例項

宗仔GEG發表於2019-03-04

為啥研究這個?在之前開發元件庫的過程中,遇到了許多遺留的問題,包括資料模板渲染、元件按需載入、引入自定義元件插槽等等,所以為了修復和避免這些問題,學習一波更接近編譯器的編寫方式,看看如何通過這種完全程式設計方式來解決一波這些問題~當然這裡只是一些最基本的使用和探索,因為官網例子太少了,只能一個個自己搭=。=

Vue 推薦在絕大多數情況下使用 template 來建立你的 HTML。然而在一些場景中,你真的需要 JavaScript 的完全程式設計的能力,這就是render 函式,它比 template 更接近編譯器。從官網複製的,慌得一批,其實簡單來說就是以函式的方式寫HTML,可控性更強一些~

當然,官網已經給出了一個使用template來編寫的不方便的demo,所以在這裡就不反覆提起了,初次使用或者有興趣的大佬可以直接戳這個連結瞭解一下~Vue Render

本篇文章主要介紹了以下幾點

瞭解基本概念的客官可以直接下拉到例項,例項已上傳github

  • 虛擬DOM
  • CreateElement函式
  • 【起步】最基本的例項
  • 【進階】包含屬性配置較完整的例項
  • 【進階】CreateElement中slot屬性的用法
  • 【深入】CreateElement中scopedSlots的用法
  • 【換口氣兒】Render中的JSX配置和用法
  • 【深入】函式式元件

Github傳送門

虛擬DOM

1、什麼是DOM?

DOM 就是瀏覽器解析 HTML 得來的一個樹形邏輯物件。

2、什麼是虛擬DOM?

用 Object 來代表一個節點,這個節點叫做虛擬節點( Virtual Node )簡寫為 VNode,由 VNode 樹組成虛擬DOM。

3、為什麼要用虛擬DOM?

Web 頁面的大多數操作和邏輯的本質就是不停地修改 DOM 元素,但是 DOM 操作太慢了,過於頻繁的 DOM 操作可能會導致整個頁面掉幀、卡頓甚至失去響應。仔細想一想,很多 DOM 操作是可以打包(多個操作壓成一個)和合並(一個連續更新操作只保留最終結果)的,同時 JS 引擎的計算速度要快得多,所以為什麼不把 DOM 操作先通過JS計算完成後統一來一次大招操作DOM呢,於是就有了虛擬DOM的概念。當然,虛擬DOM操作的核心是Diff演算法,也就是比較變化前後Vnode的不同,計算出最小的DOM操作來改變DOM,提高效能。

4、Vue裡面的虛擬DOM怎麼生成的呢?

通過`createElement(tag, options, VNodes)`,下面就來介紹這個函式的基本概念。

CreateElement 函式

簡單來說CreateElement就是用來生成Vnode的函式

CreateElement 到底會返回什麼呢?其實不是一個實際的 DOM 元素(返回的是Vnode)。它更準確的名字可能是 createNodeDescription,因為它所包含的資訊會告訴 Vue 頁面上需要渲染什麼樣的節點,及其子節點。

【Tips】 CreateElement函式在慣例中通常也寫作h

1、CreateElement的引數如下所示:(太懶了直接搬的官網
// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一個 HTML 標籤字串,元件選項物件,或者 解析上述任何一種的一個 async 非同步函式,必要引數。
  `div`,

  // {Object}
  // 一個包含模板相關屬性的資料物件
  // 這樣,您可以在 template 中使用這些屬性。可選引數。
  {
    // 詳情見下方
  },

  // {String | Array}
  // 子節點 (VNodes),由 `createElement()` 構建而成,或使用字串來生成“文字節點”。可選引數。
  [
    `先寫一些文字`,
    createElement(`h1`, `一則頭條`),
    createElement(MyComponent, {
      props: {
        someProp: `foobar`
      }
    })
  ]
)
複製程式碼

【Tips】 文件中此處說VNodes子節點必須是唯一的,也就是說第三個引數的Array裡不能出現相同指向的VNodes,實際驗證以後,就算寫重複的VNodes,也並不會報錯,估計此處會有些坑,現在還沒踩到,建議按照文件要求,保持子節點唯一。

2、Vnode屬性配置(第二個引數):(太懶了也是直接搬,捂臉.jpg)

以下屬性為簡單介紹,具體用法和一些 _備註解釋 _可以參考後面會講到的【包含屬性配置較完整的例項】

{
  // 和`v-bind:class`一樣的 API
  // 接收一個字串、物件或字串和物件組成的陣列
  `class`: {
    foo: true,
    bar: false
  },
  // 和`v-bind:style`一樣的 API
  // 接收一個字串、物件或物件組成的陣列
  style: {
    color: `red`,
    fontSize: `14px`
  },
  // 正常的 HTML 特性
  attrs: {
    id: `foo`
  },
  // 元件 props
  props: {
    myProp: `bar`
  },
  // DOM 屬性
  domProps: {
    innerHTML: `baz`
  },
  // 事件監聽器基於 `on`
  // 所以不再支援如 `v-on:keyup.enter` 修飾器
  // 需要手動匹配 keyCode。
  on: {
    click: this.clickHandler
  },
  // 僅對於元件,用於監聽原生事件,而不是元件內部使用
  // `vm.$emit` 觸發的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定義指令。注意,你無法對 `binding` 中的 `oldValue`
  // 賦值,因為 Vue 已經自動為你進行了同步。
  directives: [
    {
      name: `my-custom-directive`,
      value: `2`,
      expression: `1 + 1`,
      arg: `foo`,
      modifiers: {
        bar: true
      }
    }
  ],
  // 作用域插槽格式
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement(`span`, props.text)
  },
  // 如果元件是其他元件的子元件,需為插槽指定名稱
  slot: `name-of-slot`,
  // 其他特殊頂層屬性
  key: `myKey`,
  ref: `myRef`
}
複製程式碼

【起步】最基本的例項

這是一個基礎的Demo,包含了

  • 簡單的渲染用法

  • 標籤

  • props

  • slot

  • 點選事件

以下示例Demo均採用單檔案元件的方式,工程用vue-cli搭建的webpack-simple工程。

元件wii-first

<script>
export default {
  name: `wii-first`,
  data() {
    return {
      msg: 0
    }
  },
  props: {
    level: {
      type: [Number, String],
      required: true
    }
  },
  render: function(createElement) {
    this.$slots.subtitle = this.$slots.subtitle || []
      // this.level = 1時, 等價於
      // <h1 class="wii-first">
      //  第一個元件, <slot></slot>
      //  <slot name="subtitle"></slot>,此處是data的值: {{msg}}
      //  <button @click="clickHandler">點我改變內部data值</button>
      // </h1>
    return createElement(
      `h` + this.level, // tag name 標籤名稱
      {
        class: `wii-first`
      },
      // this.$slots.default, // 子元件中的slot 單個傳遞
      // this.$slots.subtitle,
      [
        `第一個元件, `,
        ...this.$slots.default, // 預設slots傳遞
        ...this.$slots.subtitle, // 具名slots傳遞
        `,此處是data的值: `,
        this.msg,
        createElement(`button`, {
          on: {
            click: this.clickHandler
          },
        }, `點我改變內部data值`)
      ]
    )
  },
  methods: {
    clickHandler() {
      this.msg = Math.ceil(Math.random() * 1000)
    }
  }
}
</script>
複製程式碼

【Tips】:CreateElement的第三個引數在文件中規定元件樹中的所有 VNode 必須是唯一的,也就是說在第三個引數中有兩個指向相同的Vnode是無效的。但經過實踐發現,實際上是可以渲染出來的,在此不推薦這麼寫哦,可能會掉到不可預料的大坑hiahiahia~

引入方式

<template>
  <div id="app">
    <wii-first level="1">我是標題 <span slot="subtitle">我是subtitle</span></wii-first>
  </div>
</template>

<script>
import WiiFirst from `./components/first/index.vue`
export default {
  name: `app`,
  components: {
    WiiFirst
  },
  data() {
    return {

    }
  }
}
</script>
複製程式碼

【進階】包含屬性配置較完整的例項

這個Demo主要展示了createElement屬性用法,包含

  • class,style,attrs,on等
  • 包含了點選事件以及click.stop的轉換示例

不包含

  • v-for v-if v-model的實現,這些在官網都有很詳細的說明了 傳送門

元件wii-second

export default {
  name: `wii-second`,
  data() {
    return {
      myProp: `我是data的值, 只是為了證明props不是走這兒`
    }
  },
  props: {

  },
  render: function(createElement) {
    // 等價於
    // <div id="second" class="wii-second blue-color" style="color: green;" @click="clickHandler">
    //     我是第二個元件測試, 點我觸發元件內部click和外部定義的@click.native事件。
    //     <div>{{myProp}}</div>
    //     <button @click="buttonClick">觸發emit</button>
    // </div>
    return createElement(
      `div`, {
        //【class】和`v-bind:class`一樣的 API
        // 接收一個字串、物件或字串和物件組成的陣列
        // class: `wii-second`,
        // class: {
        //     `wii-second`: true,
        //     `grey-color`: true
        // },
        class: [{
          `wii-second`: true
        }, `blue-color`],

        //【style】和`v-bind:style`一樣的 API
        // 接收一個字串、物件或物件組成的陣列
        style: {
          color: `green`
        },
        //【attrs】正常的 HTML 特性, id、title、align等,不支援class,原因是上面的class優先順序最高[僅猜測]
        // 等同於DOM的 Attribute
        attrs: {
          id: `second`,
          title: `測試`
        },
        // 【props】元件 props,如果createElement定義的第一個引數是元件,則生效,此處定義的資料將被傳到元件內部
        props: {
          myProp: `bar`
        },
        // DOM 屬性 如 value, innerHTML, innerText等, 是這個DOM元素作為物件, 其附加的內容
        // 等同於DOM的 Property
        // domProps: {
        //   innerHTML: `baz`
        // },
        // 事件監聽器基於 `on`, 用於元件內部的事件監聽
        on: {
          click: this.clickHandler
        },
        // 僅對於元件,同props,等同@click.native,用於監聽元件內部原生事件,而不是元件內部使用 `vm.$emit` 觸發的事件。
        // nativeOn: {
        //   click: this.nativeClickHandler
        // },
        // 如果元件是其他元件的子元件,需為插槽指定名稱,見 wii-third 元件
        // slot: `testslot`,
        // 其他特殊頂層屬性
        // key: `myKey`,
        // ref: `myRef`
      }, [
        `我是第二個元件測試, 點我觸發元件內部click和外部定義的@click.native事件。`,
        createElement(`div`, `${this.myProp}`),
        createElement(`button`, {
          on: {
            click: this.buttonClick
          }
        }, `觸發emit`)
      ]
    )
  },
  methods: {
    clickHandler() {
      console.log(`我點選了第二個元件,這是元件內部觸發的事件`)
    },
    buttonClick(e) {
      e.stopPropagation() // 阻止事件冒泡 等價於 click.stop
      console.log(`我點選了第二個元件的button,將會通過emit觸發外部的自定義事件`)
      this.$emit(`on-click-button`, e)
    }
  }
}
複製程式碼

引入方式

<template>
  <div id="app">
    <wii-second @click.native="nativeClick" @on-click-button="clickButton"></wii-second>
  </div>
</template>

<script>
import WiiSecond from `./components/second/index.vue`

export default {
  name: `app`,
  components: {
    WiiSecond
  },
  data() {
    return {
    }
  },
  methods: {
    nativeClick() {
      console.log(`這是元件外部click.native觸發的事件,第二個元件被點選了`)
    },
    clickButton() {
      console.log(`這是元件外部觸發的【emit】事件,第二個元件被點選了`)
    }
  }
}
</script>
複製程式碼

事件 & 按鍵修飾符

上面例子中用到了e.stopPropagation這個方法,等價於 template 模板寫法的click.stop,其他的事件和按鍵修飾符也有對應的方法,對應情況如下。

事件修飾符對應的字首

template事件修飾符 render寫法字首
.passive &
.capture !
.once ~
.capture.once 或 .once.capture ~!

例如

on: {
  `!click`: this.doThisInCapturingMode,
  `~keyup`: this.doThisOnce,
  `~!mouseover`: this.doThisOnceInCapturingMode
}
複製程式碼

其他事件修飾符,對應的事件處理函式中使用事件方法

template事件修飾符 對應的事件方法
.stop event.stopPropagation()
.prevent event.preventDefault()
.self if (event.target !== event.currentTarget) return
Keys: .enter, .13 if (event.keyCode !== 13) return (對於其他的鍵盤事件修飾符,將13換成其他的鍵盤code就行)
Modifiers Keys: .ctrl, .alt, .shift, .meta if (!event.ctrlKey) return (將 ctrlKey 換成 altKey, shiftKey, 或者 metaKey, respectively)

例如

on: {
  keyup: function (event) {
    // 如果觸發事件的元素不是事件繫結的元素
    // 則返回
    if (event.target !== event.currentTarget) return
    // 如果按下去的不是 enter 鍵或者
    // 沒有同時按下 shift 鍵
    // 則返回
    if (!event.shiftKey || event.keyCode !== 13) return
    // 阻止 事件冒泡
    event.stopPropagation()
    // 阻止該元素預設的 keyup 事件
    event.preventDefault()
    // ...
  }
}
複製程式碼

【進階】CreateElement中slot屬性的用法

這個Demo主要展示render中createElement的配置slot屬性用法。

因為此處一直很疑惑什麼情況下能夠用到這個slot屬性,所以就試了一下,僅供參考,具體使用場景需要根據業務邏輯來定

元件wii-third

<script>
export default {
  name: `wii-third`,
  data() {
    return {}
  },
  components: {
    WiiTestSlot: {
      name: `wii-test-slot`,
      render(createElement) {
        this.$slots.testslot = this.$slots.testslot || []
          // 等價於
          // <div>
          //     第三個元件,測試在元件中定義slot, <slot name="testslot"></slot>
          // </div>
        return createElement(
          `div`, [
            `第三個元件,測試在元件中定義slot, `,
            ...this.$slots.testslot
          ]
        )
      }
    },
    WiiTestSlotIn: {
      name: `wii-test-slot-in`,
      render(createElement) {
        // 等價於
        // <span>我是元件中的slot內容</span>
        return createElement(
          `span`, [
            `我是元件中的slot內容`
          ]
        )
      }
    }
  },
  props: {

  },
  render: function(createElement) {
    // 等價於
    // <div style="margin-top: 15px;">
    //     <wii-test-slot>
    //       <wii-test-slot-in slot="testslot"></wii-test-slot-in>
    //     </wii-test-slot>
    // </div>
    return createElement(
      `div`, {
        style: {
          marginTop: `15px`
        }
      }, [
        createElement(
          `wii-test-slot`,
          //這麼寫不會被渲染到節點中去
          // createElement(
          //    `wii-test-slot-in`,
          //    {
          //      slot: `testslot`
          //    }
          //  ),
          [
            // createElement再放createElement需要放入陣列裡面,建議所有的元件的內容都放到陣列裡面,統一格式,防止出錯
            createElement(
              `wii-test-slot-in`, {
                slot: `testslot`
              }
            )
          ]
        )
      ]
    )
  },
  methods: {

  }
}
</script>
複製程式碼

【Tips】:如果createElement裡面的第三個引數傳遞的是createElement生成的VNode物件,將不會被渲染到節點中,需要放到陣列中才能生效,此處猜測是因為VNode物件不會被直接識別,因為文件要求是String或者Array。

引入方式

<template>
  <div id="app">
    <wii-third></wii-third>
  </div>
</template>

<script>
import WiiThird from `./components/third/index.vue`

export default {
  name: `app`,
  components: {
    WiiThird
  },
  data() {
    return {}
  }
}
</script>
複製程式碼

【深入】CreateElement中scopedSlots的用法

這個Demo主要展示scopedSlots的用法,包括定義和使用。scopedSlots的template用法和解釋參考vue-slot-scope

元件wii-forth

<script>
export default {
  name: `wii-forth`,
  data() {
    return {}
  },
  components: {
    WiiScoped: {
      name: `wii-scoped`,
      props: {
        message: String
      },
      render(createElement) {
        // 等價於 <div><slot :text="message"></slot></div>
        return createElement(
          `div`, [
            this.$scopedSlots.default({
              text: this.message
            })
          ]
        )
      }
    }
  },
  render: function(createElement) {
    // 等價於 
    // <div style="margin-top: 15px;">
    //   <wii-scoped message="測試scopedSlots,我是傳入的message">
    //     <span slot-scope="props">{{props.text}}</span>
    //   </wii-scoped>
    // </div>
    return createElement(
      `div`, {
        style: {
          marginTop: `15px`
        }
      }, [
        createElement(`wii-scoped`, {
          props: {
            message: `測試scopedSlots,我是傳入的message`
          },
          // 傳遞scopedSlots,通過props(自定義名稱)取值
          scopedSlots: {
            default: function(props) {
              return createElement(`span`, props.text)
            }
          }
        })
      ]
    )
  }
}
</script>
複製程式碼

引入方法

<template>
  <div id="app">
    <wii-forth></wii-forth>
  </div>
</template>

<script>
import WiiForth from `./components/forth/index.vue`

export default {
  name: `app`,
  components: {
    WiiForth
  },
  data() {
    return {}
  }
}
</script>
複製程式碼

【換口氣兒】Render中的JSX配置和用法

寫了這麼多createElement,眼睛都花了,有的寫起來也挺麻煩的。我們試試來換個口味,試試JSX的寫法。

工欲善其事,必先利其器。

  • 首先我們安裝vue寫JSX必要的依賴:
npm install babel-plugin-syntax-jsx babel-plugin-transform-vue-jsx babel-helper-vue-jsx-merge-props babel-preset-env --save-dev
複製程式碼
  • 安裝完成以後,在.babelrc檔案配置"plugins": ["transform-vue-jsx"]

  • 將webpack配置檔案中的js解析部分改成test: /.jsx?$/表示對jsx的程式碼塊進行解析。

這個示例做了如下功能:

  1. 通過props傳入顏色,並在子元件配置
  2. 點選改變顏色
  3. 點選事件通過native獲取

父元件wii-jsx

<script type="text/jsx">
     import WiiJsxItem from `./item.vue`
     export default {
        name: `wii-jsx`,
        components: {
            WiiJsxItem
        },
        data() {
            return {
                color: `red`
            }
        },
        props: {

        },
        render: function (h) {
        return (
            <div class="wii-jsx">
                <wii-jsx-item color={this.color} nativeOnClick={this.clickHandler}>
                    <span>我是wii-jsx-item元件的slot, color通過變數傳入: {this.color}</span>
                </wii-jsx-item>
            </div>
        )
      },
      methods: {
          clickHandler() {
            this.color = this.color == `red` ? `blue` : `red`
            console.log(`點選了wii-jsx-item,通過native觸發,改變了顏色為【${this.color}】`)
          }
      }
    }
</script>
複製程式碼

子元件wii-jsx-item

該子元件在父元件中被引入,並用JSX的寫法渲染。

export default {
  name: `wii-jsx-item`,
  data() {
    return {}
  },
  props: {
    color: String
  },
  render: function(createElement) {
    // 等價於 <div class="wii-jsx-item"><slot></slot></div>
    return createElement(
      `div`, {
        class: `wii-jsx-item`,
        style: {
          color: this.color
        }
      },
      this.$slots.default
    )
  },
  methods: {

  }
}
複製程式碼

引入方式

<template>
  <div id="app">
    <wii-jsx></wii-jsx>
  </div>
</template>

<script>
import WiiJsx from `./components/jsx/index.vue`

export default {
  name: `app`,
  components: {
    WiiJsx
  },
  data() {
    return {}
  }
}
複製程式碼

JSX的主要轉換還是依靠我們之前安裝的babel外掛,而JSX的事件以及屬性的用法見babel外掛的使用說明,這裡麵包含了vue裡面事件和屬性對應的用法說明。

【深入】函式式元件

下面來進行最後一個模組的介紹,函式式元件functional,這個東西的用法就見仁見智了,這裡也沒啥好的方案,只是給出了一些示例,各位大佬如果有一些具體的使用到的地方,闊以指點一下哇~thx~(害羞.jpg)。

官方文件的定義是functional元件需要的一切都是通過上下文傳遞,包括:

  • props:提供所有 prop 的物件
  • children: VNode 子節點的陣列
  • slots: 返回所有插槽的物件的函式
  • data:傳遞給元件的資料物件,作為 createElement 的第二個引數傳入元件
  • parent:對父元件的引用
  • listeners: (2.3.0+) 一個包含了所有在父元件上註冊的事件偵聽器的物件。這只是一個指向 data.on 的別名。
  • injections: (2.3.0+) 如果使用了 inject 選項,則該物件包含了應當被注入的屬性。

_在新增 _functional: true之後,元件的render函式會增加第二個引數context(第一個是createElement),資料和節點通過context傳遞。

Tips:

在 2.3.0 之前的版本中,如果一個函式式元件想要接受 props,則props選項是必須的。在 2.3.0 或以上的版本中,你可以省略props選項,所有元件上的屬性都會被自動解析為 props。


我個人的理解是:

Functional相當於一個純函式一樣,內部不儲存用於在介面上展示的資料,傳入什麼,展示什麼,傳入的是相同的資料,展示的必然是相同的。無例項,無狀態,沒有this上下文,均通過context來控制。

優點:

因為函式式元件只是一個函式,所以渲染開銷低很多。

使用場景:

  1. 僅僅作為接收引數用的元件(不做任何管理和監聽狀態)
  2. 目前看大多數使用場景是用來包裝動畫元件,因為動畫元件不需要狀態管理這些
  3. 程式化地在多個元件中選擇一個(官方)
  4. 在將 children, props, data 傳遞給子元件之前操作它們(官方)

接下來就通過兩個元件來看看如何使用的吧,這裡也僅僅只是示例而已,使用的場景仍在探索中,具體的使用場景還需要在開發過程中根據需求複雜的和效能要求來酌情選擇~

函式式元件一 wii-functional用在動畫的functional

這個Demo的作用是在輸入框中輸入字元,對資料列表進行篩選,篩選時加入顯示和消失的動畫。

元件主體

<script>
import Velocity from `velocity-animate` // 這是一個動畫庫
export default {
  name: `wii-functional`,
  functional: true, //表明是函式式元件
  render: function(createElement, context) {
    // context是在functional: true時的引數
    let data = {
      props: {
        tag: `ul`,
        css: false
      },
      on: {
        // 進入前事件
        beforeEnter: function(el) {
          el.style.opacity = 0
          el.style.height = 0
        },
        // 進入事件
        enter: function(el, done) {
          let delay = el.dataset.index * 150
          setTimeout(function() {
            Velocity(el, {
              opacity: 1,
              height: `1.6em`
            }, {
              complete: done
            })
          }, delay)
        },
        // 離開事件
        leave: function(el, done) {
          let delay = el.dataset.index * 150
          setTimeout(function() {
            Velocity(el, {
              opacity: 0,
              height: 0
            }, {
              complete: done
            })
          }, delay)
        }
      }
    }
    return createElement(`transition-group`, data, context.children)
  }
}
</script>
複製程式碼

上面這個元件相當於建立了一個ul-li標籤組成的vue動畫,通過functional方式包裹到元件外部,可以作為通用的動畫。

引入方式

<template>
  <div id="app">
    <input v-model="query"/>
    <wii-functional>
      <li v-for="(item, index) in computedList"
          :key="item.msg"
          :data-index="index">
          {{item.msg}}
      </li>
    </wii-functional>
  </div>
</template>

<script>
import WiiFunctional from `./components/functional/index.vue`

export default {
  name: `app`,
  components: {
    WiiFunctional
  },
  data() {
    return {
      // 關鍵字
      query: ``,
      // 資料列表
      list: [{
        msg: `Bruce Lee`
      }, {
        msg: `Jackie Chan`
      }, {
        msg: `Chuck Norris`
      }, {
        msg: `Jet Li`
      }, {
        msg: `Kung Furry`
      }, {
        msg: `Chain Zhang`
      }, {
        msg: `Iris Zhao`
      }, ]
    }
  },
  computed:{
    computedList: function() {
      var vm = this
      // 過濾出符合條件的查詢結果
      return this.list.filter(function(item) {
        return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
      })
    }
  },
  watch: {
    computedList(newVal, oldVal) {
      console.log(newVal)
    }
  }
}
</script>
複製程式碼

函式式元件二 wii-choose-comp用在元件切換的functional

在這個示例中,通過props來切換載入不同的元件,並且在props傳遞給子元件之前操作它,元件內部定義了click.native事件來展示示例。如果對一批元件進行同樣的操作,則可以用這個functional,類似於加工廠。

當然如果元件需要不同的點選事件或者表現方式也可以在各個元件內部單獨寫邏輯或者監聽~因為wii-choose-comp這個外殼本質不過就是個函式而已~

元件主體 wii-choose-comp

<script>
export default {
  name: `wii-choose-comp`,
  functional: true,
  props: { // 2.3.0版本以上也可以不寫props,會將元件屬性預設繫結成props,為了統一標準還是寫上
    componentName: String // 元件名
  },
  render: function(createElement, context) {
    // 給元件加上class
    context.data.class = [context.props.componentName]
    
    // 在props傳給子元件之前操作它
    context.data.props = {
      compName: context.props.componentName
    }

    context.data.nativeOn = {
      click() {
        alert(`我是functional裡面統一的點選事件`)
      }
    }
    return createElement(context.props.componentName, context.data, context.children)
  }
}
</script>
複製程式碼

切換元件1 wii-comp-one

<script>
export default {
  name: `wii-comp-one`,
  props: {
    compName: String
  },
  render: function(createElement) {
    return createElement(`div`, [
      `我是第一個comp, 我有點選效果, `,
      `我的名字叫${this.compName}, `,
      ...this.$slots.default
    ])
  }
}
</script>
複製程式碼

切換元件2 wii-comp-two

<script>
export default {
  name: `wii-comp-two`,
  props: {
    compName: String
  },
  render: function(createElement) {
    return createElement(`div`, [
      `我是第二個comp, 點我試試唄, `,
      `我的名字叫${this.compName}, `,
      ...this.$slots.default
    ])
  }
}
</script>
複製程式碼

引入方式

<template>
  <div id="app">
    <button @click="changeComponent">點選切換元件</button>
    <wii-choose-comp :component-name="componentName">
      <span>我是{{componentName}}的slot</span>
    </wii-choose-comp>
  </div>
</template>

<script>
import WiiChooseComp from `./components/functional/chooseComp.vue`
import WiiCompOne from `./components/functional/comp1.vue`
import WiiCompTwo from `./components/functional/comp2.vue`

export default {
  name: `app`,
  components: {
    WiiChooseComp,
    WiiCompOne,
    WiiCompTwo
  },
  data() {
    return {
      componentName: `wii-comp-one`
    }
  },
  methods: {
    changeComponent() {
      this.componentName = this.componentName == `wii-comp-one` ? `wii-comp-two` : `wii-comp-one`
    }
  }
}
</script>
複製程式碼

【Tips】 需要將待切換的元件全部引入到外層。(不造有沒有更好的辦法?

總結

以上就是最近對Vue Render的一個探索,因為對於公共元件庫開發來說,需要考慮的問題有很多,所以靈活性要求也更高,如果用Vue Render這種更接近編譯的方式來編寫元件庫,可能會讓邏輯更清晰,雖然不停的建立元素的寫法是挺噁心的哈哈哈哈~~

接下來就是用來進行一下實戰了,在實戰的時候有什麼坑就到時候再慢慢填咯~~

相關文章