一文解析Vue h函數(shù)到底是個啥
h
到底是個啥?
對于了解或?qū)W習(xí)Vue高階組件(HOC)的同學(xué)來說,h()
函數(shù)無疑是一個經(jīng)常遇到的概念。
那么,這個h()
函數(shù)究竟如何使用呢,又在什么場景下適合使用呢?
一、h 是什么
看到這個函數(shù)你可能會有些許困惑,為什么叫h呢?代表著什么呢?
官方定義:
返回一個“虛擬節(jié)點” ,通??s寫為 VNode: 一個普通對象,其中包含向 Vue 描述它應(yīng)該在頁面上呈現(xiàn)哪種節(jié)點的信息,包括對任何子節(jié)點的描述。用于手動編寫render
h
其實代表的是 hyperscript 。它是 HTML 的一部分,表示的是超文本標(biāo)記語言,當(dāng)我們正在處理一個腳本的時候,在虛擬 DOM 節(jié)點中去使用它進行替換已成為一種慣例。這個定義同時也被運用到其他的框架文檔中
Hyperscript 它本身表示的是 “生成描述 HTML 結(jié)構(gòu)的腳本”
二、語法
h()
函數(shù)的基本語法如下:
h(tag, props, children)
- tag:可以是字符串或組件,表示要創(chuàng)建的 HTML 標(biāo)簽或 Vue 組件。
- props:是一個對象,包含要傳遞給標(biāo)簽或組件的屬性,如類名、樣式、事件監(jiān)聽器等。
- children:可以是字符串、數(shù)組或函數(shù),表示子節(jié)點。子節(jié)點可以是文本、其他 VNode 或一個返回 VNode 的函數(shù)。
以下是一個簡單的代碼示例,展示了如何使用 h()
函數(shù)創(chuàng)建一個包含標(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é)果請在控制臺查看 console.log(Vue.h('h1', {}, '二川兄弟')) Vue.createApp(App).mount('#app') </script> </body> </html>
效果:
打?。?/p>
在這個示例中,我們創(chuàng)建了一個 <h1>
容器,內(nèi)部插入文字 “二川兄弟”
。所有這些都是通過 h()
函數(shù)來實現(xiàn)的,而不是使用模板語法。
三、源碼解析
要深入理解 h()
函數(shù),我們需要查看 Vue.js 的源碼。在 Vue.js 的實現(xiàn)中,h()
函數(shù)是一個封裝了 VNode 創(chuàng)建邏輯的工具函數(shù)。它接收標(biāo)簽名、屬性和子節(jié)點作為參數(shù),并返回一個包含這些信息的 VNode 對象。
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 對真實 DOM 的一種輕量級表示。它包含了節(jié)點的類型、屬性、子節(jié)點等信息,但不包含具體的 DOM 元素。Vue.js 會在渲染時將 VNode 轉(zhuǎn)換為真實的 DOM 元素。
在 h()
函數(shù)的源碼中,你會看到大量的邊界情況處理和類型檢查。例如,如果子節(jié)點是一個字符串,它會被轉(zhuǎn)換為一個文本節(jié)點;如果子節(jié)點是一個數(shù)組,它會遍歷數(shù)組并遞歸地創(chuàng)建子節(jié)點的 VNode;如果子節(jié)點是一個函數(shù),它會調(diào)用該函數(shù)并傳遞當(dāng)前上下文來創(chuàng)建子節(jié)點的 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, // 是否是動態(tài)節(jié)點,(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 一個副本 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é)點需要更新,組件節(jié)點一直存在 patchFlag,因為即使不需要更新,它需要將實例持久化到下一個 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 }
四、使用場景
h()
函數(shù)在 Vue.js 中有多種使用場景,以下是一些常見的場景:
渲染函數(shù):
- 當(dāng)需要完全控制組件的渲染邏輯時,可以使用渲染函數(shù),并在其中使用
h()
函數(shù)來創(chuàng)建 VNode。這種方式提供了比模板語法更高的靈活性和控制力。
以下是一個使用渲染函數(shù)的示例,展示了如何根據(jù)條件動態(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):
- 高階組件是一種模式,它接收一個組件作為參數(shù),并返回一個新的組件。在創(chuàng)建新組件的過程中,
h()
函數(shù)用于定制或擴展原始組件的渲染邏輯。
以下是一個高階組件的示例,展示了如何為組件添加額外的類名:
function withClassName(WrappedComponent, className) { return { props: WrappedComponent.props, render() { return h(WrappedComponent, { ...this.$props, class: className }); } }; } // 使用高階組件 const MyComponentWithClass = withClassName(MyComponent, 'my-custom-class');
動態(tài)組件:
- 在需要根據(jù)條件動態(tài)渲染不同組件時,
h()
函數(shù)可以方便地根據(jù)條件創(chuàng)建不同的 VNode。
以下是一個動態(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 }); } });
手動創(chuàng)建復(fù)雜結(jié)構(gòu):
- 在某些情況下,可能需要手動創(chuàng)建復(fù)雜的組件結(jié)構(gòu),而不是使用模板語法。這時,
h()
函數(shù)就顯得非常有用。
以下是一個手動創(chuàng)建復(fù)雜結(jié)構(gòu)的示例,展示了如何創(chuàng)建一個帶有嵌套子組件的表格:
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 }) ) ); } };
五、其他比較
與模板語法相比,使用 h()
函數(shù)提供了更高的靈活性和控制力。模板語法更適合于簡單的組件和靜態(tài)內(nèi)容,而 h()
函數(shù)則更適合于復(fù)雜的組件和動態(tài)內(nèi)容。
此外,與 JSX 相比,h()
函數(shù)更加簡潔和直接。JSX 是一種語法糖,它允許在 JavaScript 中編寫類似 HTML 的代碼。然而,JSX 需要額外的編譯步驟,并且可能會增加代碼的復(fù)雜性。而 h()
函數(shù)則是 Vue.js 內(nèi)置的工具函數(shù),無需額外的編譯步驟,并且更加符合 Vue.js 的設(shè)計哲學(xué)。
六、最佳實踐
在使用 h()
函數(shù)時,以下是一些最佳實踐:
保持簡潔:
- 盡量避免在渲染函數(shù)中編寫過多的邏輯??梢詫?fù)雜的邏輯拆分成多個函數(shù)或組件,以提高代碼的可讀性和可維護性。
使用輔助函數(shù):
- 可以編寫一些輔助函數(shù)來簡化
h()
函數(shù)的使用。例如,可以編寫一個函數(shù)來創(chuàng)建帶有特定樣式的節(jié)點,或者一個函數(shù)來創(chuàng)建帶有事件監(jiān)聽器的節(jié)點。
避免過度使用:
- 在大多數(shù)情況下,模板語法已經(jīng)足夠滿足需求。只有在需要更高的靈活性和控制力時,才應(yīng)該考慮使用
h()
函數(shù)。
與模板語法結(jié)合使用:
- 可以將
h()
函數(shù)與模板語法結(jié)合使用。例如,可以在模板中使用<script setup>
語法來定義渲染函數(shù),并在其中使用h()
函數(shù)來創(chuàng)建復(fù)雜的節(jié)點結(jié)構(gòu)。
七、總結(jié)
h()
函數(shù)是 Vue.js 框架中一個強大且靈活的工具,用于在渲染函數(shù)中創(chuàng)建虛擬 DOM 節(jié)點。通過深入理解 h()
函數(shù)的語法、源碼和使用場景,開發(fā)者可以更好地掌握 Vue.js 的渲染機制,并在實際開發(fā)中靈活運用這一工具來創(chuàng)建高效、可維護的組件。無論是編寫渲染函數(shù)、實現(xiàn)高階組件,還是處理動態(tài)組件和復(fù)雜結(jié)構(gòu),h()
函數(shù)都是不可或缺的一部分。
到此這篇關(guān)于一文解析Vue h函數(shù)到底是個啥的文章就介紹到這了,更多相關(guān)Vue h函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3如何將html元素變成canvas(海報生成),進行圖片保存/截圖
這篇文章主要介紹了vue3實現(xiàn)將html元素變成canvas(海報生成),進行圖片保存/截圖,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05vue頁面渲染數(shù)組中數(shù)據(jù)文案后添加逗號最后不加
這篇文章主要為大家介紹了vue頁面渲染數(shù)組中數(shù)據(jù)文案后添加逗號最后不加逗號示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08vue通過v-html指令渲染的富文本無法修改樣式的解決方案
這篇文章主要介紹了vue通過v-html指令渲染的富文本無法修改樣式的解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05vue中的事件觸發(fā)(emit)及監(jiān)聽(on)問題
這篇文章主要介紹了vue中的事件觸發(fā)(emit)及監(jiān)聽(on)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10