天天看點

render函數&進階元件&jsx基本使用

前言

在學習了解元件複用的時候檢視資料,看到了

mixins,extend

方法同時也查到了

進階函數(Higher-Order Components)和jsx

等字眼。聽上去hoc和jsx有種進階的感覺,那得好好研究下。

概念

高階元件定義 : 高階元件是一個方法,這個方法接收一個原始元件作為參數,并傳回新的元件。
jsx定義 : JSX 是一種類似于 XML 的 JavaScript 文法擴充 JSX 不是由引擎或浏覽器實作的。相反,我們将使用像 Babel 這樣的轉換器将 JSX 轉換為正常 JavaScript。基本上,JSX 允許我們在 JavaScript 中使用類似 HTML 的文法。

template模闆生成方式

Vue 推薦使用在絕大多數情況下使用 template 來建立你的 HTML。然而在一些場景中,你真的需要 JavaScript 的完全程式設計的能力,這就是 render 函數,它比 template 更接近編譯器。

其實我們傳入template模闆之後,在

vue底層也會将其轉換成render函數

vue源碼 src/platform/web/entry-runtime-with-compiler.js檔案中代碼如下,我們隻需要

關注tempalte變量

,首先判斷配置項中有無傳入render,如果沒有也會有很多種情況,無論哪種情況都會給template變量

指派一個DOM字元串

,最終通過

compileToFunctions函數将其轉換成render函數

,通過

結構指派的方式指派給render變量

,然後添加到執行個體上 具體可以看這篇文章

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

這四個屬性,首先來看代碼

// 子元件
<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 hljs-string">'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标簽

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節點

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 hljs-string">'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 函數

繼續閱讀