Vue 渲染函數 & JSX

Vue - template

Vue 官方推薦使用template語法來創(chuàng)建應用,雖然寫法像html,但Vue最終會把template解析為render函數返回虛擬DOM,這點可以在Vue Dev Tools中看到:

template渲染流程:

因此在某些特定情況下,我們可以直接使用render函數來實現我們的組件。

示例

根據接口返回的數值level,動態(tài)渲染標題組件h1~h6。

采用Vue的模板語法,實現如下:

<template>
    <h1 v-if="level==1" class="template_class"><slot></slot></h1>
    <h2 v-if="level==2" class="template_class"><slot></slot></h2>
    <h3 v-if="level==3" class="template_class"><slot></slot></h3>
    <h4 v-if="level==4" class="template_class"><slot></slot></h4>
    <h5 v-if="level==5" class="template_class"><slot></slot></h5>
    <h5 v-if="level==6" class="template_class"><slot></slot></h5>
</template>

<script>
export default {
    props: {
        level: Number,
    },
}
</script>

不過實現過程有些冗余了。因此我們可以使用render函數來動態(tài)返回我們的組件。在此之前我們需要先了解下Vue中的h函數;

Vue - Render & H

h 函數

h函數是Vue創(chuàng)建一個Vnode的函數。

<!--模板語法-->
<div class="className" style="color:red" @click="clicked">
     <h1>hello,world</h1>
</div>

等價于

let vnode = h(
                'div',     //標簽名
                {
                    class: 'className',
                    style: { color: 'red' },
                    onClick() {
                        console.log("點擊事件")
                    }
                },         ///標簽屬性
                h(         ///子節(jié)點
                    'h1',
                    'hello,world'
                ),
              )

render函數

render函數在Vue中可以代替template標簽,直接返回虛擬DOM

接下來,我們使用render函數的方式實現示例需求,有三種寫法:

Vue 2

<script>
import { h } from 'vue'
export default {
    props: {
        level: Number,
    },
    methods: {
        clicked() {
            alert('點擊事件')
        }
    },
    render() {
        let tag = 'h' + this.level
        return h(
            tag,//tag type
            {
                class: 'head_h',
                onClick: this.clicked
            }, ///props
            this.$slots.default() //children 
        )
    }
}
</script>

Vue 3

<script>
import { h } from 'vue'
export default {
   props: {
       level: Number,
   },
   setup(props, { slots }) {
       return () => {
           let tag = 'h' + props.level
           return h(
               tag,//tag type
               {
                   class: 'head_h',
                   onClick: ()=> {
                       alert('點擊事件')
                   }
               }, ///props
               slots.default() //children 
           )
       }
   }
}
</script>

Vue 3 變體

<script>
import { h } from 'vue'
export default (props, { slots }) => { 
//或 export default function head_h(props, { slots }) {
   let tag = 'h' + props.level
   return h(
       tag,//tag type
       {
           class: 'head_h',
           onClick: () => {
               alert('點擊事件')
           }
       }, ///props
       slots.default() //children 
   )
}
</script>

注意:setup語法糖是不能直接使用render函數的。

<script setup> 
//這里不能使用render函數
</script>

render函數除了返回單個vNode外,也可以返回字符串和數組,不過組件樹中的vNodes必須是唯一的(同一個vNode實例,不能在組件中多次使用)。

極簡且合法的Vue組件:

function Hello() {
 return 'hello world!'
}

小結

render函數+h函數雖可以處理動態(tài)性較高的場景,但是遇到復雜的組件時h函數層層嵌套,各種屬性對象,寫起來很復雜 ~ ~;有沒有簡單,方便的寫法呢? 說到這里,就不得不說說JSX。

Vue - Render & JSX

JSX

JSX 是在JavaScript 語法上的拓展,允許 HTML 代碼和 JS 一起寫。
React框架的結合是比較緊密的。

///JSX 表達式 :單行代碼
const heading = <h1>Mozilla Developer Network</h1>; 
///JSX 表達式:多行代碼
const header = (
  <header>
    <h1>Mozilla Developer Network</h1>
  </header>
);

React一樣,Vue會將JSX表達式,經過parcelbabel編譯后,轉換為創(chuàng)建虛擬DOM節(jié)點的函數。不同的是:ReactReact.createElement函數,而VuecreateVNode

///React JSX 編譯后
const header = React.createElement("header", null,
  React.createElement("h1", null, "Mozilla Developer Network")
);
///Vue JSX 編譯后
const header = createVNode("header", null,
  createVNode("h1", null, "Mozilla Developer Network")
);

Vue JSX編譯后的產物,在Vue Dev Tools也能看見:

因此在Vue中要想使用JSX,則必須安裝可以將JSX表達式轉換為createVNode的編譯器插件。

環(huán)境

