渲染 Vue3 component 的新方式

Vue3 發佈在即,先來一波私貨Vue3初步踩坑javascript

衆所周知,UI 組件是現代前端框架的核心概念之一,同時伴隨着社區的蓬勃發展,愈來愈多建立組件的方式被開發出來,給開發者帶了極大的便利;層出不窮的概念,特別是React社區對組件的探索,開發者經歷了MixinHOCrender props 再到 hooks;組件的抽象方式逐漸肯定下來,隨着Vue3 Composition API 的肯定,兩大前端框架(庫)都選擇hooks做爲抽象組件的最佳方式。 那麼回到組件自己,一個組件從生成到渲染再到銷燬,其套路大致以下html

大部分的前端框架都是如此。

Vue2.0 時代,前端建立組件並掛載在頁面之中大概有兩種思路:前端

  • 正常的建立組件,並在父組件註冊子組件,模板聲明便可vue

    export default {
      components: {
        childComponent,
      }
    }
    複製代碼
  • 使用 Vue.extend 建立 Vue 子類,再 $mount 實例化組件,拿到生成好的 Dom 節點插入body或者任意父組件便可(具體代碼能夠參考 Element-ui this.$loading 的實例化方法);這裏有必要提一下 vue-create-api,它的思路與之相似,但又些許不一樣,vue-create-api 直接實例化了一個 Vue ,同時綁定了調用方的生命週期,讓邏輯上的銷燬更加符合直覺。java

    import loadingVue from './loading.vue'
    const LoadingConstructor = Vue.extend(loadingVue)
    
    let instance = new LoadingConstructor({
      el: document.createElement('div'),
      data: options
    })
    複製代碼

現代的前端框架建立組件就是這麼樸實無華,且高效。小程序

時間來到0202年4月21日(誤),yyx 在B站分享了Vue3.beta的最新進展,有提到Custom Renderer API,有了這個東西,理論上你能夠自定義任意平臺的渲染函數,把VNode渲染到不一樣的平臺上,好比小程序;你能夠對着@vue/runtime-dom複製一個@vue/runtime-miniprogram出來。同時@vue/runtime-dom也給開發者帶來了新的建立組件的方式,讓咱們來嘗試一下吧。api

首先準備一個Loading.vue組件前端框架

<template>
  <transition name="v">
    <div v-show="isShow" class="loading">
      <span>{{ msg }}</span>
    </div>
  </transition>
</template>

<script lang="tsx"> import { defineComponent, ref } from 'vue' export default defineComponent({ name: 'Loading', props: { msg: { type: String }, }, setup(props, context) { const isShow = ref(false) return { isShow } }, methods: { show() { this.isShow = true this.$emit('show') }, hide() { this.isShow = false }, }, mounted() { console.log('掛載') }, unmounted() { console.log('卸載') } }) </script>
<style lang="stylus"> .loading position fixed top 0 left 0 right 0 bottom 0 display flex flex-direction column // justify-content center align-items center transition all .3s ease span position relative top -20px font-size 32px font-family Helvetica Neue For Number,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif color #333 .v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; } </style>
複製代碼

接下來寫一個工廠函數,用來渲染組件,(參考vue-create-api)app

// create-api.ts
import { App, createVNode, render, mergeProps, ComponentOptions } from 'vue'
// 顯然咱們要一個單例模式
let _instance: any = null

export const useCreate = function(Component: ComponentOptions, app?: App, options?: any, ) {
  if (!_instance) {
    /** * 默認的 render 函數,不支持 DocumentFragment 參數咱們要拓展一下這個聲明 * 參考代碼: * // vue-shim.d.ts * import * as vue from 'vue' * declare moudle 'vue' { * export declare const render: vue.RootRenderFunction<Element | DocumentFragment> * } * declare module '@vue/runtime-core' { * interface ComponentCustomProperties { * $createLoading: () => any * } * } */
    const container = document.createDocumentFragment()
    // 直接根據組件生成 VNode
    _instance = createVNode(Component)
    // Vue3 的 props 是扁平化的,事件直接 onMethods 便可;和 React props 相似,合併屬性更輕鬆
    _instance.props = mergeProps(_instance.props, {
      // 測試代碼
      msg: 'it\'s a prop msg',
      // 測試代碼
      onShow() {
        console.log('emit handler')
      },
      ...options,
    })
    // 渲染組件,並插入 body 之中
    render(_instance, container)
    document.body.appendChild(container)
    // 在組件添加一個 remove 方法用來銷燬組件
    _instance.component.ctx.remove = function() {
      render(null, container)
      _instance = null
    }
    // 暴露一個 updateprops 的方法
    _instance.component.ctx.$updateProps = function(props: any) {
      props && Object.keys(props).forEach(k => {
        _instance.component.props[k] = props[k]
      })
    }
  }
  // 將組件直接暴露出去
  return _instance.component.ctx
}
// 暴露一個插件 API 
const install = (app: App, Component: ComponentOptions) => {
  // 在 this 上掛載一個貫穿方法,用 provider 也行
   app.config.globalProperties[`$create${Component.name}`] = useCreate(Component, app)
}
export default install
複製代碼

如何使用呢?也很簡單框架

// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import Loading from 'path/to/Loading.vue'
import { useCreate } from 'path/to/create-api.ts'

const app = createApp(App)
app.mount('#app')

const loading = useCreate(Loading, app)
loading.show()

setTimeout(() => {
  loading.$updateProps({
    msg: '測試message',
  })
}, 1000)

setTimeout(() => {
  loading.remove()
}, 5000)
複製代碼

至此,稍加打磨,開發者就能夠快樂建立組件了。

祝你們生活愉快<完>

受限於筆者的開發能力,文中代碼可能存在若干 bug,歡迎與筆者聯繫、探討 FAQ:

  • Q: 這麼寫代碼有什麼用? A: 哈哈哈,我也不知道
  • Q: Vue3 正式版何時發佈? A: 2020年8月份左右
  • Q: Vue 和 React 選哪一個?A:你幫我擦下鍵盤 cnbmoaqngobxge bavmxdfe

本文首發於我的博客

相關文章
相關標籤/搜索