何時/如何使用 Vue3 render 函數

来源:https://www.cnblogs.com/guangzan/archive/2020/07/23/13358322.html
-Advertisement-
Play Games

什麼是 DOM? 如果我們把這個 HTML 載入到瀏覽器中,瀏覽器創建這些節點,用來顯示網頁。所以這個HTML映射到一系列DOM節點,然後我們可以使用JavaScript進行操作。例如: let item = document.getElementByTagName('h1')[0] item.te ...


什麼是 DOM?

如果我們把這個 HTML 載入到瀏覽器中,瀏覽器創建這些節點,用來顯示網頁。所以這個HTML映射到一系列DOM節點,然後我們可以使用JavaScript進行操作。例如:

let item = document.getElementByTagName('h1')[0]
item.textContent = "New Heading"

VDOM

網頁可以有很多DOM節點,這意味著DOM樹可以有數千個節點。這就是為什麼我們有像Vue這樣的框架,幫我們乾這些重活兒,併進行大量的JavaScript調用。

然而,搜索和更新數千個DOM節點很明顯會變慢。這就是Vue和其他類似框架有一種叫做虛擬DOM的東西。虛擬DOM是表示DOM的一種方式。例如,這個HTML也可以通過一個虛擬節點來表示,看起來像這樣。如您所見,它只是一個JavaScript對象。

<div>Hello</div>
{
    tag: 'div',
    children: [
        {
            text: 'Hello'
        }
    ]
}

Vue知道如何使用此虛擬節點並掛載到DOM上,它會更新我們在瀏覽器中看到的內容。實際上還有一個步驟其中,Vue基於我們的模板創建一個渲染函數,返回一個虛擬DOM節點。

渲染函數可以是這樣的:

render(h) {
    return h('div', 'hello')
}

當組件更改時,Render函數將重新運行,它將創建另一個虛擬節點。然後發送舊的 VNode 和新的 VNode 到Vue中進行比較並以最高效的方式在我們的網頁上更新。

我們可以將虛擬DOM和實際DOM的關係類比為藍圖和實際建築的關係。假設我更改了29樓的一些數據。我改變了傢具的佈局還加了一些櫥櫃。我有兩種方法可以改變。首先,我可以拆除29樓的一切從頭開始重建。或者我可以創造新的藍圖,比較新舊藍圖併進行更新以儘可能減少工作量。這就是虛擬DOM的工作原理。Vue 3讓這些更新更快並且更高效。

核心模塊

Vue 的三個核心模塊:

  • Reactivity Module 響應式模塊
  • Compiler Module 編譯器模塊
  • Renderer Module 渲染模塊

響應式模塊允許我們創建 JavaScript 響應對象並可以觀察其變化。當使用這些對象的代碼運行時,它們會被跟蹤,因此,它們可以在響應對象發生變化後運行。

編譯器模塊獲取 HTML 模板並將它們編譯成渲染函數。這可能在運行時在瀏覽器中發生,但在構建 Vue 項目時更常見。這樣瀏覽器就可以只接收渲染函數。

渲染模塊的代碼包含在網頁上渲染組件的三個不同階段:

  • 渲染階段
  • 掛載階段
  • 補丁階段

在渲染階段,將調用 render 函數,它返回一個虛擬 DOM 節點。
在掛載階段,使用虛擬DOM節點並調用 DOM API 來創建網頁。
在補丁階段,渲染器將舊的虛擬節點和新的虛擬節點進行比較並只更新網頁變化的部分。

現在讓我們來看一個例子,一個簡單組件的執行。它有一個模板,以及在模板內部使用的響應對象。首先,模板編譯器將 HTML 轉換為一個渲染函數。然後初始化響應對象,使用響應式模塊。接下來,在渲染模塊中,我們進入渲染階段。這將調用 render 函數,它引用了響應對象。我們現在監聽這個響應對象的變化,render 函數返回一個虛擬 DOM 節點。接下來,在掛載階段,調用 mount 函數使用虛擬 DOM 節點創建 web 頁面。最後,如果我們的響應對象發生任何變化,正在被監視,渲染器再次調用render函數,創建一個新的虛擬DOM節點。新的和舊的虛擬DOM節點,發送到補丁函數中,然後根據需要更新我們的網頁。

