render函數&高級組件&jsx基本使用

前言

在學習瞭解組件複用的時候查看資料,看到了mixins,extend方法同時也查到了高級函數(Higher-Order Components)和jsx等字眼。聽上去hoc和jsx有種高級的感受,那得好好研究下。css

概念

高階組件定義 : 高階組件是一個方法,這個方法接收一個原始組件做爲參數,並返回新的組件。html

jsx定義 : JSX 是一種相似於 XML 的 JavaScript 語法擴展 JSX 不是由引擎或瀏覽器實現的。相反,咱們將使用像 Babel 這樣的轉換器將 JSX 轉換爲常規 JavaScript。基本上,JSX 容許咱們在 JavaScript 中使用相似 HTML 的語法。vue

template模板生成方式

Vue 推薦使用在絕大多數狀況下使用 template 來建立你的 HTML。然而在一些場景中,你真的須要 JavaScript 的徹底編程的能力,這就是 render 函數,它比 template 更接近編譯器。node

其實咱們傳入template模板以後,在vue底層也會將其轉換成render函數 vue源碼 src/platform/web/entry-runtime-with-compiler.js文件中代碼以下,咱們只須要關注tempalte變量,首先判斷配置項中有無傳入render,若是沒有也會有不少種狀況,不管哪一種狀況都會給template變量賦值一個DOM字符串,最終經過compileToFunctions函數將其轉換成render函數,經過結構賦值的方式賦值給render變量,而後添加到實例上 具體能夠看這篇文章git

if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
    }
  }
複製代碼

render語法

使用以前須要的配置和使用語法這裏咱們主要來看props 、class、on(事件)、slots這四個屬性,首先來看代碼github

// 子組件
<script>
export default {
  name: 'judge',
  props: {
    type: {
      type: Number | String,
      default: 1
    }
  },
  render(h) {
    return h(
      'div',
      {
        class: {
          default: true,
          first: this.type == 1,
          second: this.type == 2,
          third: this.type == 3
        },
        on: {
          click: this.onShowTypeClick
        }
      },
      [this.$slots.default, this.$slots.tag]
    )
  },
  methods: {
    onShowTypeClick() {
      console.log(this.type)
    }
  }
}
</script>

<style lang='scss' scoped>
.default {
  text-align: center;
  width: 200px;
  line-height: 50px;
  background-color: #eee;
}
.first {
  background-color: #f66;
}
.second {
  background-color: #c66;
}
.third {
  background-color: #804;
}
</style>

複製代碼
// 父組件
   <input type="text"
      v-model="number">
    <judge :type="number">
      <span class="render__incoming"> 外來者</span>
      <p class="render__incoming-p"
        slot="tag">我是p標籤</p>
    </judge>
複製代碼

效果圖

render函數的功能建立虛擬節點就是代替template因此 在.vue 文件中必須省略template標籤web

createElement

render函數默認接受createElement 函數createElement默認接受三個參數編程

createElement(
 // {String | Object | Function}
 // 一個 HTML 標籤字符串,組件選項對象,或者
 // 解析上述任何一種的一個 async 異步函數。必需參數。
 'div',

 // {Object}
 // 一個包含模板相關屬性的數據對象
 // {String | Array}
 // 子虛擬節點 (VNodes),由 `createElement()` 構建而成,
 [
   '先寫一些文字',
   createElement('h1', '一則頭條'),
   this.$slots.default
 ]
)
複製代碼

除了第一個參數,後面兩個都爲可選參數,第二個參數是配置選項如:style、attrs、props等,注意的是使用了domProps屬性下的innerHTML後,會覆蓋子節點和slot插入的元素第三個參數是關於子節點如:再用createElement函數建立一個虛擬子節點,或者是接受slot傳遞過來的DOM節點element-ui

hoc高階函數

一樣來看props 、class、on(事件)、slots這四個屬性的實現。數組

// hoc 組件
export default componentA => {
 return {
   // hoc 組件自己沒有設置props 須要設置傳入的組件相同的props
   props: componentA.props,
   render(h) {
     let slots = {}
     Object.keys(this.$slots).map(item => {
       slots[item] = () => this.$slots[item]
       return slots
     })
     return h(componentA, {
       attrs: this.$attrs,
       props: this.$props,
       scopedSlots: slots,
       on:this.$listeners
     })
   }
 }
}

複製代碼
// 父組件
   <packer :test="15"
     v-on:customize-click="onCuClick">
     插入信息
     <p slot="test">1516545</p>
   </packer>
複製代碼
// componentA 組件
<template>
 <div>
   <span @click="handleClick">props: {{prop}}</span>
   <slot name="test"></slot>
   ============
   <slot></slot>
 </div>
</template>
複製代碼

實現過程

在vue中組件能夠看做是一個對象,裏面包括一些方法屬性如:data、props、methods、鉤子函數等,那hoc函數最終也是要返回這樣一個對象。咱們從新來溫習下hoc高階組件的定義,一個函數接收一個組件,而且返回一個新的組件。能夠這樣理解,接受一個組件,而且對整個組件進行包裝而後返回出去。固然hoc函數返回出去的對象應該也具備組件應該有的options如:data、props、methods、鉤子函數等
來解釋下本例中的實現過程,給hoc函數設置props和componentA相同的props屬性,這樣咱們也能夠經過packer向hoc組件傳入props。在hoc接受到外部傳入的值後經過this.$props將hoc實例中的props值都傳遞給componentA,固然咱們也能夠給hoc設置多於componentA的props值。

