淺談vu3中的多種組件形式

前言

自從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é)果
image.png

我們可以很清楚的看到,我們打印的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)控制臺有如下輸出:


image.png

這就可以證明房蝉,函數(shù)式組件確實(shí)沒有render,而是直接返回的一個可執(zhí)行的函數(shù)微渠,并且里面就是Vnode搭幻,執(zhí)行就可以渲染。

但如果在特殊情況下逞盆,我們的jsx渲染或者組件不得不自己寫在對象里檀蹋,也是可以起名叫render的,畢竟這個單詞它好理解云芦,直觀俯逾。

六贸桶、VUE多種組件形式之:對象式組件

細(xì)心的朋友可能發(fā)現(xiàn)了,講了半天函數(shù)式組件桌肴,生命周期呢皇筛?昂?生命周期呢坠七?
不好意思水醋,函數(shù)式組件沒辦法使用生命周期hook。
想要在組件中使用生命周期彪置,我們先看文檔是怎么說的:


image.png

什么意思呢拄踪?意思就是說,生命周期必須要在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)多而雜什湘,如有理解錯誤的地方 還請大家指出长赞,多多包涵。感謝你的耐心閱讀闽撤。

完得哆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市哟旗,隨后出現(xiàn)的幾起案子贩据,更是在濱河造成了極大的恐慌栋操,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饱亮,死亡現(xiàn)場離奇詭異矾芙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)近上,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門剔宪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人壹无,你說我怎么就攤上這事葱绒。” “怎么了格遭?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵哈街,是天一觀的道長留瞳。 經(jīng)常有香客問我拒迅,道長,這世上最難降的妖魔是什么她倘? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任璧微,我火速辦了婚禮,結(jié)果婚禮上硬梁,老公的妹妹穿的比我還像新娘前硫。我一直安慰自己,他們只是感情好荧止,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布屹电。 她就那樣靜靜地躺著,像睡著了一般跃巡。 火紅的嫁衣襯著肌膚如雪危号。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天素邪,我揣著相機(jī)與錄音外莲,去河邊找鬼。 笑死兔朦,一個胖子當(dāng)著我的面吹牛偷线,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沽甥,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼声邦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了摆舟?” 一聲冷哼從身側(cè)響起亥曹,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤英融,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后歇式,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驶悟,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年材失,在試婚紗的時候發(fā)現(xiàn)自己被綠了痕鳍。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡龙巨,死狀恐怖笼呆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情旨别,我是刑警寧澤诗赌,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站秸弛,受9級特大地震影響铭若,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜递览,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一叼屠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绞铃,春花似錦镜雨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至菲盾,卻和暖如春颓影,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亿汞。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工瞭空, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疗我。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓咆畏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吴裤。 傳聞我的和親對象是個殘疾皇子旧找,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容