渲染器機制

擁有虛擬DOM層有一些好處,最重要的是它讓組件的渲染邏輯完全從真實DOM中解耦,並讓它更直接地重用框架的運行時在其他環境中。例如,Vue允許第三方開發人員創建自定義渲染解決方案目標,不僅僅是瀏覽器也包括IOS和Android等原生環境,也可以使用API創建自定義渲染器直接渲染到WebGL而不是DOM節點。在Vue 2中我們實際上已經有了這種能力但是,我們在Vue 2中提供的API沒有正式記錄並且需要分叉源代碼。所以這給維護帶來了很大的負擔,對開發這些定製解決方案的開發人員在Vue 3中,我們讓自定義渲染器API成為一等公民。因此開發人員可以直接拉取Vue運行時核心作為依賴項,然後利用自定義渲染器API構建自己的自定義渲染器。事實上,我們已經有了早期用戶報告他們已經成功地構建了一個使用Vue 3 API關於虛擬DOM的WebGL渲染器。

另一個重要方面,它提供了能力以編程方式構造、檢查、克隆以及操作所需的DOM結構,在實際返回渲染引擎之前你可以利用JavaScript的全部能力做到這些。這個能力很重要,因為總會有某些情況在UI編程中使用模板語法會有一些限制,你只需要一種有充分靈活性的合適的編程語言來表達潛在的邏輯。現在,這種情況實際上是相當罕見的在日常UI開發中。但當你在創作一個庫的時候,這種情況更常見或編寫UI組件套件,你打算上傳供第三方開發者使用。讓我們想象一下一個,像複雜類型的頂部框這樣的組件或者一個與一堆文本相關聯的輸入框,這些類型的組件通常包含很少的標記,但它們將包含很多交互邏輯在這些情況下,模板語法有時候會限制你更容易地表達潛在的邏輯,或者有時候你會發現自己在模板中加入了很多邏輯,但你還是有很多邏輯在JavaScript 中而 render 函數允許你把這些邏輯組合在一個地方你通常不需要想太多關於這些情況下的標記。

所以我理解是模板會完成你要做的事在99%的情況下你只需要寫出HTML就好了,但偶爾可能想做些更可控的事情在,你需要編寫一個渲染函數。Vue 2中的渲染函數如下所示,

render(h) {
    return h (
        'div', {
        attrs: {
            id: foo
        },
        on: {
            click: this.onClick
        },
        'hello'
    })
}

所以這是組件定義中的一個選項,相對於提供一個 template 選項,在 Vue 2 中你可以為組件提供一個渲染函數,你會得到 h 參數,直接作為渲染函數的參數。你可以用它來創造我們稱之為虛擬DOM節點,簡稱 vnode。

vnode 接受三個參數:

  • 第一個參數是類型,所以我們在這裡創建一個 div。
  • 第二個參數是一個對象包含 vnode 上的所有數據或屬性,API有點冗長從某種意義上說,你必須指明傳遞給節點的綁定類型。例如,如果要綁定屬性你必須把它嵌套在attrs對象下如果要綁定事件偵聽器你得把它列在 on 下麵。
  • 第三個參數是這個 vnode 的子節點。所以直接傳遞一個字元串是一個方便的 API,表明此節點只包含文本子節點,但它也可以是包含更多子節點的數組。所以你可以在這裡有一個數組並且嵌套了更多的嵌套 h 調用。

在Vue 3中我們改變了API,目標是簡化它。

import { h } from 'vue'

render () {
    return h(
        'div', 
        {
            id: 'foo',
            onClick: this.onClick
        },
        'hello'
    })
}

第一個顯著的變化是我們現在有了一個扁平的 props 結構。當你調用 h 時,第二個參數現在總是一個扁平的對象。你可以直接給它傳遞一個屬性,這裡我們只是給它一個 ID。按慣例監聽器以 on 開頭,所以任何帶 on 的都會自動綁定為一個監聽器所以你不必考慮太多嵌套的問題。

