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表達式,經過parcel或babel編譯后,轉換為創(chuàng)建虛擬DOM節(jié)點的函數。不同的是:React是React.createElement函數,而Vue是createVNode:
///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組件,雖然展示&交互都沒有問題,但卻有一個共同的問題:不支持Vue的HMR機制,每次都需要重新載入,于開發(fā)不便利。如何讓Vue JSX組件支持HMR呢?
defineComponent
Vue JSX 組件支持HMR檢測的必要條件有兩個;
- 必須導出組件。
- 組件必須使用
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>
)
}
}
}
插槽組件使用
-
template使用
<sloter>
<template #default>
默認
</template>
<template #A>
A
</template>
</sloter>
-
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)勢:
- 官方推薦,模板化固定的語法,便于靜態(tài)標記和分析,使得編譯器能應用許多編譯時的優(yōu)化,提升性能。
- 貼近實際的
HTML,利于重用已有的HTML代碼片段,便于理解和修改。
Render & JSX優(yōu)勢:
- 處理高度動態(tài)的邏輯時,渲染函數相比于模板更加靈活。
- 一個文件中可以返回多個組件。
- 利于
React開發(fā)者快速上手Vue

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