場景一:
Vue新項目,若要使用JSX,則在Vue項目創(chuàng)建時,配置支持JSX,如此Vue便會自動幫我們配置好JSX的編譯插件。

場景二:
Vue非新建項目,若要使用JSX:

# -D @vitejs/plugin-vue-jsx 插件 開發(fā)環(huán)境下有效
npm install @vitejs/plugin-vue-jsx -D

其次找到vite.config.js文件,配置plugin-vue-jsx插件如下:

import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
  plugins: [vue(),vueJsx()]
})

使用

新建文件后綴更名.jsx,文件內容不再需要寫<script>標簽;同樣,我們使用render函數+JSX的方式實現示例需求,有三種寫法:

Vue 2

import HelloWorld from '../components/HelloWorld.vue'
import './head_jsx.css'
export const head_jsx = {
  props: {
    level: Number,
  },
  components: {
    HelloWorld
  },
  data() {
    return {};
  },
  render() {
    let tag = 'h' + this.level
    return <tag class='head_jsx' >{this.$slots.default()}</tag>
  }
}

Vue 3

export const head_jsx = {
    props: {
        level: Number,
    },
    components:{
      HelloWorld
    },

    setup(props, { slots }) {
        return () => {
            let tag = 'h' + props.level
            return <tag class='head_jsx' >{slots.default()}</tag>
        }
    }
}

Vue 3 變體

export const head_jsx = (props, { slots }) => {
    let tag = 'h' + props.level
    return  <tag class='head_jsx'>{slots.default()}</tag>
}

小結

基于上述寫法的Vue JSX組件,雖然展示&交互都沒有問題,但卻有一個共同的問題:不支持VueHMR機制,每次都需要重新載入,于開發(fā)不便利。如何讓Vue JSX組件支持HMR呢?

defineComponent

Vue JSX 組件支持HMR檢測的必要條件有兩個;

  1. 必須導出組件。
  2. 組件必須使用defineComponent根級語句調用來聲明。

defineComponent是一個函數,除了HMR檢測外,也可在定義 Vue 組件時提供類型推導的輔助。

并且代碼寫入時也會有提示。


使用

基于 Render & JSX使用部分所講的Vue 2、Vue 3的代碼,外層包裝defineComponent即可;Vue 3變體,defineComponent不可用。以下為Vue 3 JSX組件使用defineComponent 示例。

export const head_jsx = defineComponent({
  props: {
    level: Number,
  },
  components: {
    HelloWorld
  },
  setup(props, { slots }) {
    return () => {
      let tag = 'h' + props.level
      return <tag class='head_jsx' >{slots.default()}</tag>
    }
  }
})

v-model

v-model需要注意ref響應式變量,在JSX表達式中的取值

export const inputer = {
  setup(props, { slots }) {
    let val = ref(0)
    return () => (
      <div>
        <input type='number' v-model={val.value} value={val.value}/>
        <div> 結果:{val.value} </div>
      </div>
    )
  }
}

v-slot

JSX,v-slot 應替換為 v-slots

插槽組件定義

export const sloter = {
  setup(props, { slots }) {
    return () => {
      return (
        <div>
          <div style={{ color: 'green' }}>
            我是默認插槽:
            {slots.default ? slots.default() : ''}
          </div>
          <div style={{ color: 'red' }}>
            我是插槽A:
            {slots.A ? slots.A() : ''}
          </div>
          <div style={{ color: 'blue' }}>
            我是插槽B:
            {slots.B ? slots.B() : ''}
          </div>
        </div>
      )
    }
  }
}

插槽組件使用

  1. template使用
    <sloter>
      <template #default>
        默認
      </template>
      <template #A>
        A 
      </template>
    </sloter>
  1. JSX 使用
export const slotsUse = {
  components: {
    sloter
  },
  setup(props,) {
    const slots = {
      default: ()=>(<span>JSX下默認插槽的值</span>),
      A : ()=>(<span>JSX下A插槽的值</span>),
    }
    return () => <sloter v-slots={slots}> </sloter>
  }
}

小結

上文簡單列舉了渲染函數和JSX配合基本使用,詳細使用可見Vue JSX 插件文檔

Vue - Template VS Render&JSX

template 優(yōu)勢:

  1. 官方推薦,模板化固定的語法,便于靜態(tài)標記和分析,使得編譯器能應用許多編譯時的優(yōu)化,提升性能。
  2. 貼近實際的 HTML,利于重用已有的HTML代碼片段,便于理解和修改。

Render & JSX優(yōu)勢:

  1. 處理高度動態(tài)的邏輯時,渲染函數相比于模板更加靈活。
  2. 一個文件中可以返回多個組件。
  3. 利于React開發(fā)者快速上手Vue

參考

https://cn.vuejs.org/guide/extras/rendering-mechanism.html

https://cn.vuejs.org/guide/extras/render-function.html

https://github.com/vuejs/babel-plugin-jsx

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容