一文解析Vue h函數(shù)到底是個(gè)啥
h
到底是個(gè)啥?
對(duì)于了解或?qū)W習(xí)Vue高階組件(HOC)的同學(xué)來(lái)說(shuō),h()
函數(shù)無(wú)疑是一個(gè)經(jīng)常遇到的概念。
那么,這個(gè)h()
函數(shù)究竟如何使用呢,又在什么場(chǎng)景下適合使用呢?
一、h 是什么
看到這個(gè)函數(shù)你可能會(huì)有些許困惑,為什么叫h呢?代表著什么呢?
官方定義:
返回一個(gè)“虛擬節(jié)點(diǎn)” ,通??s寫為 VNode: 一個(gè)普通對(duì)象,其中包含向 Vue 描述它應(yīng)該在頁(yè)面上呈現(xiàn)哪種節(jié)點(diǎn)的信息,包括對(duì)任何子節(jié)點(diǎn)的描述。用于手動(dòng)編寫render
h
其實(shí)代表的是 hyperscript 。它是 HTML 的一部分,表示的是超文本標(biāo)記語(yǔ)言,當(dāng)我們正在處理一個(gè)腳本的時(shí)候,在虛擬 DOM 節(jié)點(diǎn)中去使用它進(jìn)行替換已成為一種慣例。這個(gè)定義同時(shí)也被運(yùn)用到其他的框架文檔中
Hyperscript 它本身表示的是 “生成描述 HTML 結(jié)構(gòu)的腳本”
二、語(yǔ)法
h()
函數(shù)的基本語(yǔ)法如下:
h(tag, props, children)
- tag:可以是字符串或組件,表示要?jiǎng)?chuàng)建的 HTML 標(biāo)簽或 Vue 組件。
- props:是一個(gè)對(duì)象,包含要傳遞給標(biāo)簽或組件的屬性,如類名、樣式、事件監(jiān)聽(tīng)器等。
- children:可以是字符串、數(shù)組或函數(shù),表示子節(jié)點(diǎn)。子節(jié)點(diǎn)可以是文本、其他 VNode 或一個(gè)返回 VNode 的函數(shù)。
以下是一個(gè)簡(jiǎn)單的代碼示例,展示了如何使用 h()
函數(shù)創(chuàng)建一個(gè)包含標(biāo)題和段落的組件:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>h()</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0-rc.4/vue.global.js"></script> </head> <body> <div id="app"> </div> <script> const App = { render() { return Vue.h('h1', {}, '二川兄弟') } } // console 結(jié)果請(qǐng)?jiān)诳刂婆_(tái)查看 console.log(Vue.h('h1', {}, '二川兄弟')) Vue.createApp(App).mount('#app') </script> </body> </html>
效果:
打?。?/p>
在這個(gè)示例中,我們創(chuàng)建了一個(gè) <h1>
容器,內(nèi)部插入文字 “二川兄弟”
。所有這些都是通過(guò) h()
函數(shù)來(lái)實(shí)現(xiàn)的,而不是使用模板語(yǔ)法。
三、源碼解析
要深入理解 h()
函數(shù),我們需要查看 Vue.js 的源碼。在 Vue.js 的實(shí)現(xiàn)中,h()
函數(shù)是一個(gè)封裝了 VNode 創(chuàng)建邏輯的工具函數(shù)。它接收標(biāo)簽名、屬性和子節(jié)點(diǎn)作為參數(shù),并返回一個(gè)包含這些信息的 VNode 對(duì)象。
export function h(type: any, propsOrChildren?: any, children?: any): VNode { if (arguments.length === 2) { if (isObject(propsOrChildren) && !isArray(propsOrChildren)) { // single vnode without props if (isVNode(propsOrChildren)) { return createVNode(type, null, [propsOrChildren]) } // props without children return createVNode(type, propsOrChildren) } else { // omit props return createVNode(type, null, propsOrChildren) } } else { if (isVNode(children)) { children = [children] } return createVNode(type, propsOrChildren, children) } }
VNode 是 Vue.js 對(duì)真實(shí) DOM 的一種輕量級(jí)表示。它包含了節(jié)點(diǎn)的類型、屬性、子節(jié)點(diǎn)等信息,但不包含具體的 DOM 元素。Vue.js 會(huì)在渲染時(shí)將 VNode 轉(zhuǎn)換為真實(shí)的 DOM 元素。
在 h()
函數(shù)的源碼中,你會(huì)看到大量的邊界情況處理和類型檢查。例如,如果子節(jié)點(diǎn)是一個(gè)字符串,它會(huì)被轉(zhuǎn)換為一個(gè)文本節(jié)點(diǎn);如果子節(jié)點(diǎn)是一個(gè)數(shù)組,它會(huì)遍歷數(shù)組并遞歸地創(chuàng)建子節(jié)點(diǎn)的 VNode;如果子節(jié)點(diǎn)是一個(gè)函數(shù),它會(huì)調(diào)用該函數(shù)并傳遞當(dāng)前上下文來(lái)創(chuàng)建子節(jié)點(diǎn)的 VNode。
_createVNode 又做了啥?
function _createVNode( type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, props: (Data & VNodeProps) | null = null, children: unknown = null, // 更新標(biāo)志 patchFlag: number = 0, // 自定義屬性 dynamicProps: string[] | null = null, // 是否是動(dòng)態(tài)節(jié)點(diǎn),(v-if v-for) isBlockNode = false ): VNode { // type必傳參數(shù) if (!type || type === NULL_DYNAMIC_COMPONENT) { if (__DEV__ && !type) { warn(`Invalid vnode type when creating vnode: ${type}.`) } type = Comment } // Class 類型的type標(biāo)準(zhǔn)化 // class component normalization. if (isFunction(type) && '__vccOpts' in type) { type = type.__vccOpts } // class & style normalization. if (props) { // props 如果是響應(yīng)式,clone 一個(gè)副本 if (isProxy(props) || InternalObjectKey in props) { props = extend({}, props) } let { class: klass, style } = props // 標(biāo)準(zhǔn)化class, 支持 string , array, object 三種形式 if (klass && !isString(klass)) { props.class = normalizeClass(klass) } // 標(biāo)準(zhǔn)化style, 支持 array ,object 兩種形式 if (isObject(style)) { // reactive state objects need to be cloned since they are likely to be // mutated if (isProxy(style) && !isArray(style)) { style = extend({}, style) } props.style = normalizeStyle(style) } } // encode the vnode type information into a bitmap const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : __FEATURE_SUSPENSE__ && isSuspense(type) ? ShapeFlags.SUSPENSE : isTeleport(type) ? ShapeFlags.TELEPORT : isObject(type) ? ShapeFlags.STATEFUL_COMPONENT : isFunction(type) ? ShapeFlags.FUNCTIONAL_COMPONENT : 0 if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) { type = toRaw(type) warn( `Vue received a Component which was made a reactive object. This can ` + `lead to unnecessary performance overhead, and should be avoided by ` + `marking the component with \`markRaw\` or using \`shallowRef\` ` + `instead of \`ref\`.`, `\nComponent that was made reactive: `, type ) } // 構(gòu)造 VNode 模型 const vnode: VNode = { __v_isVNode: true, __v_skip: true, type, props, key: props && normalizeKey(props), ref: props && normalizeRef(props), scopeId: currentScopeId, children: null, component: null, suspense: null, dirs: null, transition: null, el: null, anchor: null, target: null, targetAnchor: null, staticCount: 0, shapeFlag, patchFlag, dynamicProps, dynamicChildren: null, appContext: null } normalizeChildren(vnode, children) // presence of a patch flag indicates this node needs patching on updates. // component nodes also should always be patched, because even if the // component doesn't need to update, it needs to persist the instance on to // the next vnode so that it can be properly unmounted later. // patchFlag 標(biāo)志存在表示節(jié)點(diǎn)需要更新,組件節(jié)點(diǎn)一直存在 patchFlag,因?yàn)榧词共恍枰拢枰獙?shí)例持久化到下一個(gè) vnode,以便以后可以正確卸載它 if ( shouldTrack > 0 && !isBlockNode && currentBlock && // the EVENTS flag is only for hydration and if it is the only flag, the // vnode should not be considered dynamic due to handler caching. patchFlag !== PatchFlags.HYDRATE_EVENTS && (patchFlag > 0 || shapeFlag & ShapeFlags.SUSPENSE || shapeFlag & ShapeFlags.TELEPORT || shapeFlag & ShapeFlags.STATEFUL_COMPONENT || shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT) ) { // 壓入 VNode 棧 currentBlock.push(vnode) } return vnode }
四、使用場(chǎng)景
h()
函數(shù)在 Vue.js 中有多種使用場(chǎng)景,以下是一些常見(jiàn)的場(chǎng)景:
渲染函數(shù):
- 當(dāng)需要完全控制組件的渲染邏輯時(shí),可以使用渲染函數(shù),并在其中使用
h()
函數(shù)來(lái)創(chuàng)建 VNode。這種方式提供了比模板語(yǔ)法更高的靈活性和控制力。
以下是一個(gè)使用渲染函數(shù)的示例,展示了如何根據(jù)條件動(dòng)態(tài)渲染不同的內(nèi)容:
import { h } from 'vue'; export default { props: ['isLoggedIn'], render() { return h('div', {}, this.isLoggedIn ? h('p', {}, 'Welcome back!') : h('p', {}, 'Please log in.') ); } };
高階組件(HOC):
- 高階組件是一種模式,它接收一個(gè)組件作為參數(shù),并返回一個(gè)新的組件。在創(chuàng)建新組件的過(guò)程中,
h()
函數(shù)用于定制或擴(kuò)展原始組件的渲染邏輯。
以下是一個(gè)高階組件的示例,展示了如何為組件添加額外的類名:
function withClassName(WrappedComponent, className) { return { props: WrappedComponent.props, render() { return h(WrappedComponent, { ...this.$props, class: className }); } }; } // 使用高階組件 const MyComponentWithClass = withClassName(MyComponent, 'my-custom-class');
動(dòng)態(tài)組件:
- 在需要根據(jù)條件動(dòng)態(tài)渲染不同組件時(shí),
h()
函數(shù)可以方便地根據(jù)條件創(chuàng)建不同的 VNode。
以下是一個(gè)動(dòng)態(tài)組件的示例,展示了如何根據(jù)條件渲染不同的組件:
import { h, defineComponent } from 'vue'; import ComponentA from './ComponentA.vue'; import ComponentB from './ComponentB.vue'; export default defineComponent({ props: ['condition'], render() { return this.condition ? h(ComponentA, { ...this.$props }) : h(ComponentB, { ...this.$props }); } });
手動(dòng)創(chuàng)建復(fù)雜結(jié)構(gòu):
- 在某些情況下,可能需要手動(dòng)創(chuàng)建復(fù)雜的組件結(jié)構(gòu),而不是使用模板語(yǔ)法。這時(shí),
h()
函數(shù)就顯得非常有用。
以下是一個(gè)手動(dòng)創(chuàng)建復(fù)雜結(jié)構(gòu)的示例,展示了如何創(chuàng)建一個(gè)帶有嵌套子組件的表格:
import { h } from 'vue'; import TableRow from './TableRow.vue'; export default { props: ['data'], render() { return h('table', {}, this.data.map(row => h(TableRow, { key: row.id, row: row }) ) ); } };
五、其他比較
與模板語(yǔ)法相比,使用 h()
函數(shù)提供了更高的靈活性和控制力。模板語(yǔ)法更適合于簡(jiǎn)單的組件和靜態(tài)內(nèi)容,而 h()
函數(shù)則更適合于復(fù)雜的組件和動(dòng)態(tài)內(nèi)容。
此外,與 JSX 相比,h()
函數(shù)更加簡(jiǎn)潔和直接。JSX 是一種語(yǔ)法糖,它允許在 JavaScript 中編寫類似 HTML 的代碼。然而,JSX 需要額外的編譯步驟,并且可能會(huì)增加代碼的復(fù)雜性。而 h()
函數(shù)則是 Vue.js 內(nèi)置的工具函數(shù),無(wú)需額外的編譯步驟,并且更加符合 Vue.js 的設(shè)計(jì)哲學(xué)。
六、最佳實(shí)踐
在使用 h()
函數(shù)時(shí),以下是一些最佳實(shí)踐:
保持簡(jiǎn)潔:
- 盡量避免在渲染函數(shù)中編寫過(guò)多的邏輯??梢詫?fù)雜的邏輯拆分成多個(gè)函數(shù)或組件,以提高代碼的可讀性和可維護(hù)性。
使用輔助函數(shù):
- 可以編寫一些輔助函數(shù)來(lái)簡(jiǎn)化
h()
函數(shù)的使用。例如,可以編寫一個(gè)函數(shù)來(lái)創(chuàng)建帶有特定樣式的節(jié)點(diǎn),或者一個(gè)函數(shù)來(lái)創(chuàng)建帶有事件監(jiān)聽(tīng)器的節(jié)點(diǎn)。
避免過(guò)度使用:
- 在大多數(shù)情況下,模板語(yǔ)法已經(jīng)足夠滿足需求。只有在需要更高的靈活性和控制力時(shí),才應(yīng)該考慮使用
h()
函數(shù)。
與模板語(yǔ)法結(jié)合使用:
- 可以將
h()
函數(shù)與模板語(yǔ)法結(jié)合使用。例如,可以在模板中使用<script setup>
語(yǔ)法來(lái)定義渲染函數(shù),并在其中使用h()
函數(shù)來(lái)創(chuàng)建復(fù)雜的節(jié)點(diǎn)結(jié)構(gòu)。
七、總結(jié)
h()
函數(shù)是 Vue.js 框架中一個(gè)強(qiáng)大且靈活的工具,用于在渲染函數(shù)中創(chuàng)建虛擬 DOM 節(jié)點(diǎn)。通過(guò)深入理解 h()
函數(shù)的語(yǔ)法、源碼和使用場(chǎng)景,開(kāi)發(fā)者可以更好地掌握 Vue.js 的渲染機(jī)制,并在實(shí)際開(kāi)發(fā)中靈活運(yùn)用這一工具來(lái)創(chuàng)建高效、可維護(hù)的組件。無(wú)論是編寫渲染函數(shù)、實(shí)現(xiàn)高階組件,還是處理動(dòng)態(tài)組件和復(fù)雜結(jié)構(gòu),h()
函數(shù)都是不可或缺的一部分。
到此這篇關(guān)于一文解析Vue h函數(shù)到底是個(gè)啥的文章就介紹到這了,更多相關(guān)Vue h函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue?監(jiān)聽(tīng)視頻播放時(shí)長(zhǎng)的實(shí)例代碼
本文介紹了如何通過(guò)源碼實(shí)現(xiàn)對(duì)視頻實(shí)時(shí)時(shí)長(zhǎng)、播放時(shí)長(zhǎng)和暫停時(shí)長(zhǎng)的監(jiān)聽(tīng),詳細(xì)闡述了相關(guān)技術(shù)的應(yīng)用方法,幫助開(kāi)發(fā)者更好地掌握視頻監(jiān)控技術(shù),提高用戶體驗(yàn)2024-10-10vue3如何將html元素變成canvas(海報(bào)生成),進(jìn)行圖片保存/截圖
這篇文章主要介紹了vue3實(shí)現(xiàn)將html元素變成canvas(海報(bào)生成),進(jìn)行圖片保存/截圖,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05vue頁(yè)面渲染數(shù)組中數(shù)據(jù)文案后添加逗號(hào)最后不加
這篇文章主要為大家介紹了vue頁(yè)面渲染數(shù)組中數(shù)據(jù)文案后添加逗號(hào)最后不加逗號(hào)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Vue SPA單頁(yè)面的應(yīng)用和對(duì)比
單頁(yè)面是指整個(gè)應(yīng)用程序只有一個(gè)唯一完整的 HTML 頁(yè)面,而其它所謂的頁(yè)面,其實(shí)都是組件片段而已,切換頁(yè)面也只是切換一個(gè) HTML 中顯示不同的組件片段。在今后所有的開(kāi)發(fā)項(xiàng)目都是單頁(yè)面應(yīng)用2022-08-08關(guān)于Vue-cli3煩人的eslint問(wèn)題
這篇文章主要介紹了關(guān)于Vue-cli3煩人的eslint問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09vue通過(guò)v-html指令渲染的富文本無(wú)法修改樣式的解決方案
這篇文章主要介紹了vue通過(guò)v-html指令渲染的富文本無(wú)法修改樣式的解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05vue中的事件觸發(fā)(emit)及監(jiān)聽(tīng)(on)問(wèn)題
這篇文章主要介紹了vue中的事件觸發(fā)(emit)及監(jiān)聽(tīng)(on)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10vue使用SVG實(shí)現(xiàn)圓形進(jìn)度條音樂(lè)播放
這篇文章主要為大家詳細(xì)介紹了vue使用SVG實(shí)現(xiàn)圓形進(jìn)度條音樂(lè)播放,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04