在大多數情況下,你也不需要思考是應將其作為 attribute 綁定還是DOM屬性綁定,因為 Vue 將智能地找出為你做這件事的最好方法。我們檢查這個 key 是否作為屬性存在在原生 DOM 中。如果存在,我們會將其設置為 property,如果它不存在,我們將它設置為一個attribute。

render API 的另一項改動是 h helper 現在是直接從 Vue 本身全局導入的。一些用戶在 Vue 2 中因為 h 在這裡傳遞而在這裡面 h 又很特別,因為它綁定到當前組件實例。當你想拆分一個大的渲染函數時,你必須把這個 h 函數一路傳遞給這些分割函數。所以,這有點困難,但有了全局引入的 h 你導入一次就可以分割你的渲染函數,在同一個文件里分割多少個都行。

渲染函數不再有 h 參數了,在內部它確實接收參數,但這隻是編譯器使用的用來生成代碼。當用戶直接使用時,他們不需要這個參數。所以,如果你用 TypeScript 使用定義的組件 API 你也會得到 this 的完整類型推斷。

Q&A

1.我知道原始的那種虛擬 Dom 的實現得到了啟發來自其他項目對嗎?

是的有一個庫叫snabbdomVue 2基本上就是從這個庫中分離出來的。

2.好的然後是Vue 3,你在這裡的編碼方式只是改進了Vue 2的模式嗎?

好吧,Vue 3是一個徹底的重寫,幾乎從頭開始一切都是定製的顯然,有現有的演算法看起來像沒有變化,因為這些是我們看到社區在做廣泛研究的領域所以這是建立在所有這些以前的實現的基礎上的但代碼本身現在是從頭開始。

3.都是用TypeScript寫的,對吧?

是的,都是 TypeScript 寫的。

何時/如何使用 render 函數

看看渲染函數在 Vue 中是什麼樣子。在 Vue 2 中,一個傳統的 Vue 組件,有一個 template 選項,但是為了重用渲染函數我們可以用一個名為 render 的函數來代替它,我們會通過參數得到這個稱為 h(hyperscript)。但在這裡,我們只是示範一下我們如何在 Vue 3 中使用它。我們會從 vue 導入 h,我們可以用它來返回 h。

import { h } from 'vue'

const App = {
    render () {
        return h('div') 
    }
}

// 等效模板中的普通 div

1.所以它返回 div 的 JavaScript 對象表示?

完全正確。

2.那麼,你的虛擬dom就像…編譯器?是編譯器接收它嗎?

是渲染器,渲染器接收它。

3.然後它實際上進行 dom 調用將其帶入瀏覽器?

完全正確。

所以我們可以給這個虛擬節點一些 props,

import { h } from 'vue'

const App = {
    render () {
        return h(
            'div',
            {
                id: 'hello'
            },
            [
                h('span','world')
            ]
        ) 
    }
}

// <div id="hello"><span>world</span></div>

現在,我們知道如何生成靜態結構。但是當人們第一次使用 render 函數會問 “我該怎麼寫,比如說,v-if 或者 v-for”?我們沒有像 v-if 或者類似的東西。相反,您可以直接使用 JavaScript。

import { h } from 'vue'

const App = {
    render () {
        return this.ok
            ? h('div',{ id: 'hello' },[h('span','world')]
            : h('p', 'other branch')
        ) 
    }
}

如果 ok 的值為 true,它將呈現 div,反之,它將呈現 p。同樣,如果你想做 v-else-if 你需要嵌套這個三元表達式:

import { h } from 'vue'

const App = {
    render () {
        return this.ok
            ? h('div',{ id: 'hello' },[h('span','world')]
            : this.otherCondition
                ? h('p', 'other branch')
                : h('span')
        ) 
    }
}

我想你可能會喜歡創建一個變數,將不同的節點添加到該變數。所以當你不得不將這整個東西嵌套在一個表達式調用中這會很有用,但你不必這麼做。