attrs 指的是那些沒有被聲明爲 props 的屬性

經過scopedSlots實現hoc組件傳入slot插槽

scopedSlots: {
   // 實現默認插槽和具名插槽
   default: ()=>this.$slots.default,
   test: ()=>this.$slots.test
 }
複製代碼

listeners: (2.3.0+) 一個包含了全部在父組件上註冊的事件偵聽器的對象。這只是一個指向 data.on 的別名。

listeners指的就是在父組件上綁定的全部事件(是一個對象)格式爲 { [key: string]: Function | Array } 鍵名爲字符串,值爲函數多個用數組

<packer v-on:customize-click="onCuClick"></packer>
複製代碼

因此咱們須要在hoc組件中設置componentA的父組件事件

jsx

首先咱們來看下用render函數實現使用element-ui table組件

export default {
 render(h) {
     return  h(
         'el-table',
         {
           props: {
             data: [
               {
                 date: '2016-05-02',
                 name: '王小虎',
                 address: '上海市普陀區金沙江路 1518 弄'
               }
             ]
           }
         },
         [
           h('el-table-column', {
             attrs: {
               prop: 'date',
               label: '日期',
               width: '180'
             }
           })
         ]
       )
 }
複製代碼

能夠看到只是兩次嵌套關係的render函數寫起來的代碼很難維護,這裏就要用到jsx語法。它可讓咱們回到更接近於模板的語法上。

export default {
  data() {
    return {
      tableData: [
        {
          date: '2016-05-02',
          name: '王小虎',
          address: '上海市普陀區金沙江路 1518 弄'
        }
      ]
    }
  },
  render(h) {
    return (
      <el-table data={this.tableData}>
        <el-table-column prop="date" label="日期" width="180" />
      </el-table>
    )
  }
}

複製代碼

實現jsx中v-model

效果圖

<script>
export default {
  data() {
    return {
      initValue: ''
    }
  },
  render() {
    return (
      <div class="input__wrap">
        <div>initValue的值:{this.initValue}</div>
        <el-input
          value={this.initValue}
          on-input={val => {
            this.initValue = val
          }}
        />
      </div>
    )
  }
}
</script>

<style lang='scss' scoped>
.input__wrap {
  width: 200px;
}
</style>

複製代碼

實現jsx中 v-for

<script>
export default {
  data() {
    return {
      tagList: ['標籤1', '標籤2', '標籤3']
    }
  },
  render() {
    return (
      <div class="tag__wrap">
        {this.tagList.map((item, index) => {
          return <el-tag key={index}>{item}</el-tag>
        })}
      </div>
    )
  }
}
</script>
<style>
.tag__wrap {
  width: 200px;
  display: flex;
  justify-content: space-between;
}
</style>
複製代碼

jsx 擴展符

擴展符將一個對象的屬性智能的合併到組件optiion中

<script>
export default {
  name: 'sync',
  created() {
    console.log(this)
  },
  data() {
    return {
      obj: {
        class: ['sync__class1', 'sync__class2']
      }
    }
  },
  render() {
    return <div {...this.obj} />
  }
}
</script>

複製代碼

使用擴展符能方便咱們插入多個class時,案例中發現以下報錯

提示咱們這個對象以及observed查看源碼以下,定義在src/core/vdom/create-element.js 中的_createElement函數中

if (isDef(data) && isDef((data: any).__ob__)) {
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
      'Always create fresh vnode data objects in each render!',
      context
    )
    return createEmptyVNode()
  }
複製代碼

這段代碼判斷data中中定義的對象以及data的__ob__,若是已經定義(表明已經被observed,上面綁定了Oberver對象)則提示Avoid using observed data object ...錯誤信息。

通過分析後,改寫成

<script>
export default {
  name: 'sync',
  render() {
    const obj = {
      class: ['sync__class1', 'sync__class2']
    }
    return <div {...obj} />
  }
}
</script>

複製代碼

jsx sync實現

<script>
export default {
  name: 'sync',
  render() {
    return (
      <div class="sync__click" onClick={this.onClick}>
        <el-pagination
          total={10}
          current-page={this.index}
          page-size={5}
          {...{
            on: {
              'update:currentPage': value => {
                this.index = value
                console.log('頁數改變了')
              }
            }
          }}
        />
      </div>
    )
  },
  data() {
    return {
      index: 1
    }
  },
  methods: {
    onClick() {
      console.log('點擊事件觸發了')
    }
  }
}
</script>
<style>
.sync__click {
  width: 400px;
  height: 500px;
  background-color: #efefef;
}
</style>

複製代碼

總結

本文主要是認識了高階組件和jsx的定義和基本的使用方式。再這以前梳理了template生成的過程,底層都是經過render函數來實現的。接下來說了render的相關語法,以及書寫render會帶來維護的困難,不易讀懂的困擾。最後使用jsx語法簡化render書寫,而且舉了幾個vue經常使用的指令,除了v-show其餘的實現都要經過本身來實現。固然jsx的知識點還有不少,在有了以上知識的基礎上,再去研究jsx的更多語法以及使用場景也會更加輕鬆些。

參考

hoc issues

babel-plugin-transform-vue-jsx

騰訊web

vue 官網

render 函數

相關文章
相關標籤/搜索