前言
自從vue3徹底支持函數(shù)式編程后情屹,終于支持jsx的寫法了。寫過react的朋友可能更容易理解無狀態(tài)組件與有狀態(tài)組件的區(qū)別褐隆。對寫慣了vue2對象式編程的人來說滔以,可能對于函數(shù)式組件還有些一知半解,時常不知道普通函數(shù)與jsx函數(shù)有什么區(qū)別歼捐,畢竟習(xí)慣了在.vue文件中的template一把梭。分不清在函數(shù)組件中直接返回jsx模板與返回一個對象又有什么區(qū)別晨汹。這篇文章我將淺談一下自己的理解豹储,希望為你解惑。
*基礎(chǔ)好的朋友可直接閱讀第四章
一淘这、腳手架的.vue文件是什么
這里我們不討論腳手架具體是如何處理.vue文件的剥扣,這個涉及到打包器的知識,感興趣的朋友可以自行拓展了解铝穷。我們在基于vite或webpack的腳手架中開發(fā)vue時钠怯,能夠很方便的在.vue文件中開發(fā),一個.vue文件的基本三要素就是“template”曙聂、“style”以及“script”晦炊。在其他vue文件中使用時,只需要 import A from 'A.vue'宁脊,然后在template模板中寫上 <A />即可渲染断国。這是一個大家再熟悉不過的流程,可.vue文件的本質(zhì)是什么呢榆苞?我們不妨輸出一下我們引入的組件:
Test.vue
<template>
<div>測試</div>
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped></style>
在App.vue中引入Test.vue稳衬,然后輸出Test
<script setup lang="ts">
import Test from './Test.vue';
console.log(Test);
</script>
查看瀏覽器中的輸出結(jié)果
我們可以很清楚的看到,我們打印的Test輸出了一個對象坐漏,并且對象中存在一個render函數(shù)薄疚,其他兩個帶下劃線的我們暫且忽略。這里最重要的是一個render對象赊琳。 我們由此可見街夭,在模板中引入的vue組件,實(shí)際上是一個帶有render函數(shù)的對象躏筏。也就是說莱坎,vue在執(zhí)行我們的組件時,會去對象中找render函數(shù)并且執(zhí)行它寸士,render函數(shù)會返回一個Vnodes渲染樹檐什,Vue就能根據(jù)這個樹進(jìn)行渲染顯示碴卧。
二、render函數(shù)是什么
在vue中乃正,render函數(shù)實(shí)際上返回的是一個h函數(shù)返回的虛擬dom樹住册,實(shí)際上就是一個對象。它里面存儲了很多dom的節(jié)點(diǎn)信息瓮具,這些信息都是虛擬的荧飞。這里我們不考慮vue的template,我們暫時把它忘掉名党。這里我們考慮一個場景叹阔,我給定你一個對象,例如
const obj = {
div:{
style:"color:red;font-size:13px"
}
}
我的需求是传睹,希望你用這個對象耳幢,使用你知道的所有javascript知識,將它渲染到網(wǎng)頁里欧啤。
這里我就不貼代碼了睛藻。
我相信對你來說這是一件很簡單的事情,不就是讀取這個obj對象邢隧,然后創(chuàng)建相對應(yīng)的div標(biāo)簽店印,接著將style設(shè)置為對象中的style,最后append到dom容器里不就好了嗎倒慧?
是的按摘,你的做法以及思想都完全沒有錯,而且這也不算很有難度纫谅。那么院峡,如果我希望在div下再渲染一個span呢?像這樣:
const obj = {
div: {
style: 'color:red;font-size:13px',
children: [
{ span: { style: 'color:red', content: '內(nèi)容' } },
{ span: { style: 'color:red', content: '內(nèi)容2' } }
]
},
};
你可能注意到系宜,我在div下增加了一個叫children的數(shù)組照激,為什么是數(shù)組?因?yàn)閐iv下不可能只有一個元素嘛盹牧!那么現(xiàn)在我希望在div下渲染出這兩個span俩垃,并且content 內(nèi)容也要顯示在標(biāo)簽里,你會怎么做呢汰寓?
我覺得這個對于看我文章的人來說口柳,應(yīng)該也是毫無壓力的,無非就是處理div的時候有滑,順便找一下有沒有children,有的話就一起處理跃闹,最后一起append到dom容器里就好了嘛!
你真是個天才!根本難不倒你望艺,你的想法完全可行苛秕。
可是content內(nèi)容不可能一直不變,如果我需要修改span中的內(nèi)容找默,那么你又得重新執(zhí)行一遍你剛剛的處理的流程艇劫,我假設(shè)你很聰明的把剛剛對于obj的處理寫了一個函數(shù),那就意味著惩激,你會想辦法監(jiān)聽到我對obj到更改店煞。只要有更改,你就會重新執(zhí)行风钻,確保頁面顯示的跟我的obj對象是一致的顷蟀。
如果你真的這么做了,當(dāng)obj這個對象所表達(dá)的標(biāo)簽足夠多時骡技,可能只是其中一個標(biāo)簽中的content改變鸣个,你就需要重新運(yùn)行整個函數(shù),然后整個處理完再全部append哮兰。你的頁面性能將會迎來瓶頸毛萌,說人話就是卡到爆苟弛。
你會如何處理這個性能問題呢喝滞?
我覺得可以自己悄悄再寫一個函數(shù),專門用來緩存上次保存的obj對象膏秫,當(dāng)原始o(jì)bj對象更改時右遭,我將緩存里的obj對象和更改的obj對象先進(jìn)行一次比對,找出實(shí)際被更改的那個標(biāo)簽缤削,然后單獨(dú)對這個標(biāo)簽進(jìn)行append窘哈。這樣,我就可以做到只對有更改的標(biāo)簽進(jìn)行append到頁面上亭敢,做到了局部的DOM更新滚婉。 很棒對吧?帅刀!
如果你也覺得上面這個方案很棒让腹,尤雨溪也是這么覺得的,這就是我們所謂的diff算法扣溺。
看到這里骇窍,實(shí)際上我們已經(jīng)實(shí)現(xiàn)了最基本的數(shù)據(jù)驅(qū)動視圖,我們只需要控制obj這個對象锥余,具體的渲染更新我們交給了事先封裝好的針對處理obj的函數(shù)(我假設(shè)你封裝好了)腹纳。
我們剛剛討論的那個obj,里面的結(jié)構(gòu)完全是我定義的,你是被動的嘲恍。雖然可以實(shí)現(xiàn)想要的效果足画,但每個人都有自己的想法。比如那個content蛔钙,你偏偏改成text或者value來表達(dá)不行嗎锌云? 那當(dāng)然行啦!誰能犟過你啊吁脱。 但是無規(guī)矩不成方圓桑涎,既然這個方案很好用,能實(shí)現(xiàn)數(shù)據(jù)驅(qū)動視圖的最基本方案兼贡。是不是應(yīng)該大力推廣呢攻冷?那用的人一多,那就必須有一套規(guī)范了遍希,可不能亂來等曼。
這套規(guī)范叫hyperscript,簡稱為h凿蒜,就是我們熟悉的h函數(shù)啦禁谦。實(shí)際上,h函數(shù)就是我們上面討論規(guī)范后的結(jié)構(gòu)废封。具體的可以參考vue官方文檔:https://cn.vuejs.org/guide/extras/render-function.html#creating-vnodes
為什么是h函數(shù)而不是對象州泊?
在實(shí)際開發(fā)中,框架除了要處理我們的模板外漂洋,還要處理很多其他的邏輯遥皂。比如說,你寫了一個虛擬DOM表達(dá)對象:{type:"div",....}刽漂,但一個成熟可用的框架要做的事情很多演训,例如vue可能還需要維護(hù)虛擬DOM的唯一性,需要往對象里插入更多屬性配合性能優(yōu)化贝咙。所以vue自身提供了一個h函數(shù)样悟,我們可以把虛擬dom表達(dá)在函數(shù)中。vue調(diào)用h函數(shù)會經(jīng)過一層處理庭猩,再次返回一個可用的虛擬DOM樹窟她,也就是VNode。同樣眯娱,在vue3中使用jsx時礁苗,它的邏輯也是h函數(shù)的語法糖,最終vue拿到的都需要是一個Vnode徙缴。也就是一個實(shí)際可用的虛擬DOM樹试伙。
寫了這么多嘁信,終于可以回到主題了,什么是render函數(shù)疏叨? 其實(shí)render函數(shù)返回的就是我們上面寫的obj對象潘靖,只不過vue自身封裝了一個h函數(shù),h函數(shù)基于hyperscript規(guī)范同時又增加了vue中特有的字段蚤蔓,比如props卦溢。最終h函數(shù)會返回一個vue實(shí)際可用的Vnode虛擬DOM對象⌒阌郑總的來說单寂,render是返回一個可執(zhí)行的h函數(shù),h函數(shù)返回一個Vnode對象吐辙,對象結(jié)構(gòu)里表達(dá)了各種標(biāo)簽渲染規(guī)則宣决。
還記得一開始我打印的組件嗎?留給大家一個小問題:為什么vue調(diào)用render返回h函數(shù)昏苏,為什么不直接命名為h而是命名為render呢尊沸?
三、什么是JSX
我們上面聊過h函數(shù)贤惯,在vue中洼专,它就是接收hyperscript基礎(chǔ)規(guī)范語法的函數(shù)。并且h函數(shù)返回一個vue可用的虛擬DOM樹孵构。對象里描述了將來渲染真實(shí)html的規(guī)則屁商。又由于對象可以層層嵌套描述更多html規(guī)則,所以我們把這種對象稱之為虛擬DOM樹浦译。為什么說是虛擬棒假?因?yàn)樗且粋€js對象溯职,并不是真實(shí)dom精盅,真實(shí)dom是需要你根據(jù)這個對象再去處理渲染的。又因?yàn)樗鼘訉忧短缀芟褚豢脴浞植婷站疲晕覀兎Q之為虛擬DOM樹叹俏。
說這么多,那什么是jsx呢僻族?要不說程序員偷起懶來粘驰,就會促進(jìn)科技的進(jìn)步呢。實(shí)際上JSX就是一種偷懶的結(jié)果述么。我們先看下面這個示例:
const test = h('div', { id: 'foo' }, 'hello')
這是一個很簡單的h函數(shù)蝌数,里面描述了一個div,它代表根節(jié)點(diǎn)度秘。自身id為"foo"顶伞,并且這個div內(nèi)有一個文本叫"hello"。我把這個h函數(shù)用test包裝起來,Vue的h函數(shù)將會為我們返回vue可執(zhí)行的虛擬DOM樹唆貌,將來它渲染出html就是以下樣式:
<div id="foo">
hello
</div>
看起來結(jié)果很好滑潘,但現(xiàn)在我們只有一個div。我們知道在實(shí)際開發(fā)中锨咙,要寫完整個頁面可不止這一個div语卤。那總不可能所有寫頁面過程都面向這個h函數(shù)吧,要一個個手寫對象去描述html結(jié)構(gòu)酪刀,想想就頭大了粹舵。 所以這個時候jsx來了:
const test = <div id="foo">hello</div>
是的你沒看錯,我把html丟到了變量里骂倘。那有朋友就要問了:這么寫js不會報(bào)錯嗎齐婴?
會!而且大錯特錯稠茂!js才不認(rèn)識你這堆玩意兒呢柠偶。js里有這種奇葩的做法嗎?再不濟(jì)寫到j(luò)s里也應(yīng)該用字符串包裹一下吧睬关,你這直接扔過來算什么诱担?報(bào)錯是必然的。
所以电爹,JSX它注定要經(jīng)過一層編譯處理蔫仙。無論你是在原生scirpt里寫jsx語法還是在腳手架里寫jsx語法。要么你就用scirpt引入解析jsx的插件丐箩,要么你就是在腳手架里配置好jsx的插件摇邦。無論如何,它都必須要經(jīng)過一層處理屎勘。
處理什么施籍?如何處理?實(shí)際上概漱,就是把jsx的語法轉(zhuǎn)換為h函數(shù)丑慎,然后再交給實(shí)際要處理h函數(shù)渲染的邏輯去處理。
說到這里瓤摧,肯定有朋友要問了:那既然jsx還得轉(zhuǎn)換成h函數(shù)才能執(zhí)行竿裂,那不是比直接寫h函數(shù)執(zhí)行效率更低了嗎?
我說朋友照弥,你要這么想腻异,也沒錯。但是jsx我們一般是在開發(fā)環(huán)境使用这揣,畢竟一般開發(fā)react和vue的都上腳手架了是吧悔常。實(shí)際上在我們最后執(zhí)行打包操作后敢会,打包完的代碼里是沒有jsx的,所以在打包后的運(yùn)行代碼是沒有性能消耗的这嚣,也就是開發(fā)運(yùn)行時耗一點(diǎn)性能鸥昏。再說這都2024了,你是要jsx呢姐帚?還是要自己手寫h函數(shù)里的DOM結(jié)構(gòu)描述對象吏垮?
jsx作為h函數(shù)的語法糖,它并不只是帶給我們布局更方便的好處罐旗。由于我們將布局揉合到了js里膳汪,也就意味著可以在jsx模板里一起把變量渲染的邏輯做了。
比如:
const name = '張三'
const test = <div id="foo">{name}</div>
渲染結(jié)果:
<div id="foo">
張三
</div>
是不是很方便九秀?那有同學(xué)說了遗嗽,如果我變量更新了,如何保證重新渲染呢鼓蜒?
我們之前討論過痹换,jsx只不過是h函數(shù)的語法糖,而h函數(shù)也只不過是返回一個標(biāo)準(zhǔn)化的虛擬DOM樹而已都弹。說人話就是 只是統(tǒng)一了數(shù)據(jù)規(guī)范娇豫。至于如何處理jsx中的邏輯,不同的框架語言有自己的優(yōu)化邏輯畅厢。比如react能搭配hooks和state對虛擬DOM樹進(jìn)行動態(tài)更新渲染冯痢。同樣vue3中的ref也可以搭配jsx使用,就像vue3的h函數(shù)在返回可用虛擬DOM之前框杜,會往對象里插入更多其他的屬性浦楣,這些都是vue的一部分。
總之咪辱,jsx是h函數(shù)的語法糖振劳。除此之外 我們需要搭配框架語言本身提供的功能使用。也就是說梧乘,在vue3里寫jsx跟react里最簡單的不同點(diǎn)是澎迎,你們可以在jsx基本模板語法保持一致的情況下庐杨,可以使用自身框架的狀態(tài)管理和hook來編寫代碼选调。
四、VUE多種組件形式之:函數(shù)式組件(無狀態(tài)組件)
函數(shù)式組件通常用來做無狀態(tài)組件和有狀態(tài)組件灵份,這里并不是說.vue文件那種不能寫無狀態(tài)和有狀態(tài)無狀態(tài)組件仁堪,只是在無狀態(tài)組件比較簡單時,更優(yōu)先采用函數(shù)式填渠。在vue3中,平時當(dāng)我們編寫組件時逾柿,我們會在組件內(nèi)寫很多響應(yīng)式變量橄杨,可能還會寫點(diǎn)擊事件改變某些變量從而重新渲染頁面。這種有自己內(nèi)部響應(yīng)式變量需要管理維護(hù)的組件叫有狀態(tài)組件匪凉。而相反,如果一個組件它只是接收一個參數(shù)捺檬,然后渲染到頁面再层,自身并沒有定義響應(yīng)式變量和方法來異步控制頁面更新,那么這種就叫無狀態(tài)組件堡纬。 類似組件比如時間轉(zhuǎn)換組件聂受,可能你有一個組件接收一個時間props,處理后展示到頁面上烤镐,僅此而已沒有其他變量狀態(tài)需要管理蛋济。這種我們就可以稱之為無狀態(tài)組件,它就兩件事 1.接收變量2.處理變量渲染炮叶。也有可能就一件事:渲染靜態(tài)碗旅。
沒有任何自身其他的響應(yīng)式變量需要管理。
一般無狀態(tài)組件镜悉,很適合編寫一個函數(shù)扛芽,然后函數(shù)內(nèi)返回一個jsx模板,比如下面的示例:
假設(shè)有個index.jsx有以下代碼:
export default function HandleTime(){
return <div>2024年x月x日<div>
}
這是一個很普通的函數(shù)式組件积瞒,那如何使用這個組件呢川尖?
假設(shè)我們有個index.vue:
<templat>
<HanldeTime />
</template>
<script setup>
import HanldeTime from 'index.jsx'
</script>
以上代碼會在頁面渲染出以下結(jié)果
<div>2024年x月x日</div>
這是很顯而易見的結(jié)果,但是 我編寫的HandleTime不是一個函數(shù)嗎茫孔?為什么vue能把我的函數(shù)當(dāng)作模板渲染叮喳?
在第一小節(jié)中,我打印了一個.vue后綴的組件缰贝,在瀏覽器中能看到它實(shí)際上是一個對象馍悟,里面有個render方法。所以我們其實(shí)可以得到一個結(jié)論:vue的.vue后綴文件被解析成一個對象剩晴,并且在解析過程中锣咒,template會被解析成render函數(shù)。那render函數(shù)又返回什么呢赞弥?
在第二小節(jié)我已經(jīng)給了解答毅整,實(shí)際上render返回的是一個h函數(shù)。而h函數(shù)又是返回一個包裝好的虛擬DOM樹绽左,也就是Vnode悼嫉,vue拿到Vnode就能執(zhí)行渲染邏輯了。
那到底為什么vue可以把HanldeTime函數(shù)當(dāng)作模板渲染拼窥?
因?yàn)橐粋€我編寫的函數(shù)組件戏蔑,采用的是jsx的方式返回
return <div>2024年x月x日<div>
這一段return實(shí)際上就是jsx蹋凝,然而我們前面總結(jié)過,在vue中jsx其實(shí)就是h函數(shù)的語法糖总棵。 而我們又看到一個組件輸出的是一個對象里面包含render鳍寂,調(diào)用render仍然也是為了拿到h函數(shù)的結(jié)果得到Vnode。所以如果我們函數(shù)里直接返回了jsx也就意味著直接返回了Vnode情龄,所以它可以直接放在模板中當(dāng)作組件渲染伐割。
既然如此,如果項(xiàng)目中沒有用到j(luò)sx刃唤,應(yīng)當(dāng)如何返回一個無狀態(tài)組件隔心?
那就不得不手動引入vue提供的h函數(shù)了,你可以用以下寫法
import { h } from 'vue'
export default function HandleTime(){
return h('div','2024年x月x日')
}
引入這個HandleTime尚胞,渲染的效果跟用jsx是一樣的硬霍,因?yàn)閖sx就是h函數(shù)的語法糖。在沒有jsx的情況下笼裳,用h函數(shù)來應(yīng)急也是不錯的選擇唯卖,但不建議大量在項(xiàng)目中使用,是真的很難維護(hù)躬柬。
五拜轨、VUE多種組件形式之:函數(shù)式組件(有狀態(tài)組件)
我們了解過無狀態(tài)函數(shù)組件后,總會發(fā)現(xiàn)一個組件不能傳參允青、不能管理自己的變量的話橄碾,使用場景會變得很局限。所以如何給一個函數(shù)式組件傳參颠锉?
其實(shí)很簡單:
<templat>
<HandleTime name='張三' />
</template>
<script setup>
import HandleTime from 'index.jsx'
</script>
我們可以把vue的模板使用當(dāng)作函數(shù)的調(diào)用來看待法牲,這里其實(shí)就是調(diào)用了HandleTime這個函數(shù),并且將name當(dāng)作函數(shù)參數(shù)傳遞進(jìn)去了琼掠。
export default function HandleTime(props){
return <div>{props.name}<div>
}
參數(shù)會變成一個對象傳遞進(jìn)來拒垃,在HandleTime這個函數(shù)中接收,直接使用這個name就可以了瓷蛙。
那有人會問了悼瓮,如果外面的name變化了怎么辦?
其實(shí)艰猬,在vue3中横堡,只需要保證你傳遞的這個name是響應(yīng)式變量,當(dāng)name更新時姥宝,HandleTtime組件就會重新渲染翅萤。
比如你可以這么寫:
<templat>
<HandleTime :name='name' />
</template>
<script setup>
import {ref} from 'vue
import HandleTime from 'index.jsx'
const name = ref('張三')
</script>
我把name用ref包裹,并且傳遞到HandleTime組件中腊满,后續(xù)只要name更新套么,組件內(nèi)也會跟著一起重新渲染。
到此為止碳蛋,我們其實(shí)還算是無狀態(tài)組件胚泌,無非就是傳遞了一個參數(shù)進(jìn)去,如果Handle需要自己的響應(yīng)式變量怎么辦肃弟?
其實(shí)也很簡單玷室,直接引用vue的函數(shù)就好了,這也是vue3的特點(diǎn):
import { ref} from 'vue'
export default function HandleTime(props){
const count = ref(0)
return <div onClick=()=>{ count++ }>{props.name}<div>
}
這就是一個最基礎(chǔ)的有狀態(tài)組件笤受,我們可以引入Vue的hook來搭配使用穷缤。是不是很簡單?
*問題解答:為什么vue調(diào)用render返回h函數(shù)箩兽,為什么不直接命名為h而是命名為render呢津肛?
因?yàn)?vue的文件中,render只是轉(zhuǎn)換了你的template模板代碼汗贫,render實(shí)際上是不包含你的業(yè)務(wù)代碼的身坐,包括生命周期也不包含在內(nèi)。render負(fù)責(zé)渲染html那么就只是需要返回一個h函數(shù)的Vnode即可落包。 除了渲染工作部蛇,還有其他的事情要做,所以重新定義一個render函數(shù)咐蝇,可以在render函數(shù)中做很多中間處理涯鲁,最后只需要返回一個可用的Vnode即可。類似于:
{
render(){
return h('div')
}
}
而在render中還可以做很多中間操作
{
render(){
const name = 'content'
return h('div',name)
}
}
vue在渲染時有序,調(diào)用render即可拿到需要渲染的Vnode信息撮竿,而除了render,vue還可以在這個對象里存儲其他的數(shù)據(jù)笔呀,比如生命周期或一些私有變量幢踏。
這里也就意味著,我們直接用函數(shù)式組件 等于就直接返回了Vnode许师,不需要再次使用render來包裝,我們打印一個函數(shù)式組件會發(fā)現(xiàn)控制臺有如下輸出:
這就可以證明房蝉,函數(shù)式組件確實(shí)沒有render,而是直接返回的一個可執(zhí)行的函數(shù)微渠,并且里面就是Vnode搭幻,執(zhí)行就可以渲染。
但如果在特殊情況下逞盆,我們的jsx渲染或者組件不得不自己寫在對象里檀蹋,也是可以起名叫render的,畢竟這個單詞它好理解云芦,直觀俯逾。
六贸桶、VUE多種組件形式之:對象式組件
細(xì)心的朋友可能發(fā)現(xiàn)了,講了半天函數(shù)式組件桌肴,生命周期呢皇筛?昂?生命周期呢坠七?
不好意思水醋,函數(shù)式組件沒辦法使用生命周期hook。
想要在組件中使用生命周期彪置,我們先看文檔是怎么說的:
什么意思呢拄踪?意思就是說,生命周期必須要在setup周期內(nèi)執(zhí)行拳魁,生命周期鉤子也可以封裝到其他函數(shù)里惶桐,但前提是函數(shù)調(diào)用必須在setup周期內(nèi)。但請大家注意了的猛,它這里指的是.vue模板開發(fā)中耀盗,你可以把生命周期鉤子寫在別的函數(shù)里,在setup作用域使用即可卦尊。 但我們這個函數(shù)式組件是直接給模板使用了叛拷,不存在什么setup,在函數(shù)式組件里寫生命周期是無效的岂却。
但同時它也給出了答案忿薇,在setup上下文中使用即可。
還記得vue的對象式編程剛轉(zhuǎn)vue3的時候嗎躏哩?是不是用setup函數(shù)代替的署浩?那時候還沒有script setup這個用法。這里扫尺,我們也可以這樣使用:
在index.jsx中
import {onMounted} from 'vue'
export default {
setup(){
onMounted(()=>{
console.log('生命周期加載')
})
return '<div>你好</div>'
}
}
同樣在模板中
<template>
<HandleTime />
</template>
<script setup>
import HandleTime from 'index.jsx'
<script>
有朋友會細(xì)心的發(fā)現(xiàn)筋栋,不對啊,setup最終return不是代表對外暴露的變量嗎正驻?
的確是弊攘,我們回想一下,在vue3剛出來的時候姑曙,我們是不是可以在寫template的情況下襟交,然后對象上加一個setup就好了? 但是我們這邊是不是沒有template了伤靠,我們也沒辦法使用template捣域,因?yàn)槲覀冞@不是約束在.vue模板中了,那沒有template了怎么辦?
這里的問題還是焕梅,template是什么迹鹅?我們前面討論過,templte就是render函數(shù)的語法糖丘侠,而render返回的是h函數(shù)徒欣。h函數(shù)又會返回Vnode逐样。沒錯吧蜗字?所以沒有了template,我們需要手動寫上render脂新,所以以下的代碼也是可用的:
import {onMounted} from 'vue'
export default {
render(){
return '<div>你好</div>'
},
setup(){
onMounted(()=>{
console.log('生命周期加載')
})
}
}
而以上寫法挪捕,會使得變量的傳遞顯得異常的麻煩,因?yàn)樯厦娴膶懛ú欢嗾悖@里不再過多贅述级零,我們了解一下本質(zhì)就可以。
因?yàn)樽兞總鬟f過于麻煩滞乙,所以vue3還支持在setup中直接return返回模板奏纪,至于變量我們在setup中做就可以了,生命周期也能用了斩启。
那么問題來了序调,本身setup返回的是對外暴露的變量,這么一寫兔簇,還怎么暴露變量发绢?現(xiàn)在都默認(rèn)暴露的是JSX了,也就是Vnode垄琐。 怎么辦边酒?
我們可以使用expose函數(shù),它不是一個需要顯示導(dǎo)入的hook狸窘,以下是官方示例用法:
export default {
setup(props, { expose }) {
// 讓組件實(shí)例處于 “關(guān)閉狀態(tài)”
// 即不向父組件暴露任何東西
expose()
const publicCount = ref(0)
const privateCount = ref(0)
// 有選擇地暴露局部狀態(tài)
expose({ count: publicCount })
}
}
此時我們可以用expose替代原本的return暴露屬性墩朦,return我們就用來返回一個jsx。 具體的細(xì)節(jié)可以查看官方文檔:https://cn.vuejs.org/api/composition-api-setup.html#usage-with-render-functions
不知道大家注意到?jīng)]有翻擒,我導(dǎo)出的是一個對象氓涣。而函數(shù)組件我導(dǎo)出的是一個函數(shù),無論是函數(shù)還是對象韭寸,vue都可以正常解析春哨,這里面的基礎(chǔ)邏輯是什么呢?可以總結(jié)為:
當(dāng)vue模板渲染時恩伺,如果拿到的是一個函數(shù)赴背,會嘗試執(zhí)行它,如果返回的不是一個Vnode,將會在控制臺報(bào)錯凰荚,如果拿到的是Vnode燃观,將會正常渲染成html。
如果拿到的是一個對象便瑟,vue會去找這個對象的render函數(shù)缆毁,如果沒有render函數(shù)將會執(zhí)行setup,最終拿到setup返回的Vnode到涂。
七:VUE多種組件形式之:輔助函數(shù)包裝組件:defineComponent
輔助函數(shù)包裝組件這個名字是我自己取的脊框,我也不知道具體如何稱呼它,但也是字面意思践啄,它通過vue提供的輔助函數(shù)編寫組件浇雹。
它的作用主要是為Ts提供類型推導(dǎo),以下是官方語法:
import { ref, h } from 'vue'
const Comp = defineComponent(
(props) => {
// 就像在 <script setup> 中一樣使用組合式 API
const count = ref(0)
return () => {
// 渲染函數(shù)或 JSX
return h('div', count.value)
}
},
// 其他選項(xiàng)屿讽,例如聲明 props 和 emits昭灵。
{
props: {
/* ... */
}
}
)
它接收兩個參數(shù),參數(shù)1可以當(dāng)setup函數(shù)使用伐谈,最終返回一個Vnode烂完,也就是JSX。但它這里沒有基于jsx诵棵,所以返回的是h函數(shù)抠蚣。如果有引入jsx這里直接返回jsx就可以。
第二個參數(shù)是組件的一些選項(xiàng)非春,這里不再過多贅述了柱徙,就是提供多了一些TS支持,如果有需要可以用defineComponent包裝一下奇昙,它既支持生命周期又支持TS推導(dǎo)护侮,也是一個不錯的選擇,不過我比較少用储耐。
具體的可看文檔了解:https://cn.vuejs.org/api/general.html#definecomponent
最后
這篇文章我嘗試從vue的h函數(shù)開始到JSX到templat模板語法羊初,接著給大家介紹基于這些概念的幾種組件編寫方式。知識點(diǎn)多而雜什湘,如有理解錯誤的地方 還請大家指出长赞,多多包涵。感謝你的耐心閱讀闽撤。
完得哆。