import { h } from 'vue'

let nodeToReturn
if(this.ok) {
    nodeToReturn = ...
} else if () {

}

const App = {
    render () {
        return this.ok
            ? h('div',{ id: 'hello' },[h('span','world')]
            : this.otherCondition
                ? h('p', 'other branch')
                : h('span')
        ) 
    }
}

這就是 JavaScript 靈活的地方,這看起來更像普通的 JavaScript。當你的代碼變得更加複雜時您可以使用普通的 JavaScript 重構技巧使它們更容易理解。

我們討論了 v-if, 接下來看看 v-for。 類似的,你也可以給它們加上 key,這是渲染函數中的渲染列表。

import { h } from 'vue'

const App = {
    render () {
        return this.list.map(item => {
            return h('div', {key: item.id}, item.text)
        })) 
    }
}

在渲染函數中,您可能要處理插槽。當你寫一個重標記組件(markup heavy component),或者我更喜歡稱之為特性組件(feature component),它與你的應用程式的外觀佈局結構有關,將實際的 HTML 顯示給用戶。對於那些類型的組件,我更喜歡始終使用模板。只有在我必須使用渲染函數的時候,比如我在寫一些功能型的組件,有時會期望獲取一些插槽內容,將其打包或者以某種方式操縱他們。在 Vue 3 里預設插槽將暴露在這個 this.$slot.default。如果對於組件什麼都沒有提供,這將是 undefined,所以你得先檢查一下它的存在。如果它存在,它將永遠是一個數組。有了作用域槽,我們可以將 props 傳遞給作用域槽,所以把數據傳遞到作用域槽只是通過傳遞一個參數到這個函數調用中。因為這是一個數組你可以將它直接放在 children 位置。

import { h } from 'vue'

const App = {
    render () {
        const slot = this.$slot.default
            ? this.$slot.default()
            : []
        
        return h('div', slot)
    }
}

你可以在 render 函數中用插槽做一件很強大的事,比如以某種方式操縱插槽,因為它只是一個 JavaScript 對象數組,你可以用 map 遍歷它。

import { h } from 'vue'

const App = {
    render () {
        const slot = this.$slot.default
            ? this.$slot.default()
            : []
        
        slot.map(vnode => {
            return h('div', [vnode])
        })
    }
}

這裡有一個例子,截住並更改插槽數據。假設我們有一個堆棧組件(tack component),在一些用戶界面庫(UI libraries)中很常見。你可以傳遞很多屬性給它,得到嵌套的堆棧渲染結果,有點像 HTML 中 ulol 的預設樣式。

<Stack size="4">
    <div>hello</div>
    <Stack size="4">
        <div>hello</div>
        <div>hello</div>
    </Stack>
</Stack>

渲染成這樣:

<div class="stack">
    <div class="mt-4">
         <div>hello</div>
    </div>
    <div class="mt-4">
         <div class="stack">
            <div class="mt-4">
                <div>hello</div>
            </div>
         </div>
    </div>
</div>

這裡有一個普通的基於模板的語法,在同一個插槽內它們都是預設插槽,你能做的只有渲染這個部分,在模板很難實現。但是你可以用渲染函數來實現,程式化的遍歷插槽內的每個項目然後把它們變成別的東西。

import { h } from 'vue'

const Stack = {
    render () {
        const slot = this.$slots.default
            ? this.$slots.default()
            : []
        
        return h(
            'div',
            {class: 'stack'},
            slot.map(child => {
                return h(
                        'div', 
                        {class: `mt-${this.$props.size}`},
                        [child]
                    )
            })
        )
    }
}

我們用 slot.map 生成新的 vnode 列表,原來的子插槽被包裝在裡面。有了這個,我們把它放到一個 stack.html 文件里。

stack.html

<script src="https://unpkg.com/vue@next"></script>
<style>
    .mt-4 {
        margin: 10px
    }
</style>


<div id="app"></div>

