摘要:如果為,則渲染生命周期中相關(guān)的回調(diào)特別是和渲染回調(diào)函數(shù)是同步運(yùn)行的。如果為,則在下一次重繪之前,回調(diào)函數(shù)被安排為異步運(yùn)行。更改檢測(cè)策略,確定應(yīng)用程序是否需要更新外部的節(jié)點(diǎn)是一個(gè)可選的回調(diào)函數(shù),在節(jié)點(diǎn)追加到后執(zhí)行檢測(cè)外部節(jié)點(diǎn)的
部件的基本原理
部件是所有 Dojo 應(yīng)用程序的基本構(gòu)建要素。部件是主要的封裝單元,它能表示從用戶界面的單個(gè)元素,到更高級(jí)別的容器元素(如 Form 表單、段落、頁(yè)面甚至是完整的應(yīng)用程序)等所有內(nèi)容。
前言: 降低復(fù)雜度單個(gè)部件通常表示應(yīng)用程序中的單個(gè)職責(zé)。細(xì)微的職責(zé)自然會(huì)轉(zhuǎn)化為多帶帶的部件,而復(fù)雜的職責(zé)就需要拆分為幾個(gè)相互依賴的部分。然后,每部分就可以實(shí)現(xiàn)為一個(gè)部件,其中一個(gè)或多個(gè)父容器部件會(huì)協(xié)調(diào)所有拆開部件的交互。在這種層級(jí)結(jié)構(gòu)中,可以看出根部件在整體上實(shí)現(xiàn)了更大的責(zé)任,但實(shí)際上它是通過(guò)組合很多簡(jiǎn)單的部件實(shí)現(xiàn)的。
對(duì)一個(gè)完整的應(yīng)用程序的來(lái)講,它的所有需求集就是一個(gè)單一的、復(fù)雜的責(zé)任。使用 Dojo 實(shí)現(xiàn)這個(gè)完整的需求集,會(huì)產(chǎn)生具有層級(jí)結(jié)構(gòu)的部件,通常從根節(jié)點(diǎn)的“Application”部件開始,然后根據(jù)每層功能分支出層層部件,最終到達(dá)表示 HTML 頁(yè)面中單個(gè)元素的葉節(jié)點(diǎn)。
簡(jiǎn)單的好處讓部件盡可能簡(jiǎn)單的原因有:對(duì)單個(gè)部件而言,降低復(fù)雜度意味著更大的職責(zé)隔離(縮小范圍);更容易做全面測(cè)試;減少出錯(cuò)的機(jī)會(huì);更有針對(duì)性的修復(fù)錯(cuò)誤;以及更廣泛的組件復(fù)用潛力。
從整個(gè)應(yīng)用程序的層面看,簡(jiǎn)單的部件使得我們更容易理解每個(gè)組件,以及它們是如何組合在一起的。
這些好處會(huì)簡(jiǎn)化日常維護(hù),并最終降低了構(gòu)建和運(yùn)行應(yīng)用程序的總開銷。
基本的部件結(jié)構(gòu)部件的核心只是一個(gè)渲染函數(shù),該函數(shù)返回虛擬 DOM 節(jié)點(diǎn),正是通過(guò)虛擬 DOM 節(jié)點(diǎn)描述部件在網(wǎng)頁(yè)中的結(jié)構(gòu)。但是,應(yīng)用程序通常需要處理更多邏輯,不僅僅是簡(jiǎn)單的羅列 HTML 元素,因此有意義的部件通常不僅僅由簡(jiǎn)單的渲染函數(shù)組成。
部件通常位于它們各自的、多帶帶命名的 TypeScript 模塊中,且每個(gè)模塊默認(rèn)導(dǎo)出定義的部件。
表示部件最簡(jiǎn)單的方法是基于普通函數(shù),從渲染函數(shù)的工廠定義開始。Dojo 的 @dojo/framework/core/vdom 模塊中提供了一個(gè) create() 函數(shù),允許作者定義他們自己的部件渲染函數(shù)工廠。可優(yōu)先使用命名的渲染函數(shù),因?yàn)檫@樣有助于調(diào)試;但并非必須如此;部件也可以使用一個(gè)被導(dǎo)出的變量標(biāo)識(shí),該變量保存了部件的工廠定義。
對(duì)于更喜歡使用類的結(jié)構(gòu)而不是函數(shù)的應(yīng)用程序,Dojo 也提供了基于類的部件。此部件繼承 @dojo/framework/core/WidgetBase 模塊中提供的 WidgetBase,并必須要實(shí)現(xiàn)一個(gè) render() 方法。
以下示例展示了一個(gè) Dojo 應(yīng)用程序的部件,雖然沒(méi)有實(shí)際用途,但功能完整:
src/widgets/MyWidget.ts
基于函數(shù)的 Dojo 部件:
import { create } from "@dojo/framework/core/vdom"; const factory = create(); export default factory(function MyWidget() { return []; });
基于類的 Dojo 部件:
import WidgetBase from "@dojo/framework/core/WidgetBase"; export default class MyWidget extends WidgetBase { protected render() { return []; } }
因?yàn)榇瞬考匿秩竞瘮?shù)返回的是空數(shù)組,所以在應(yīng)用程序的輸出中沒(méi)有任何內(nèi)容。部件通常返回一到多個(gè)虛擬 DOM 節(jié)點(diǎn),以便在應(yīng)用程序的 HTML 輸出中包含有意義的結(jié)構(gòu)。
將虛擬 DOM 節(jié)點(diǎn)轉(zhuǎn)換為網(wǎng)頁(yè)中的輸出是由 Dojo 的渲染系統(tǒng)處理的。
部件樣式部件的 DOM 輸出的樣式是由 CSS 處理的,相關(guān)的樣式類存在 CSS 模塊文件中,它與部件的 TypeScript 模塊是對(duì)應(yīng)的。基于函數(shù)的部件和基于類的部件使用相同的樣式。該主題會(huì)在樣式和主題參考指南中詳細(xì)介紹。
渲染部件Dojo 是一個(gè)響應(yīng)式框架,負(fù)責(zé)處理數(shù)據(jù)變更的傳播和相關(guān)的后臺(tái)更新渲染。Dojo 采用虛擬 DOM(VDOM) 的概念來(lái)描述輸出的元素,VDOM 中的節(jié)點(diǎn)是簡(jiǎn)單的 JavaScript 對(duì)象,旨在提高開發(fā)人員效率,而不用與實(shí)際的 DOM 元素交互。
應(yīng)用程序只需要關(guān)心,將它們的期望的輸出結(jié)構(gòu)聲明為有層級(jí)的虛擬 DOM 節(jié)點(diǎn)即可,通常是作為部件的渲染函數(shù)的返回值來(lái)完成的。然后,框架的 Renderer 組件會(huì)將期望的輸出同步為 DOM 中的具體元素。也可以通過(guò)給虛擬 DOM 節(jié)點(diǎn)傳入屬性,從而配置部件和元素,以及為部件和元素提供狀態(tài)。
Dojo 支持樹的部分子節(jié)點(diǎn)渲染,這意味著當(dāng)狀態(tài)發(fā)生變化時(shí),框架能夠定位到受變化影響的 VDOM 節(jié)點(diǎn)的對(duì)應(yīng)子集。然后,只更新 DOM 樹中受影響的子樹,從而響應(yīng)變化、提高渲染性能并改善用戶的交互體驗(yàn)。
注意: 部件渲染函數(shù)中返回的虛擬節(jié)點(diǎn),是唯一影響應(yīng)用程序渲染的因素。嘗試使用任何其他實(shí)踐,在 Dojo 應(yīng)用程序開發(fā)中是被視為反模式的,應(yīng)當(dāng)避免。支持 TSX
Dojo 支持使用 jsx 語(yǔ)法擴(kuò)展,在 TypeScript 中被稱為 tsx。此語(yǔ)法能更方便的描述 VDOM 的輸出,并且更接近于構(gòu)建的應(yīng)用程序中的 HTML。
允許使用 TSX 的應(yīng)用程序可以通過(guò) dojo create app --tsx CLI 命令 輕松搭建出允許使用 TSX 的項(xiàng)目。
對(duì)于不是通過(guò)這種方式搭建的 Dojo 項(xiàng)目,可以通過(guò)在項(xiàng)目的 TypeScript 配置中添加以下內(nèi)容來(lái)啟用 TSX:
./tsconfig.json
{ "compilerOptions": { "jsx": "react", "jsxFactory": "tsx" }, "include": ["./src/**/*.ts", "./src/**/*.tsx", "./tests/**/*.ts", "./tests/**/*.tsx"] }TSX 部件示例
具有 .tsx 文件擴(kuò)展名的部件,要在渲染函數(shù)中輸出 TSX,只需要導(dǎo)入 @dojo/framework/core/vdom 模塊中的 tsx 函數(shù):
src/widgets/MyTsxWidget.tsx
基于函數(shù)的部件:
import { create, tsx } from "@dojo/framework/core/vdom"; const factory = create(); export default factory(function MyTsxWidget() { returnHello from a TSX widget!; });
基于類的部件:
import WidgetBase from "@dojo/framework/core/WidgetBase"; import { tsx } from "@dojo/framework/core/vdom"; export default class MyTsxWidget extends WidgetBase { protected render() { returnHello from a TSX widget!; } }
若部件需要返回多個(gè)頂級(jí) TSX 節(jié)點(diǎn),則可以將它們包裹在
src/widgets/MyTsxWidget.tsx
基于函數(shù)的部件:
import { create, tsx } from "@dojo/framework/core/vdom"; const factory = create(); export default factory(function MyTsxWidget() { return (使用 VDOM VDOM 節(jié)點(diǎn)類型); }); First top-level widget elementSecond top-level widget element
Dojo 會(huì)在 VDOM 中識(shí)別出兩類節(jié)點(diǎn):
VNode,或稱為 _Virtual Nodes_,是具體 DOM 元素的虛擬表示,作為所有 Dojo 應(yīng)用程序最底層的渲染輸出。
WNode,或稱為 _Widget Nodes_,將 Dojo 部件關(guān)聯(lián)到 VDOM 的層級(jí)結(jié)構(gòu)上。
Dojo 的虛擬節(jié)點(diǎn)中,VNode 和 WNode 都可看作 DNode 的子類型,但應(yīng)用程序通常不處理抽象層面的 DNode。推薦使用 TSX 語(yǔ)法,因?yàn)樗芤越y(tǒng)一的語(yǔ)法渲染兩類虛擬節(jié)點(diǎn)。
實(shí)例化 VDOM 節(jié)點(diǎn)如果不想使用 TSX,在部件中可以導(dǎo)入 @dojo/framework/core/vdom 模塊中的 v() 或 w() 函數(shù)。它們分別創(chuàng)建 VNode 和 WNode,并可作為部件渲染函數(shù)返回值的一部分。它們的簽名,抽象地說(shuō),如下:
v(tagName | VNode, properties?, children?):
w(Widget | constructor, properties, children?)
參數(shù) | 可選 | 描述 | |
---|---|---|---|
`tagName | VNode` | 否 | 通常,會(huì)以字符串的形式傳入 tagName,該字符串對(duì)應(yīng) VNode 將要渲染的相應(yīng) DOM 元素的標(biāo)簽名。如果傳入的是 VNode,新創(chuàng)建的 VNode 將是原始 VNode 的副本。如果傳入了 properties 參數(shù),則會(huì)合并 properties 中重復(fù)的屬性,并應(yīng)用到副本 VNode 中。如果傳入了 children 參數(shù),將在新的副本中完全覆蓋原始 VNode 中的所有子節(jié)點(diǎn)。 |
`Widget | constructor` | 否 | 通常,會(huì)傳入 Widget,它將導(dǎo)入部件當(dāng)作泛型類型引用。還可以傳入幾種類型的 constructor,它允許 Dojo 以各種不同的方式實(shí)例化部件。它們支持延遲加載等高級(jí)功能。 |
properties | v: 是, w: 否 | 用于配置新創(chuàng)建的 VDOM 節(jié)點(diǎn)的屬性集。它們還允許框架檢測(cè)節(jié)點(diǎn)是否已更新,從而重新渲染。 | |
children | 是 | 一組節(jié)點(diǎn),會(huì)渲染為新創(chuàng)建節(jié)點(diǎn)的子節(jié)點(diǎn)。如果需要,還可以使用字符串字面值表示任何文本節(jié)點(diǎn)。部件通常會(huì)封裝自己的子節(jié)點(diǎn),因此此參數(shù)更可能會(huì)與 v() 一起使用,而不是 w()。 |
以下示例部件包含一個(gè)更有代表性的渲染函數(shù),它返回一個(gè) VNode。它期望的結(jié)構(gòu)描述為,一個(gè)簡(jiǎn)單的 div DOM 元素下包含一個(gè)文本節(jié)點(diǎn):
src/widgets/MyWidget.ts
基于函數(shù)的部件:
import { create, v } from "@dojo/framework/core/vdom"; const factory = create(); export default factory(function MyWidget() { return v("div", ["Hello, Dojo!"]); });
基于類的部件:
import WidgetBase from "@dojo/framework/core/WidgetBase"; import { v } from "@dojo/framework/core/vdom"; export default class MyWidget extends WidgetBase { protected render() { return v("div", ["Hello, Dojo!"]); } }組合部件的示例
類似地,也可以使用 w() 方法組合部件,還可以混合使用兩種類型的節(jié)點(diǎn)來(lái)輸出多個(gè)節(jié)點(diǎn),以形成更復(fù)雜的層級(jí)結(jié)構(gòu):
src/widgets/MyComposingWidget.ts
基于函數(shù)的部件:
import { create, v, w } from "@dojo/framework/core/vdom"; const factory = create(); import MyWidget from "./MyWidget"; export default factory(function MyComposingWidget() { return v("div", ["This widget outputs several virtual nodes in a hierarchy", w(MyWidget, {})]); });
基于類的部件:
import WidgetBase from "@dojo/framework/core/WidgetBase"; import { v, w } from "@dojo/framework/core/vdom"; import MyWidget from "./MyWidget"; export default class MyComposingWidget extends WidgetBase { protected render() { return v("div", ["This widget outputs several virtual nodes in a hierarchy", w(MyWidget, {})]); } }渲染到 DOM 中
Dojo 為應(yīng)用程序提供了一個(gè)渲染工廠函數(shù) renderer(),@dojo/framework/core/vdom 模塊默認(rèn)導(dǎo)出該函數(shù)。提供的工廠函數(shù)定義了應(yīng)用程序的根節(jié)點(diǎn),會(huì)在此處插入 VDOM 結(jié)構(gòu)的輸出結(jié)果。
應(yīng)用程序通常在主入口點(diǎn) (main.tsx/main.ts) 調(diào)用 renderer() 函數(shù),然后將返回的 Renderer 對(duì)象掛載到應(yīng)用程序的 HTML 頁(yè)面中指定的 DOM 元素上。如果掛載應(yīng)用程序時(shí)沒(méi)有指定元素,則默認(rèn)掛載到 document.body 下。
例如:
src/main.tsx
import renderer, { tsx } from "@dojo/framework/core/vdom"; import MyComposingWidget from "./widgets/MyComposingWidget"; const r = renderer(() =>MountOptions 屬性); r.mount();
Renderer.mount() 方法接收一個(gè)可選參數(shù) MountOptions,該參數(shù)用于配置如何執(zhí)行掛載操作。
屬性 | 類型 | 可選 | 描述 |
---|---|---|---|
sync | boolean | 是 | 默認(rèn)為: false。 如果為 true,則渲染生命周期中相關(guān)的回調(diào)(特別是 after 和 deferred 渲染回調(diào)函數(shù))是同步運(yùn)行的。 如果為 false,則在 window.requestAnimationFrame() 下一次重繪之前,回調(diào)函數(shù)被安排為異步運(yùn)行。在極少數(shù)情況下,當(dāng)特定節(jié)點(diǎn)需要存在于 DOM 中時(shí),同步運(yùn)行渲染回調(diào)函數(shù)可能很有用,但對(duì)于大多數(shù)應(yīng)用程序,不建議使用此模式。 |
domNode | HTMLElement | 是 | 指定 DOM 元素,VDOM 的渲染結(jié)果會(huì)插入到該 DOM 節(jié)點(diǎn)中。如果沒(méi)有指定,則默認(rèn)為 document.body。 |
registry | Registry | 是 | 一個(gè)可選的 Registry 實(shí)例,可在掛載的 VDOM 間使用。 |
例如,將一個(gè) Dojo 應(yīng)用程序掛載到一個(gè)指定的 DOM 元素,而不是 document.body 下:
src/index.html
This div is outside the mounted Dojo application.This div contains the mounted Dojo application.
src/main.tsx
import renderer, { tsx } from "@dojo/framework/core/vdom"; import MyComposingWidget from "./widgets/MyComposingWidget"; const dojoAppRootElement = document.getElementById("my-dojo-app") || undefined; const r = renderer(() =>向 VDOM 中加入外部的 DOM 節(jié)點(diǎn)); r.mount({ domNode: dojoAppRootElement });
Dojo 可以包裝外部的 DOM 元素,有效地將它們引入到應(yīng)用程序的 VDOM 中,用作渲染輸出的一部分。這是通過(guò) @dojo/framework/core/vdom 模塊中的 dom() 工具方法完成的。它的工作原理與 v() 類似,但它的主參數(shù)使用的是現(xiàn)有的 DOM 節(jié)點(diǎn)而不是元素標(biāo)記字符串。在返回 VNode 時(shí),它會(huì)引用傳遞給它的 DOM 節(jié)點(diǎn),而不是使用 v() 新創(chuàng)建的元素。
一旦 dom() 返回的 VNode 添加到應(yīng)用程序的 VDOM 中,Dojo 應(yīng)用程序就實(shí)際獲得了被包裝 DOM 節(jié)點(diǎn)的所有權(quán)。請(qǐng)注意,此過(guò)程僅適用于 Dojo 應(yīng)用程序的外部節(jié)點(diǎn),如掛載應(yīng)用程序元素的兄弟節(jié)點(diǎn),或與主網(wǎng)頁(yè)的 DOM 斷開連接的新創(chuàng)建的節(jié)點(diǎn)。如果包裝的節(jié)點(diǎn)是掛載了應(yīng)用程序的元素的祖先或子孫節(jié)點(diǎn),將無(wú)效。
dom() APIdom({ node, attrs = {}, props = {}, on = {}, diffType = "none", onAttach })
參數(shù) | 可選 | 描述 |
---|---|---|
node | 否 | 添加到 Dojo VDOM 中的外部 DOM 節(jié)點(diǎn) |
attrs | 是 | 應(yīng)用到外部 DOM 節(jié)點(diǎn)上的 HTML 屬性(attributes) |
props | 是 | 附加到 DOM 節(jié)點(diǎn)上的屬性(properties) |
on | 是 | 應(yīng)用到外部 DOM 節(jié)點(diǎn)上的事件集合 |
diffType | 是 | 默認(rèn)為: none。更改檢測(cè)策略,確定 Dojo 應(yīng)用程序是否需要更新外部的 DOM 節(jié)點(diǎn) |
onAttach | 是 | 一個(gè)可選的回調(diào)函數(shù),在節(jié)點(diǎn)追加到 DOM 后執(zhí)行 |
通過(guò) dom() 添加的外部節(jié)點(diǎn)是從常規(guī)的虛擬 DOM 節(jié)點(diǎn)中移除的,因?yàn)樗鼈兛赡軙?huì)在 Dojo 應(yīng)用程序之外被處理。這意味著 Dojo 不能主要使用 VNode 的屬性設(shè)置元素的狀態(tài),而是必須依賴 DOM 節(jié)點(diǎn)本身的 JavaScript 屬性(properties)和 HTML 屬性(attributes)。
dom() 接收 diffType 屬性,允許用戶為包裝的節(jié)點(diǎn)指定屬性變更檢測(cè)策略。一個(gè)指定的策略,會(huì)指明如何使用包裝的節(jié)點(diǎn),以幫助 Dojo 來(lái)確定 JavaScript 屬性和 HTML 屬性是否已變化,然后將變化應(yīng)用到包裝的 DOM 節(jié)點(diǎn)上。默認(rèn)的策略是 none,意味著 Dojo 只需在每個(gè)渲染周期將包裝好的 DOM 元素添加到應(yīng)用程序輸出中。
注意: 所有的策略都使用前一次 VNode 中的事件,以確保它們會(huì)被正確的刪除并應(yīng)用到每個(gè)渲染中。
可用的 dom() 變化檢測(cè)策略:
diffType | 描述 |
---|---|
none | 此模式會(huì)為包裝的 VNode 的前一次 attributes 和 properties 傳入空對(duì)象,意味著在每個(gè)渲染周期,都會(huì)將傳給 dom() 的 props 和 attrs 重新應(yīng)用于包裝的節(jié)點(diǎn)。 |
dom | 此模式基于 DOM 節(jié)點(diǎn)中的 attributes 和 properties 與傳入 dom() 的 props 和 attrs 進(jìn)行比較計(jì)算,確定是否存在差異,然后應(yīng)用這些差異。 |
vdom | 此模式與前一次的 VNODE 做比較,這實(shí)際上是 Dojo 默認(rèn)的 VDOM 差異對(duì)比策略。在變更檢測(cè)和更新渲染時(shí)會(huì)忽略直接對(duì)包裝的節(jié)點(diǎn)所做的任何修改。 |
傳遞給 VDOM 中節(jié)點(diǎn)的屬性(properties)概念是 Dojo 的核心支柱。節(jié)點(diǎn)屬性充當(dāng)在應(yīng)用程序中傳播狀態(tài)的主要管道,可將其從父部件傳給子部件,也可以通過(guò)事件處理器逐層回傳。它們也可以作為使用者與部件交互的重要 API,為父部件傳入屬性來(lái)配置其 DOM 結(jié)構(gòu)(返回 VNode),也可以傳給其管理的子部件(返回 WNode)。
VNode 接收 VNodeProperties 類型的屬性,WNode 最低接收 WidgetProperties。部件的作者通常會(huì)定義自己的屬性接口,然后需要調(diào)用者傳入該接口。
VDOM 節(jié)點(diǎn)的 keyWidgetproperties 非常簡(jiǎn)單,只包含一個(gè)可選屬性 key,該屬性也存在于 VNodeProperties 中。
當(dāng)部件開始輸出的多個(gè)元素,處在 VDOM 的同一個(gè)層級(jí),并且類型相同,就必須指定 key。例如,一個(gè)列表部件管理了多個(gè)列表項(xiàng),就需要為列表中的每一項(xiàng)指定一個(gè) key。
當(dāng)重新渲染 VDOM 中受影響部分時(shí),Dojo 使用虛擬節(jié)點(diǎn)的 key 來(lái)唯一標(biāo)識(shí)特定實(shí)例。如果沒(méi)有使用 key 在 VDOM 中區(qū)分開同一層級(jí)中的相同類型的多個(gè)節(jié)點(diǎn),則 Dojo 就無(wú)法準(zhǔn)確地確定哪些子節(jié)點(diǎn)受到了失效更改(invalidating change)的影響。
注意: 虛擬節(jié)點(diǎn)的 key 應(yīng)在多次渲染函數(shù)的調(diào)用中保持一致。在每一次的渲染調(diào)用中,為相同的輸出節(jié)點(diǎn)生成不同的 key,在 Dojo 應(yīng)用程序開發(fā)中被認(rèn)為是反模式的,應(yīng)當(dāng)避免。配置 VNode
VNodeProperties 包含很多字段,是與 DOM 中的元素交互的重要 API。其中很多屬性鏡像了 HTMLElement 中的可用屬性,包括指定各種 oneventname 的事件處理器。
應(yīng)用程序的這些屬性是單向的,因?yàn)?Dojo 將給定的屬性集應(yīng)用到具體的 DOM 元素上,但不會(huì)將相應(yīng)的 DOM 屬性后續(xù)的任何更改同步到 VNodeProperties。任何此類更改都應(yīng)該通過(guò)事件處理器回傳給 Dojo 應(yīng)用程序。當(dāng)調(diào)用事件處理程序時(shí),應(yīng)用程序可以處理事件所需的任何狀態(tài)更改,在輸出 VDOM 結(jié)構(gòu)進(jìn)行渲染時(shí),更新對(duì)應(yīng)的 VNodeProperties 視圖,然后 Dojo 的 Renderer 會(huì)同步所有相關(guān)的 DOM 更新。
修改屬性和差異檢測(cè)Dojo 使用虛擬節(jié)點(diǎn)的屬性來(lái)確定給定節(jié)點(diǎn)是否已更新,從而是否需要重新渲染。具體來(lái)說(shuō),它使用差異檢測(cè)策略來(lái)比較前一次和當(dāng)前渲染幀的屬性集。如果在節(jié)點(diǎn)接收的最新屬性集中檢測(cè)到差異,則該節(jié)點(diǎn)將失效,并在下一個(gè)繪制周期中重新渲染。
注意: 屬性更改檢測(cè)是由框架內(nèi)部管理的,依賴于在部件的渲染函數(shù)中聲明的 VDOM 輸出結(jié)構(gòu)。試圖保留屬性的引用,并在正常的部件渲染周期之外對(duì)其進(jìn)行修改,在 Dojo 應(yīng)用程序開發(fā)中被視為反模式的,應(yīng)當(dāng)避免。支持交互 事件監(jiān)聽器
在實(shí)例化節(jié)點(diǎn)時(shí),為虛擬節(jié)點(diǎn)指定事件監(jiān)聽器的方法與指定任何其他屬性的方法相同。當(dāng)輸出 VNode 時(shí),VNodeProperties 上事件監(jiān)聽器的名字會(huì)鏡像到 HTMLElement 的等價(jià)事件上。雖然自定義部件的作者可以根據(jù)自己的選擇命名事件,但通常也遵循類似的 onEventName 的命名約定。
函數(shù)屬性(如事件處理程序)會(huì)自動(dòng)綁定到實(shí)例化此虛擬節(jié)點(diǎn)的部件的 this 上下文。但是,如果將已綁定的函數(shù)傳給屬性值,將不會(huì)重復(fù)綁定給 this。
處理 focus輸出 VNode 時(shí),部件可以使用 VNodeProperties 的 focus 屬性來(lái)控制生成的 DOM 元素在渲染時(shí)是否獲取焦點(diǎn)。這是一個(gè)特殊屬性,它可接收一個(gè) boolean 類型的對(duì)象或者是返回一個(gè) boolean 類型的函數(shù)。
當(dāng)直接傳入 true 時(shí),只有上一次的值不是 true 時(shí),元素才會(huì)獲取焦點(diǎn)(類似于常規(guī)屬性變更檢測(cè))。而傳入函數(shù)時(shí),只要函數(shù)返回 true,元素就會(huì)獲取焦點(diǎn),而不管上一次返回值。
例如:
根據(jù)元素的順序,下面的 “firstFocus” 輸入框只會(huì)在初始化渲染時(shí)獲取焦點(diǎn),而 “subsequentFocus” 輸入框在每次渲染時(shí)都會(huì)獲取焦點(diǎn),因?yàn)?focus 屬性的值是函數(shù)。
src/widgets/FocusExample.tsx
基于函數(shù)的部件:
import { create, tsx, invalidator } from "@dojo/framework/core/vdom"; const factory = create({ invalidator }); export default factory(function FocusExample({ middleware: { invalidator } }) { return (true} />); });
基于類的部件:
import WidgetBase from "@dojo/framework/core/WidgetBase"; import { tsx } from "@dojo/framework/core/vdom"; export default class FocusExample extends WidgetBase { protected render() { return (委托 focustrue} />); } }
基于函數(shù)的部件可使用 focus 中間件為其子部件設(shè)置焦點(diǎn),或者接受來(lái)自父部件的焦點(diǎn)?;陬惖牟考墒褂?FocusMixin(來(lái)自 @dojo/framework/core/mixins/Focus)以相同的方式委托 focus。
FocusMixin 會(huì)給部件的類中添加一個(gè) this.shouldFocus() 方法,而基于函數(shù)的部件使用 focus.shouldFocus() 中間件方法實(shí)現(xiàn)相同的目的。此方法會(huì)檢查部件是否處于執(zhí)行了獲取焦點(diǎn)的狀態(tài)(譯注:即調(diào)用了 this.focus()),并且僅對(duì)單個(gè)調(diào)用返回 true,直到再次調(diào)用部件的 this.focus() 方法(基于函數(shù)的部件使用等價(jià)的 focus.focus())。
FocusMixin 或者 focus 中間件也會(huì)為部件的 API 添加一個(gè) focus 函數(shù)屬性??蚣苁褂么藢傩缘牟紶柦Y(jié)果來(lái)確定渲染時(shí),部件(或其一個(gè)子部件)是否應(yīng)獲得焦點(diǎn)。通常,部件通過(guò)其 focus 屬性將 shouldFocus 方法傳遞給特定的子部件或輸出的節(jié)點(diǎn)上,從而允許父部件將焦點(diǎn)委托給其子部件。
基于函數(shù)的部件的示例,請(qǐng)參閱 Dojo 中間件參考指南中的 focus 中間件委派示例
下面基于類的部件示例,顯示了在部件層次結(jié)構(gòu)內(nèi)和輸出的 VNode 之間委托和控制焦點(diǎn):
src/widgets/FocusableWidget.tsx
import WidgetBase from "@dojo/framework/core/WidgetBase"; import { tsx } from "@dojo/framework/core/vdom"; import Focus from "@dojo/framework/core/mixins/Focus"; interface FocusInputChildProperties { onFocus: () => void; } class FocusInputChild extends Focus(WidgetBase)狀態(tài)管理{ protected render() { /* The child widget"s `this.shouldFocus()` method is assigned directly to the input node"s `focus` property, allowing focus to be delegated from a higher level containing parent widget. The input"s `onfocus()` event handler is also assigned to a method passed in from a parent widget, allowing user-driven focus changes to propagate back into the application. */ return ; } } export default class FocusableWidget extends Focus(WidgetBase) { private currentlyFocusedKey = 0; private childCount = 5; private onFocus(key: number) { this.currentlyFocusedKey = key; this.invalidate(); } /* Calling `this.focus()` resets the widget so that `this.shouldFocus()` will return true when it is next invoked. */ private focusPreviousChild() { --this.currentlyFocusedKey; if (this.currentlyFocusedKey < 0) { this.currentlyFocusedKey = this.childCount - 1; } this.focus(); } private focusNextChild() { ++this.currentlyFocusedKey; if (this.currentlyFocusedKey === this.childCount) { this.currentlyFocusedKey = 0; } this.focus(); } protected render() { /* The parent widget"s `this.shouldFocus()` method is passed to the relevant child element that requires focus, based on the simple previous/next widget selection logic. This allows focus to be delegated to a specific child node based on higher-level logic in a container/parent widget. */ return ( ); } }this.onFocus(0)} /> this.onFocus(1)} /> this.onFocus(2)} /> this.onFocus(3)} /> this.onFocus(4)} />
在數(shù)據(jù)不需要在多個(gè)組件之間流動(dòng)的簡(jiǎn)單應(yīng)用程序中,狀態(tài)管理是非常簡(jiǎn)單的。可將部件需要的數(shù)據(jù)封裝在部件內(nèi),這是 Dojo 應(yīng)用程序中狀態(tài)管理的最基本形式。
隨著應(yīng)用程序變得越來(lái)越復(fù)雜,并且開始要求在多個(gè)部件之間共享和傳輸數(shù)據(jù),就需要一種更健壯的狀態(tài)管理形式。在這里,Dojo 開始展現(xiàn)出其響應(yīng)式框架的價(jià)值,允許應(yīng)用程序定義數(shù)據(jù)如何在組件之間流動(dòng),然后由框架管理變更檢測(cè)和重新渲染。這是通過(guò)在部件的渲染函數(shù)中聲明 VDOM 輸出時(shí)將部件和屬性連接在一起而做到的。
對(duì)于大型應(yīng)用程序,狀態(tài)管理可能是最具挑戰(zhàn)性的工作之一,需要開發(fā)人員在數(shù)據(jù)一致性、可用性和容錯(cuò)性之間進(jìn)行平衡。雖然這種復(fù)雜性大多超出了 web 應(yīng)用程序?qū)拥姆秶?,?Dojo 提供了更進(jìn)一步的解決方案,以確保數(shù)據(jù)的一致性。Dojo Store 組件提供了一個(gè)集中式的狀態(tài)存儲(chǔ),它提供一致的 API,用于訪問(wèn)和管理應(yīng)用程序中多個(gè)位置的數(shù)據(jù)。
基礎(chǔ):自封裝的部件狀態(tài)部件可以通過(guò)多種方式維護(hù)其內(nèi)部狀態(tài)?;诤瘮?shù)的部件可以使用 cache 或 icache 中間件來(lái)存儲(chǔ)部件的本地狀態(tài),而基于類的部件可以使用內(nèi)部的類字段。
內(nèi)部狀態(tài)數(shù)據(jù)可能直接影響部件的渲染輸出,也可能作為屬性傳遞給子部件,而它們繼而又直接影響了子部件的渲染輸出。部件還可能允許更改其內(nèi)部狀態(tài),例如響應(yīng)用戶交互事件。
以下示例解釋了這些模式:
src/widgets/MyEncapsulatedStateWidget.tsx
基于函數(shù)的部件:
import { create, tsx } from "@dojo/framework/core/vdom"; import cache from "@dojo/framework/core/middleware/cache"; const factory = create({ cache }); export default factory(function MyEncapsulatedStateWidget({ middleware: { cache } }) { return (Current widget state: {cache.get); });("myState") || "Hello from a stateful widget!"}
基于類的部件:
import WidgetBase from "@dojo/framework/core/WidgetBase"; import { tsx } from "@dojo/framework/core/vdom"; export default class MyEncapsulatedStateWidget extends WidgetBase { private myState = "Hello from a stateful widget!"; private counter = 0; protected render() { return (Current widget state: {this.myState}); } }
注意,這個(gè)示例是不完整的,在正在運(yùn)行的應(yīng)用程序中,單擊“Change State”按鈕不會(huì)對(duì)部件的渲染輸出產(chǎn)生任何影響。這是因?yàn)闋顟B(tài)完全封裝在 MyEncapsulatedStateWidget 部件中,而 Dojo 無(wú)從得知對(duì)部件的任何更改。框架只處理了部件的初始渲染。
要通知 Dojo 重新渲染,則需要封裝渲染狀態(tài)的部件自行失效。
讓部件失效基于函數(shù)的部件可以使用 icache 中間件處理本地的狀態(tài)管理,當(dāng)狀態(tài)更新時(shí)會(huì)自動(dòng)失效部件。icache 組合了 cache 和 invalidator 中間件,擁有 cache 的處理部件狀態(tài)管理的功能,和 invalidator 的當(dāng)狀態(tài)變化時(shí)讓部件失效的功能。如果需要,基于函數(shù)的部件也可以直接使用 invalidator。
基于類的部件,則有兩種失效的方法:
在狀態(tài)被更改后的適當(dāng)位置顯式調(diào)用 this.invalidate()
在 MyEncapsulatedStateWidget 示例中,可在“Change State”按鈕的 onclick 處理函數(shù)中完成。
使用 @watch() 裝飾器(來(lái)自 @dojo/framework/core/vdomercorators/watch 模塊)注釋任何相關(guān)字段。當(dāng)修改了 @watch 注釋的字段后,將隱式調(diào)用 this.invalidate(),這對(duì)于狀態(tài)字段很有用,這些字段在更新時(shí)總是需要重新渲染。
注意: 將一個(gè)部件標(biāo)記為無(wú)效,并不會(huì)立刻重新渲染該部件,而是通知 Dojo,部件已處于 dirty 狀態(tài),應(yīng)在下一個(gè)渲染周期中進(jìn)行更新和重新渲染。這意味著在同一個(gè)渲染幀內(nèi)多次失效同一個(gè)部件并不會(huì)對(duì)應(yīng)用程序的性能產(chǎn)生負(fù)面影響,但應(yīng)避免過(guò)多重復(fù)的失效以確保最佳性能。
以下是修改過(guò)的 MyEncapsulatedStateWidget 示例,當(dāng)狀態(tài)變化時(shí)會(huì)正確地更新輸出。
基于函數(shù)的部件:
import { create, tsx } from "@dojo/framework/core/vdom"; import icache from "@dojo/framework/core/middleware/icache"; const factory = create({ icache }); export default factory(function MyEncapsulatedStateWidget({ middleware: { icache } }) { return (Current widget state: {icache.getOrSet); });("myState", "Hello from a stateful widget!")}
基于類的部件:
此處,myState 和 counter 都在應(yīng)用程序邏輯操作的同一個(gè)地方進(jìn)行了更新,因此可將 @watch() 添加到任一字段上或者同時(shí)添加到兩個(gè)字段上,這些配置的實(shí)際結(jié)果和性能狀況完全相同:
src/widgets/MyEncapsulatedStateWidget.tsx
import WidgetBase from "@dojo/framework/core/WidgetBase"; import watch from "@dojo/framework/core/decorators/watch"; import { tsx } from "@dojo/framework/core/vdom"; export default class MyEncapsulatedStateWidget extends WidgetBase { private myState: string = "Hello from a stateful widget!"; @watch() private counter: number = 0; protected render() { return (中級(jí):傳入部件屬性Current widget state: {this.myState}); } }
通過(guò)虛擬節(jié)點(diǎn)的 properties 將狀態(tài)傳入部件是 Dojo 應(yīng)用程序中連接響應(yīng)式數(shù)據(jù)流最有效的方法。
部件指定自己的屬性接口,該接口包含部件希望向使用者公開的任何字段,包括配置選項(xiàng)、表示注入狀態(tài)的字段以及任何事件處理函數(shù)。
基于函數(shù)的部件是將其屬性接口以泛型參數(shù)的形式傳給 create().properties
基于類的部件可將其屬性接口定義為類定義中 WidgetBase 的泛型參數(shù),然后通過(guò) this.properties 對(duì)象訪問(wèn)其屬性。
例如,一個(gè)支持狀態(tài)和事件處理器屬性的部件:
src/widgets/MyWidget.tsx
基于函數(shù)的部件:
import { create, tsx } from "@dojo/framework/core/vdom"; import icache from "@dojo/framework/core/middleware/icache"; const factory = create().properties<{ name: string; onNameChange?(newName: string): void; }>(); export default factory(function MyWidget({ middleware: { icache }, properties }) { const { name, onNameChange } = properties(); let newName = icache.get("new-name") || ""; return ( Hello, {name}! Not you? Set your name: { icache.set("new-name", (e.target as HTMLInputElement).value); }} />); });
基于類的部件:
import WidgetBase from "@dojo/framework/core/WidgetBase"; import { tsx } from "@dojo/framework/core/vdom"; export interface MyWidgetProperties { name: string; onNameChange?(newName: string): void; } export default class MyWidget extends WidgetBase{ private newName = ""; protected render() { const { name, onNameChange } = this.properties; return ( Hello, {name}! Not you? Set your name: { this.newName = (e.target as HTMLInputElement).value; this.invalidate(); }} />); } }
此示例部件的使用者可以通過(guò)傳入適當(dāng)?shù)膶傩耘c之交互:
src/widgets/NameHandler.tsx
基于函數(shù)的部件:
import { create, tsx } from "@dojo/framework/core/vdom"; import icache from "@dojo/framework/core/middleware/icache"; import MyWidget from "./MyWidget"; const factory = create({ icache }); export default factory(function NameHandler({ middleware: { icache } }) { let currentName = icache.get("current-name") || "Alice"; return ( { icache.set("current-name", newName); }} /> ); });
基于類的部件:
import WidgetBase from "@dojo/framework/core/WidgetBase"; import { tsx } from "@dojo/framework/core/vdom"; import watch from "@dojo/framework/core/decorators/watch"; import MyWidget from "./MyWidget"; export default class NameHandler extends WidgetBase { @watch() private currentName: string = "Alice"; protected render() { return (高級(jí):提取和注入狀態(tài){ this.currentName = newName; }} /> ); } }
實(shí)現(xiàn)復(fù)雜功能時(shí),在部件內(nèi)遵循狀態(tài)封裝模式可能會(huì)導(dǎo)致組件膨脹、難以管理。在大型應(yīng)用程序中也可能出現(xiàn)另一個(gè)問(wèn)題,數(shù)百個(gè)部件跨數(shù)十個(gè)層級(jí)組合在一起。通常是葉部件使用狀態(tài)數(shù)據(jù),并不是 VDOM 層次結(jié)構(gòu)中的中間容器。讓數(shù)據(jù)狀態(tài)穿透這樣一個(gè)層次結(jié)構(gòu)復(fù)雜的部件需要增加脆弱、不必要的代碼。
Dojo 提供的 Store 組件 解決了這些問(wèn)題,它將狀態(tài)管理提取到專用上下文中,然后將應(yīng)用程序中的相關(guān)狀態(tài)注入到特定的部件中。
最佳開發(fā)實(shí)踐使用 Dojo 部件時(shí),應(yīng)謹(jǐn)記一些重要原則,以避免在應(yīng)用程序代碼中引入反模式。試圖以不受支持的方式使用框架可能會(huì)導(dǎo)致意外的行為,并在應(yīng)用程序中引入難以發(fā)現(xiàn)的錯(cuò)誤。
部件屬性
部件應(yīng)只能讀取傳入其中的屬性(properties)。
如果修改了傳入部件中的屬性值,則不能回傳給框架,以避免導(dǎo)致部件和框架之間出現(xiàn)差異。
Widgets should avoid deriving further render state from their properties, and instead rely on their complete render state being provided to them.
Deriving render state can cause similar divergences between the widget and the framework as modifying received properties; the framework is not aware of the derived state, so cannot properly determine when a widget has been updated and requires invalidation and re-rendering.
如果需要,內(nèi)部或私有狀態(tài)可以完全封裝在部件內(nèi)。
實(shí)現(xiàn)“純”部件是一個(gè)有效且通常是可取的模式,它不會(huì)產(chǎn)生副作用,并用屬性接收它們的所有狀態(tài),但這不是開發(fā) Dojo 部件的唯一模式。
使用基于類的部件__render___, __setProperties__, and __setChildren___ 函數(shù)屬于框架內(nèi)部實(shí)現(xiàn)細(xì)節(jié),絕不允許在應(yīng)用程序中調(diào)用或覆寫。
應(yīng)用程序不應(yīng)直接實(shí)例化部件——Dojo 完全接管部件實(shí)例的生命周期,包括實(shí)例化、緩存和銷毀。
虛擬 DOM
虛擬節(jié)點(diǎn)的 key 應(yīng)在多次渲染調(diào)用中保持一致。
如果在每次渲染調(diào)用中都指定一個(gè)不同的 key,則 Dojo 無(wú)法有效地將前一次渲染和本次渲染中的相同節(jié)點(diǎn)關(guān)聯(lián)上。Dojo 會(huì)將上一次渲染中沒(méi)有看到的新 key 當(dāng)作新元素,這會(huì)導(dǎo)致從 DOM 中刪除之前的節(jié)點(diǎn)并重新添加一套,即使屬性沒(méi)有發(fā)生變化,不需要重新更新 DOM。
一個(gè)常見(jiàn)的反模式是在部件的渲染函數(shù)中為節(jié)點(diǎn)的 key 分配一個(gè)隨機(jī)生成的 ID(如 GUID 或 UUID)。除非生成策略是等冪的,否則不應(yīng)在渲染函數(shù)中生成節(jié)點(diǎn)的 key 值。
應(yīng)用程序不應(yīng)存儲(chǔ)虛擬節(jié)點(diǎn)的引用,以便從部件的渲染函數(shù)返回它們后,進(jìn)行后續(xù)操作;也不應(yīng)嘗試通過(guò)使用單個(gè)實(shí)例跨多個(gè)渲染調(diào)用來(lái)優(yōu)化內(nèi)存分配。
虛擬節(jié)點(diǎn)被設(shè)計(jì)成輕量級(jí)的,并且在每次部件渲染周期內(nèi)實(shí)例化新版本的開銷非常低。
框架依賴于在兩次部件渲染函數(shù)調(diào)用中有兩個(gè)多帶帶的虛擬節(jié)點(diǎn)實(shí)例來(lái)執(zhí)行準(zhǔn)確的更改檢測(cè)。如果未檢測(cè)到任何變化,則不會(huì)產(chǎn)生進(jìn)一步的開銷、渲染等。
渲染到 DOM 中
應(yīng)用程序不應(yīng)使用命令式的 DOM 操作調(diào)用。
框架負(fù)責(zé)處理所有具體的渲染職責(zé),并且為部件作者提供了替代機(jī)制,以更簡(jiǎn)單、類型安全和響應(yīng)式的方式使用各種 DOM 功能。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/106834.html
摘要:的中間件系統(tǒng)能以響應(yīng)式的方式管理異步或命令式,以及影響基于函數(shù)的組合部件或其他中間件的行為與屬性。控制部件渲染的生命周期對(duì)任何組合部件,中間件可以控制渲染管道的各個(gè)部分,如當(dāng)需要更新渲染時(shí)讓部件失效。 Dojo 的中間件系統(tǒng)能以響應(yīng)式的方式管理異步或命令式 API,以及影響基于函數(shù)的組合部件或其他中間件的行為與屬性 API。 框架已提供了幾個(gè)核心中間件和可選中間件,應(yīng)用程序開發(fā)人員也可...
摘要:是移動(dòng)開發(fā)人員的首選框架之一。這個(gè)框架用于開發(fā)跨平臺(tái)的移動(dòng)應(yīng)用,例如,黑莓,和等多個(gè)移動(dòng)操作系統(tǒng)。是一個(gè)用于現(xiàn)代的和移動(dòng)應(yīng)用程序的全面的框架。是一個(gè)插件,可以搭配或工作,并配備了流暢的動(dòng)畫,導(dǎo)航和移動(dòng)瀏覽器的主題如,安卓,黑莓和。 選擇正確的 JavaScript 框架,對(duì)于開發(fā)移動(dòng) Web 應(yīng)用程序是至關(guān)重要的,也是移動(dòng)應(yīng)用程序開發(fā)的一項(xiàng)重要任務(wù)。開發(fā)人員可以使用框架實(shí)現(xiàn)的功能高效地...
摘要:以下知識(shí)點(diǎn)是前輩師兄總結(jié)基礎(chǔ)語(yǔ)義化標(biāo)簽引進(jìn)了一些新的標(biāo)簽,特別注意等,注意的標(biāo)題結(jié)構(gòu)理解瀏覽器解析的過(guò)程,理解的樹形結(jié)構(gòu),及相應(yīng)理解標(biāo)簽在各個(gè)瀏覽器上的默認(rèn)樣式代理樣式,理解中的重置樣式表的概念理解等功能性標(biāo)簽理解標(biāo)簽,理解文件提交過(guò)程推薦 以下知識(shí)點(diǎn)是前輩師兄總結(jié) 1、HTML/HTML5基礎(chǔ): 1.0、語(yǔ)義化H5標(biāo)簽1.1、H5引進(jìn)了一些新的標(biāo)簽,特別注意article...
摘要:以下知識(shí)點(diǎn)是前輩師兄總結(jié)基礎(chǔ)語(yǔ)義化標(biāo)簽引進(jìn)了一些新的標(biāo)簽,特別注意等,注意的標(biāo)題結(jié)構(gòu)理解瀏覽器解析的過(guò)程,理解的樹形結(jié)構(gòu),及相應(yīng)理解標(biāo)簽在各個(gè)瀏覽器上的默認(rèn)樣式代理樣式,理解中的重置樣式表的概念理解等功能性標(biāo)簽理解標(biāo)簽,理解文件提交過(guò)程推薦 以下知識(shí)點(diǎn)是前輩師兄總結(jié) 1、HTML/HTML5基礎(chǔ): 1.0、語(yǔ)義化H5標(biāo)簽1.1、H5引進(jìn)了一些新的標(biāo)簽,特別注意article...
摘要:可以看到,第三個(gè)參數(shù)是回調(diào)函數(shù),可以直接使用依賴的模塊,他們按依賴聲明順序作為參數(shù)提供給回調(diào)函數(shù)。結(jié)論規(guī)范是開發(fā)的一次重要嘗試,它以簡(jiǎn)單而優(yōu)雅的方式統(tǒng)一了的模塊定義和加載機(jī)制,并迅速得到很多框架的認(rèn)可和采納。 1. AMD的由來(lái) 前端技術(shù)雖然在不斷發(fā)展之中,卻一直沒(méi)有質(zhì)的飛躍。除了已有的各大著名框架,比如Dojo,jQuery,ExtJs等等,很多公司也都有著自己的前端開發(fā)框架。這些...
閱讀 819·2023-04-25 20:18
閱讀 2104·2021-11-22 13:54
閱讀 2546·2021-09-26 09:55
閱讀 3912·2021-09-22 15:28
閱讀 2982·2021-09-03 10:34
閱讀 1719·2021-07-28 00:15
閱讀 1644·2019-08-30 14:25
閱讀 1288·2019-08-29 17:16