<script>
    const { h, createApp } = Vue

    const Stack = {
        render() {
            const slot = this.$slots.default
                ? this.$slots.default()
                : []

            return h(
                'div',
                { class: 'stack' },
                slot.map(child => {
                    return h('div', { class: `mt-${this.$attrs.size}` }, [child])
                    // this.$props.size ?
                })
            )
        },
    }

    const App = {
        components: {
            Stack
        },
        template: `
        <Stack size="4">
            <div>hello</div>
            <Stack size="4">
                <div>hello</div>
                <div>hello</div>
            </Stack>
        </Stack>
        `
    }

    createApp(App).mount('#app')
</script>

當你創作這些底層的公用設施組件,有時真的會遇到麻煩,這時渲染函數更有效。但話說回來,也需要瞭解每種方法的利弊,這些是為了讓你更好地理解在什麼情況下應該使用模板或使用渲染函數。基本上是當你用一個模板時遇到限制時,比如你就像我們剛纔看到的那樣,可能改為使用渲染函數會更有效。當你意識到想表達的邏輯用 JavaScript 更容易而不是使用模板語法時就使用它。從我的經驗來看,這種情況在您創作可重用的功能組件,要跨多個應用程式共用或者在組織內部共用時更常見。在日常開發中你主要是在編寫特性組件,模板通常是有效的方式,模板的好處是更簡單,當你有很多標記的時候會通過編譯器優化,它的另一個好處是它更容易讓設計師接管組件並用CSS設計樣式。因此,Vue 提供了這兩個選項,當情況出現的時候以便您可以選擇合適的方式。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 請輸入有效的11位數字手機號碼,或者包含區號的11位或12位數字座機號碼 出現這個問題按照提示上的來看應該是手機號碼輸入的不對 但是輸入的手機號碼的確是11位 這個手機號是開發者賬號綁定的手機號 也就是這個開發者賬號的蘋果ID綁定的電子郵箱和綁定的手機號碼 解決方案就是在手機上登錄上開發者的那個賬號 ...
  • 首先刪除 SceneDelegate.h 和 SceneDelegate.m ,info.plist中的 Application Scene Manifest 和 AppDelegate.m 中的 #pragma mark - UISceneSession lifecycle - (UISceneC ...
  • 功能點: 1.更新彈窗UI 2.強更與非強更且別控制 3.屏蔽物理返回鍵(因為強更的時候點擊返回鍵,彈窗會消失) 4.點擊彈窗外透明區域時,彈窗不消失 ...
  • 1.盒子模型主要定義四個區域:內容(content)、內邊距(padding)、邊框(border)、外邊距(margin) 2.border,設置元素的邊框,屬性(邊框三要素):寬度、樣式、顏色,這也是通常border屬性值的書寫順序(非嚴格要求) 單獨書寫:border-width(寬度,預設3 ...
  • 快速反饋對於任何 UI 的實現都是至關重要的。研究表明,100ms 是界面讓用戶感到即時的最大延遲。儘管如此,移動網路仍然受到一個巨大的反饋問題的困擾:觸摸任何元素後,延遲 300 毫秒。這種延遲是許多用戶認為基於 HTML 的 Web 應用程式“卡頓”的最重要原因之一。在本文中,本文將帶你瞭解移動... ...
  • 一個網站建立以後,如果不註意安全方面的問題,很容易被人攻擊,下麵就討論一下幾種漏洞情況和防止攻擊的辦法。 一、SQL註入 所謂SQL註入,就是通過把SQL命令插入到Web表單提交或輸入功能變數名稱或頁面請求的查詢字元串,最終達到欺騙伺服器執行惡意的SQL命令。具體來說,它是利用現有應用程式,將(惡意)的SQ ...
  • 通過 javascript 偽協議, HTML5 dialog 元素實現的分屏書簽程式 ...
  • 俗話說,出來江湖混的,持善良之心,懷正義之氣,總有志同道合者共相為謀。而HTML也有這麼幾個大哥們er、小兄dei、小寶貝兒。他們一起乾大事,為服務好普羅大眾貢獻自己一份力量,接下來讓我們一起走進他們吧。 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...