溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Dojo部件怎么用

發(fā)布時間:2021-10-19 15:29:12 來源:億速云 閱讀:129 作者:小新 欄目:web開發(fā)

這篇文章主要介紹了Dojo部件怎么用,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

部件的基本原理

部件是所有 Dojo 應(yīng)用程序的基本構(gòu)建要素。部件是主要的封裝單元,它能表示從用戶界面的單個元素,到更高級別的容器元素(如 Form 表單、段落、頁面甚至是完整的應(yīng)用程序)等所有內(nèi)容。

前言: 降低復(fù)雜度

單個部件通常表示應(yīng)用程序中的單個職責(zé)。細(xì)微的職責(zé)自然會轉(zhuǎn)化為單獨的部件,而復(fù)雜的職責(zé)就需要拆分為幾個相互依賴的部分。然后,每部分就可以實現(xiàn)為一個部件,其中一個或多個父容器部件會協(xié)調(diào)所有拆開部件的交互。在這種層級結(jié)構(gòu)中,可以看出根部件在整體上實現(xiàn)了更大的責(zé)任,但實際上它是通過組合很多簡單的部件實現(xiàn)的。

對一個完整的應(yīng)用程序的來講,它的所有需求集就是一個單一的、復(fù)雜的責(zé)任。使用 Dojo 實現(xiàn)這個完整的需求集,會產(chǎn)生具有層級結(jié)構(gòu)的部件,通常從根節(jié)點的“Application”部件開始,然后根據(jù)每層功能分支出層層部件,最終到達(dá)表示 HTML 頁面中單個元素的葉節(jié)點。

簡單的好處

讓部件盡可能簡單的原因有:對單個部件而言,降低復(fù)雜度意味著更大的職責(zé)隔離(縮小范圍);更容易做全面測試;減少出錯的機(jī)會;更有針對性的修復(fù)錯誤;以及更廣泛的組件復(fù)用潛力。

從整個應(yīng)用程序的層面看,簡單的部件使得我們更容易理解每個組件,以及它們是如何組合在一起的。

這些好處會簡化日常維護(hù),并最終降低了構(gòu)建和運行應(yīng)用程序的總開銷。

基本的部件結(jié)構(gòu)

部件的核心只是一個渲染函數(shù),該函數(shù)返回虛擬 DOM 節(jié)點,正是通過虛擬 DOM 節(jié)點描述部件在網(wǎng)頁中的結(jié)構(gòu)。但是,應(yīng)用程序通常需要處理更多邏輯,不僅僅是簡單的羅列 HTML 元素,因此有意義的部件通常不僅僅由簡單的渲染函數(shù)組成。

部件通常位于它們各自的、單獨命名的 TypeScript 模塊中,且每個模塊默認(rèn)導(dǎo)出定義的部件。

表示部件最簡單的方法是基于普通函數(shù),從渲染函數(shù)的工廠定義開始。Dojo 的 @dojo/framework/core/vdom 模塊中提供了一個 create() 函數(shù),允許作者定義他們自己的部件渲染函數(shù)工廠??蓛?yōu)先使用命名的渲染函數(shù),因為這樣有助于調(diào)試;但并非必須如此;部件也可以使用一個被導(dǎo)出的變量標(biāo)識,該變量保存了部件的工廠定義。

對于更喜歡使用類的結(jié)構(gòu)而不是函數(shù)的應(yīng)用程序,Dojo 也提供了基于類的部件。此部件繼承 @dojo/framework/core/WidgetBase 模塊中提供的 WidgetBase,并必須要實現(xiàn)一個 render() 方法。

以下示例展示了一個 Dojo 應(yīng)用程序的部件,雖然沒有實際用途,但功能完整:

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 [];
    }
}

因為此部件的渲染函數(shù)返回的是空數(shù)組,所以在應(yīng)用程序的輸出中沒有任何內(nèi)容。部件通常返回一到多個虛擬 DOM 節(jié)點,以便在應(yīng)用程序的 HTML 輸出中包含有意義的結(jié)構(gòu)。

將虛擬 DOM 節(jié)點轉(zhuǎn)換為網(wǎng)頁中的輸出是由 Dojo 的渲染系統(tǒng)處理的。

部件樣式

部件的 DOM 輸出的樣式是由 CSS 處理的,相關(guān)的樣式類存在 CSS 模塊文件中,它與部件的 TypeScript 模塊是對應(yīng)的?;诤瘮?shù)的部件和基于類的部件使用相同的樣式。該主題會在樣式和主題參考指南中詳細(xì)介紹。

渲染部件

Dojo 是一個響應(yīng)式框架,負(fù)責(zé)處理數(shù)據(jù)變更的傳播和相關(guān)的后臺更新渲染。Dojo 采用虛擬 DOM(VDOM) 的概念來描述輸出的元素,VDOM 中的節(jié)點是簡單的 JavaScript 對象,旨在提高開發(fā)人員效率,而不用與實際的 DOM 元素交互。

應(yīng)用程序只需要關(guān)心,將它們的期望的輸出結(jié)構(gòu)聲明為有層級的虛擬 DOM 節(jié)點即可,通常是作為部件的渲染函數(shù)的返回值來完成的。然后,框架的 Renderer 組件會將期望的輸出同步為 DOM 中的具體元素。也可以通過給虛擬 DOM 節(jié)點傳入屬性,從而配置部件和元素,以及為部件和元素提供狀態(tài)。

Dojo 支持樹的部分子節(jié)點渲染,這意味著當(dāng)狀態(tài)發(fā)生變化時,框架能夠定位到受變化影響的 VDOM 節(jié)點的對應(yīng)子集。然后,只更新 DOM 樹中受影響的子樹,從而響應(yīng)變化、提高渲染性能并改善用戶的交互體驗。

注意: 部件渲染函數(shù)中返回的虛擬節(jié)點,是唯一影響應(yīng)用程序渲染的因素。嘗試使用任何其他實踐,在 Dojo 應(yīng)用程序開發(fā)中是被視為反模式的,應(yīng)當(dāng)避免。

支持 TSX

Dojo 支持使用 jsx 語法擴(kuò)展,在 TypeScript 中被稱為 tsx。此語法能更方便的描述 VDOM 的輸出,并且更接近于構(gòu)建的應(yīng)用程序中的 HTML。

允許使用 TSX 的應(yīng)用程序

可以通過 dojo create app --tsx CLI 命令 輕松搭建出允許使用 TSX 的項目。

對于不是通過這種方式搭建的 Dojo 項目,可以通過在項目的 TypeScript 配置中添加以下內(nè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() {
    return <div>Hello from a TSX widget!</div>;
});

基于類的部件:

import WidgetBase from '@dojo/framework/core/WidgetBase';
import { tsx } from '@dojo/framework/core/vdom';

export default class MyTsxWidget extends WidgetBase {
    protected render() {
        return <div>Hello from a TSX widget!</div>;
    }
}

若部件需要返回多個頂級 TSX 節(jié)點,則可以將它們包裹在 &lt;virtual&gt; 容器元素中。這比返回節(jié)點數(shù)組更清晰明了,因為這樣支持更自然的自動格式化 TSX 代碼塊。如下:

src/widgets/MyTsxWidget.tsx

基于函數(shù)的部件:

import { create, tsx } from '@dojo/framework/core/vdom';

const factory = create();

export default factory(function MyTsxWidget() {
    return (
        <virtual>
            <div>First top-level widget element</div>
            <div>Second top-level widget element</div>
        </virtual>
    );
});

使用 VDOM

VDOM 節(jié)點類型

Dojo 會在 VDOM 中識別出兩類節(jié)點:

  • VNode,或稱為 Virtual Nodes,是具體 DOM 元素的虛擬表示,作為所有 Dojo 應(yīng)用程序最底層的渲染輸出。

  • WNode,或稱為 Widget Nodes,將 Dojo 部件關(guān)聯(lián)到 VDOM 的層級結(jié)構(gòu)上。

Dojo 的虛擬節(jié)點中,VNodeWNode 都可看作 DNode 的子類型,但應(yīng)用程序通常不處理抽象層面的 DNode。推薦使用 TSX 語法,因為它能以統(tǒng)一的語法渲染兩類虛擬節(jié)點。

實例化 VDOM 節(jié)點

如果不想使用 TSX,在部件中可以導(dǎo)入 @dojo/framework/core/vdom 模塊中的 v()w() 函數(shù)。它們分別創(chuàng)建 VNodeWNode,并可作為部件渲染函數(shù)返回值的一部分。它們的簽名,抽象地說,如下:

  • v(tagName | VNode, properties?, children?):

  • w(Widget | constructor, properties, children?)

參數(shù)可選描述
tagName | VNode通常,會以字符串的形式傳入 tagName,該字符串對應(yīng) VNode 將要渲染的相應(yīng) DOM 元素的標(biāo)簽名。如果傳入的是 VNode,新創(chuàng)建的 VNode 將是原始 VNode 的副本。如果傳入了 properties 參數(shù),則會合并 properties 中重復(fù)的屬性,并應(yīng)用到副本 VNode 中。如果傳入了 children 參數(shù),將在新的副本中完全覆蓋原始 VNode 中的所有子節(jié)點。
Widget | constructor通常,會傳入 Widget,它將導(dǎo)入部件當(dāng)作泛型類型引用。還可以傳入幾種類型的 constructor,它允許 Dojo 以各種不同的方式實例化部件。它們支持延遲加載等高級功能。
propertiesv: 是, w: 否用于配置新創(chuàng)建的 VDOM 節(jié)點的屬性集。它們還允許框架檢測節(jié)點是否已更新,從而重新渲染。
children一組節(jié)點,會渲染為新創(chuàng)建節(jié)點的子節(jié)點。如果需要,還可以使用字符串字面值表示任何文本節(jié)點。部件通常會封裝自己的子節(jié)點,因此此參數(shù)更可能會與 v() 一起使用,而不是 w()

虛擬節(jié)點示例

以下示例部件包含一個更有代表性的渲染函數(shù),它返回一個 VNode。它期望的結(jié)構(gòu)描述為,一個簡單的 div DOM 元素下包含一個文本節(jié)點:

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é)點來輸出多個節(jié)點,以形成更復(fù)雜的層級結(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)用程序提供了一個渲染工廠函數(shù) renderer(),@dojo/framework/core/vdom 模塊默認(rèn)導(dǎo)出該函數(shù)。提供的工廠函數(shù)定義了應(yīng)用程序的根節(jié)點,會在此處插入 VDOM 結(jié)構(gòu)的輸出結(jié)果。

應(yīng)用程序通常在主入口點 (main.tsx/main.ts) 調(diào)用 renderer() 函數(shù),然后將返回的 Renderer 對象掛載到應(yīng)用程序的 HTML 頁面中指定的 DOM 元素上。如果掛載應(yīng)用程序時沒有指定元素,則默認(rèn)掛載到 document.body 下。

例如:

src/main.tsx

import renderer, { tsx } from '@dojo/framework/core/vdom';

import MyComposingWidget from './widgets/MyComposingWidget';

const r = renderer(() => <MyComposingWidget />);
r.mount();

MountOptions 屬性

Renderer.mount() 方法接收一個可選參數(shù) MountOptions,該參數(shù)用于配置如何執(zhí)行掛載操作。

屬性類型可選描述
syncboolean默認(rèn)為: false。 如果為 true,則渲染生命周期中相關(guān)的回調(diào)(特別是 afterdeferred 渲染回調(diào)函數(shù))是同步運行的。 如果為 false,則在 window.requestAnimationFrame() 下一次重繪之前,回調(diào)函數(shù)被安排為異步運行。在極少數(shù)情況下,當(dāng)特定節(jié)點需要存在于 DOM 中時,同步運行渲染回調(diào)函數(shù)可能很有用,但對于大多數(shù)應(yīng)用程序,不建議使用此模式。
domNodeHTMLElement指定 DOM 元素,VDOM 的渲染結(jié)果會插入到該 DOM 節(jié)點中。如果沒有指定,則默認(rèn)為 document.body。
registryRegistry一個可選的 Registry 實例,可在掛載的 VDOM 間使用。

例如,將一個 Dojo 應(yīng)用程序掛載到一個指定的 DOM 元素,而不是 document.body 下:

src/index.html

<!DOCTYPE html>
<html lang="en-us">
    <body>
        <div>This div is outside the mounted Dojo application.</div>
        <div id="my-dojo-app">This div contains the mounted Dojo application.</div>
    </body>
</html>

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(() => <MyComposingWidget />);
r.mount({ domNode: dojoAppRootElement });

向 VDOM 中加入外部的 DOM 節(jié)點

Dojo 可以包裝外部的 DOM 元素,有效地將它們引入到應(yīng)用程序的 VDOM 中,用作渲染輸出的一部分。這是通過 @dojo/framework/core/vdom 模塊中的 dom() 工具方法完成的。它的工作原理與 v() 類似,但它的主參數(shù)使用的是現(xiàn)有的 DOM 節(jié)點而不是元素標(biāo)記字符串。在返回 VNode 時,它會引用傳遞給它的 DOM 節(jié)點,而不是使用 v() 新創(chuàng)建的元素。

一旦 dom() 返回的 VNode 添加到應(yīng)用程序的 VDOM 中,Dojo 應(yīng)用程序就實際獲得了被包裝 DOM 節(jié)點的所有權(quán)。請注意,此過程僅適用于 Dojo 應(yīng)用程序的外部節(jié)點,如掛載應(yīng)用程序元素的兄弟節(jié)點,或與主網(wǎng)頁的 DOM 斷開連接的新創(chuàng)建的節(jié)點。如果包裝的節(jié)點是掛載了應(yīng)用程序的元素的祖先或子孫節(jié)點,將無效。

dom() API

  • dom({ node, attrs = {}, props = {}, on = {}, diffType = 'none', onAttach })

參數(shù)可選描述
node添加到 Dojo VDOM 中的外部 DOM 節(jié)點
attrs應(yīng)用到外部 DOM 節(jié)點上的 HTML 屬性(attributes)
props附加到 DOM 節(jié)點上的屬性(properties)
on應(yīng)用到外部 DOM 節(jié)點上的事件集合
diffType默認(rèn)為: none。更改檢測策略,確定 Dojo 應(yīng)用程序是否需要更新外部的 DOM 節(jié)點
onAttach一個可選的回調(diào)函數(shù),在節(jié)點追加到 DOM 后執(zhí)行

檢測外部 DOM 節(jié)點的變化

通過 dom() 添加的外部節(jié)點是從常規(guī)的虛擬 DOM 節(jié)點中移除的,因為它們可能會在 Dojo 應(yīng)用程序之外被處理。這意味著 Dojo 不能主要使用 VNode 的屬性設(shè)置元素的狀態(tài),而是必須依賴 DOM 節(jié)點本身的 JavaScript 屬性(properties)和 HTML 屬性(attributes)。

dom() 接收 diffType 屬性,允許用戶為包裝的節(jié)點指定屬性變更檢測策略。一個指定的策略,會指明如何使用包裝的節(jié)點,以幫助 Dojo 來確定 JavaScript 屬性和 HTML 屬性是否已變化,然后將變化應(yīng)用到包裝的 DOM 節(jié)點上。默認(rèn)的策略是 none,意味著 Dojo 只需在每個渲染周期將包裝好的 DOM 元素添加到應(yīng)用程序輸出中。

注意: 所有的策略都使用前一次 VNode 中的事件,以確保它們會被正確的刪除并應(yīng)用到每個渲染中。

可用的 dom() 變化檢測策略:

diffType描述
none此模式會為包裝的 VNode 的前一次 attributesproperties 傳入空對象,意味著在每個渲染周期,都會將傳給 dom()propsattrs 重新應(yīng)用于包裝的節(jié)點。
dom此模式基于 DOM 節(jié)點中的 attributesproperties 與傳入 dom()propsattrs 進(jìn)行比較計算,確定是否存在差異,然后應(yīng)用這些差異。
vdom此模式與前一次的 VNODE 做比較,這實際上是 Dojo 默認(rèn)的 VDOM 差異對比策略。在變更檢測和更新渲染時會忽略直接對包裝的節(jié)點所做的任何修改。

通過屬性配置部件

傳遞給 VDOM 中節(jié)點的屬性(properties)概念是 Dojo 的核心支柱。節(jié)點屬性充當(dāng)在應(yīng)用程序中傳播狀態(tài)的主要管道,可將其從父部件傳給子部件,也可以通過事件處理器逐層回傳。它們也可以作為使用者與部件交互的重要 API,為父部件傳入屬性來配置其 DOM 結(jié)構(gòu)(返回 VNode),也可以傳給其管理的子部件(返回 WNode)。

VNode 接收 VNodeProperties 類型的屬性,WNode 最低接收 WidgetProperties。部件的作者通常會定義自己的屬性接口,然后需要調(diào)用者傳入該接口。

VDOM 節(jié)點的 key

Widgetproperties 非常簡單,只包含一個可選屬性 key,該屬性也存在于 VNodeProperties 中。

當(dāng)部件開始輸出的多個元素,處在 VDOM 的同一個層級,并且類型相同,就必須指定 key。例如,一個列表部件管理了多個列表項,就需要為列表中的每一項指定一個 key。

當(dāng)重新渲染 VDOM 中受影響部分時,Dojo 使用虛擬節(jié)點的 key 來唯一標(biāo)識特定實例。如果沒有使用 key 在 VDOM 中區(qū)分開同一層級中的相同類型的多個節(jié)點,則 Dojo 就無法準(zhǔn)確地確定哪些子節(jié)點受到了失效更改(invalidating change)的影響。

注意: 虛擬節(jié)點的 key 應(yīng)在多次渲染函數(shù)的調(diào)用中保持一致。在每一次的渲染調(diào)用中,為相同的輸出節(jié)點生成不同的 key,在 Dojo 應(yīng)用程序開發(fā)中被認(rèn)為是反模式的,應(yīng)當(dāng)避免。

配置 VNode

VNodeProperties 包含很多字段,是與 DOM 中的元素交互的重要 API。其中很多屬性鏡像了 HTMLElement 中的可用屬性,包括指定各種 oneventname 的事件處理器。

應(yīng)用程序的這些屬性是單向的,因為 Dojo 將給定的屬性集應(yīng)用到具體的 DOM 元素上,但不會將相應(yīng)的 DOM 屬性后續(xù)的任何更改同步到 VNodeProperties。任何此類更改都應(yīng)該通過事件處理器回傳給 Dojo 應(yīng)用程序。當(dāng)調(diào)用事件處理程序時,應(yīng)用程序可以處理事件所需的任何狀態(tài)更改,在輸出 VDOM 結(jié)構(gòu)進(jìn)行渲染時,更新對應(yīng)的 VNodeProperties 視圖,然后 Dojo 的 Renderer 會同步所有相關(guān)的 DOM 更新。

修改屬性和差異檢測

Dojo 使用虛擬節(jié)點的屬性來確定給定節(jié)點是否已更新,從而是否需要重新渲染。具體來說,它使用差異檢測策略來比較前一次和當(dāng)前渲染幀的屬性集。如果在節(jié)點接收的最新屬性集中檢測到差異,則該節(jié)點將失效,并在下一個繪制周期中重新渲染。

注意: 屬性更改檢測是由框架內(nèi)部管理的,依賴于在部件的渲染函數(shù)中聲明的 VDOM 輸出結(jié)構(gòu)。試圖保留屬性的引用,并在正常的部件渲染周期之外對其進(jìn)行修改,在 Dojo 應(yīng)用程序開發(fā)中被視為反模式的,應(yīng)當(dāng)避免。

支持交互

事件監(jiān)聽器

在實例化節(jié)點時,為虛擬節(jié)點指定事件監(jiān)聽器的方法與指定任何其他屬性的方法相同。當(dāng)輸出 VNode 時,VNodeProperties 上事件監(jiān)聽器的名字會鏡像到 HTMLElement 的等價事件上。雖然自定義部件的作者可以根據(jù)自己的選擇命名事件,但通常也遵循類似的 onEventName 的命名約定。

函數(shù)屬性(如事件處理程序)會自動綁定到實例化此虛擬節(jié)點的部件的 this 上下文。但是,如果將已綁定的函數(shù)傳給屬性值,將不會重復(fù)綁定給 this。

處理 focus

輸出 VNode 時,部件可以使用 VNodePropertiesfocus 屬性來控制生成的 DOM 元素在渲染時是否獲取焦點。這是一個特殊屬性,它可接收一個 boolean 類型的對象或者是返回一個 boolean 類型的函數(shù)。

當(dāng)直接傳入 true 時,只有上一次的值不是 true 時,元素才會獲取焦點(類似于常規(guī)屬性變更檢測)。而傳入函數(shù)時,只要函數(shù)返回 true,元素就會獲取焦點,而不管上一次返回值。

例如:

根據(jù)元素的順序,下面的 “firstFocus” 輸入框只會在初始化渲染時獲取焦點,而 “subsequentFocus” 輸入框在每次渲染時都會獲取焦點,因為 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 (
        <div>
            <input key="subsequentFocus" type="text" focus={() => true} />
            <input key="firstFocus" type="text" focus={true} />
            <button onclick={() => invalidator()}>Re-render</button>
        </div>
    );
});

基于類的部件:

import WidgetBase from '@dojo/framework/core/WidgetBase';
import { tsx } from '@dojo/framework/core/vdom';

export default class FocusExample extends WidgetBase {
    protected render() {
        return (
            <div>
                <input key="subsequentFocus" type="text" focus={() => true} />
                <input key="firstFocus" type="text" focus={true} />
                <button onclick={() => this.invalidate()}>Re-render</button>
            </div>
        );
    }
}

委托 focus

基于函數(shù)的部件可使用 focus 中間件為其子部件設(shè)置焦點,或者接受來自父部件的焦點。基于類的部件可使用 FocusMixin(來自 @dojo/framework/core/mixins/Focus)以相同的方式委托 focus。

FocusMixin 會給部件的類中添加一個 this.shouldFocus() 方法,而基于函數(shù)的部件使用 focus.shouldFocus() 中間件方法實現(xiàn)相同的目的。此方法會檢查部件是否處于執(zhí)行了獲取焦點的狀態(tài)(譯注:即調(diào)用了 this.focus()),并且僅對單個調(diào)用返回 true,直到再次調(diào)用部件的 this.focus() 方法(基于函數(shù)的部件使用等價的 focus.focus())。

FocusMixin 或者 focus 中間件也會為部件的 API 添加一個 focus 函數(shù)屬性??蚣苁褂么藢傩缘牟紶柦Y(jié)果來確定渲染時,部件(或其一個子部件)是否應(yīng)獲得焦點。通常,部件通過其 focus 屬性將 shouldFocus 方法傳遞給特定的子部件或輸出的節(jié)點上,從而允許父部件將焦點委托給其子部件。

基于函數(shù)的部件的示例,請參閱 Dojo 中間件參考指南中的 focus 中間件委派示例

下面基于類的部件示例,顯示了在部件層次結(jié)構(gòu)內(nèi)和輸出的 VNode 之間委托和控制焦點:

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)<FocusInputChildProperties> {
    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 <input onfocus={this.properties.onFocus} focus={this.shouldFocus} />;
    }
}

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 (
            <div>
                <button onclick={this.focusPreviousChild}>Previous</button>
                <button onclick={this.focusNextChild}>Next</button>
                <FocusInputChild
                    key={0}
                    focus={this.currentlyFocusedKey === 0 ? this.shouldFocus : undefined}
                    onFocus={() => this.onFocus(0)}
                />
                <FocusInputChild
                    key={1}
                    focus={this.currentlyFocusedKey === 1 ? this.shouldFocus : undefined}
                    onFocus={() => this.onFocus(1)}
                />
                <FocusInputChild
                    key={2}
                    focus={this.currentlyFocusedKey === 2 ? this.shouldFocus : undefined}
                    onFocus={() => this.onFocus(2)}
                />
                <FocusInputChild
                    key={3}
                    focus={this.currentlyFocusedKey === 3 ? this.shouldFocus : undefined}
                    onFocus={() => this.onFocus(3)}
                />
                <FocusInputChild
                    key={4}
                    focus={this.currentlyFocusedKey === 4 ? this.shouldFocus : undefined}
                    onFocus={() => this.onFocus(4)}
                />
            </div>
        );
    }
}

狀態(tài)管理

在數(shù)據(jù)不需要在多個組件之間流動的簡單應(yīng)用程序中,狀態(tài)管理是非常簡單的??蓪⒉考枰臄?shù)據(jù)封裝在部件內(nèi),這是 Dojo 應(yīng)用程序中狀態(tài)管理的最基本形式。

隨著應(yīng)用程序變得越來越復(fù)雜,并且開始要求在多個部件之間共享和傳輸數(shù)據(jù),就需要一種更健壯的狀態(tài)管理形式。在這里,Dojo 開始展現(xiàn)出其響應(yīng)式框架的價值,允許應(yīng)用程序定義數(shù)據(jù)如何在組件之間流動,然后由框架管理變更檢測和重新渲染。這是通過在部件的渲染函數(shù)中聲明 VDOM 輸出時將部件和屬性連接在一起而做到的。

對于大型應(yīng)用程序,狀態(tài)管理可能是最具挑戰(zhàn)性的工作之一,需要開發(fā)人員在數(shù)據(jù)一致性、可用性和容錯性之間進(jìn)行平衡。雖然這種復(fù)雜性大多超出了 web 應(yīng)用程序?qū)拥姆秶?,?Dojo 提供了更進(jìn)一步的解決方案,以確保數(shù)據(jù)的一致性。Dojo Store 組件提供了一個集中式的狀態(tài)存儲,它提供一致的 API,用于訪問和管理應(yīng)用程序中多個位置的數(shù)據(jù)。

基礎(chǔ):自封裝的部件狀態(tài)

部件可以通過多種方式維護(hù)其內(nèi)部狀態(tài)。基于函數(shù)的部件可以使用 cacheicache 中間件來存儲部件的本地狀態(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 (
        <div>
            Current widget state: {cache.get<string>('myState') || 'Hello from a stateful widget!'}
            <br />
            <button
                onclick={() => {
                    let counter = cache.get<number>('counter') || 0;
                    let myState = 'State change iteration #' + ++counter;
                    cache.set('myState', myState);
                    cache.set('counter', counter);
                }}
            >
                Change State
            </button>
        </div>
    );
});

基于類的部件:

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 (
            <div>
                Current widget state: {this.myState}
                <br />
                <button
                    onclick={() => {
                        this.myState = 'State change iteration #' + ++this.counter;
                    }}
                >
                    Change State
                </button>
            </div>
        );
    }
}

注意,這個示例是不完整的,在正在運行的應(yīng)用程序中,單擊“Change State”按鈕不會對部件的渲染輸出產(chǎn)生任何影響。這是因為狀態(tài)完全封裝在 MyEncapsulatedStateWidget 部件中,而 Dojo 無從得知對部件的任何更改??蚣苤惶幚砹瞬考某跏间秩?。

要通知 Dojo 重新渲染,則需要封裝渲染狀態(tài)的部件自行失效。

讓部件失效

基于函數(shù)的部件可以使用 icache 中間件處理本地的狀態(tài)管理,當(dāng)狀態(tài)更新時會自動失效部件。icache 組合了 cacheinvalidator 中間件,擁有 cache 的處理部件狀態(tài)管理的功能,和 invalidator 的當(dāng)狀態(tài)變化時讓部件失效的功能。如果需要,基于函數(shù)的部件也可以直接使用 invalidator。

基于類的部件,則有兩種失效的方法:

  1. 在狀態(tài)被更改后的適當(dāng)位置顯式調(diào)用 this.invalidate()

    • MyEncapsulatedStateWidget 示例中,可在“Change State”按鈕的 onclick 處理函數(shù)中完成。

  2. 使用 @watch() 裝飾器(來自 @dojo/framework/core/vdomercorators/watch 模塊)注釋任何相關(guān)字段。當(dāng)修改了 @watch 注釋的字段后,將隱式調(diào)用 this.invalidate(),這對于狀態(tài)字段很有用,這些字段在更新時總是需要重新渲染。

注意: 將一個部件標(biāo)記為無效,并不會立刻重新渲染該部件,而是通知 Dojo,部件已處于 dirty 狀態(tài),應(yīng)在下一個渲染周期中進(jìn)行更新和重新渲染。這意味著在同一個渲染幀內(nèi)多次失效同一個部件并不會對應(yīng)用程序的性能產(chǎn)生負(fù)面影響,但應(yīng)避免過多重復(fù)的失效以確保最佳性能。

以下是修改過的 MyEncapsulatedStateWidget 示例,當(dāng)狀態(tài)變化時會正確地更新輸出。

基于函數(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 (
        <div>
            Current widget state: {icache.getOrSet<string>('myState', 'Hello from a stateful widget!')}
            <br />
            <button
                onclick={() => {
                    let counter = icache.get<number>('counter') || 0;
                    let myState = 'State change iteration #' + ++counter;
                    icache.set('myState', myState);
                    icache.set('counter', counter);
                }}
            >
                Change State
            </button>
        </div>
    );
});

基于類的部件:

此處,myStatecounter 都在應(yīng)用程序邏輯操作的同一個地方進(jìn)行了更新,因此可將 @watch() 添加到任一字段上或者同時添加到兩個字段上,這些配置的實際結(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 (
            <div>
                Current widget state: {this.myState}
                <br />
                <button
                    onclick={() => {
                        this.myState = 'State change iteration #' + ++this.counter;
                    }}
                >
                    Change State
                </button>
            </div>
        );
    }
}

中級:傳入部件屬性

通過虛擬節(jié)點的 properties 將狀態(tài)傳入部件是 Dojo 應(yīng)用程序中連接響應(yīng)式數(shù)據(jù)流最有效的方法。

部件指定自己的屬性接口,該接口包含部件希望向使用者公開的任何字段,包括配置選項、表示注入狀態(tài)的字段以及任何事件處理函數(shù)。

基于函數(shù)的部件是將其屬性接口以泛型參數(shù)的形式傳給 create().properties&lt;MyPropertiesInterface&gt;() 的。然后,本調(diào)用鏈返回的工廠函數(shù)通過渲染函數(shù)定義中的 properties 函數(shù)參數(shù),讓屬性值可用。

基于類的部件可將其屬性接口定義為類定義中 WidgetBase 的泛型參數(shù),然后通過 this.properties 對象訪問其屬性。

例如,一個支持狀態(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<string>('new-name') || '';
    return (
        <div>
            <span>Hello, {name}! Not you? Set your name:</span>
            <input
                type="text"
                value={newName}
                oninput={(e: Event) => {
                    icache.set('new-name', (e.target as HTMLInputElement).value);
                }}
            />
            <button
                onclick={() => {
                    icache.set('new-name', undefined);
                    onNameChange && onNameChange(newName);
                }}
            >
                Set new name
            </button>
        </div>
    );
});

基于類的部件:

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<MyWidgetProperties> {
    private newName = '';
    protected render() {
        const { name, onNameChange } = this.properties;
        return (
            <div>
                <span>Hello, {name}! Not you? Set your name:</span>
                <input
                    type="text"
                    value={this.newName}
                    oninput={(e: Event) => {
                        this.newName = (e.target as HTMLInputElement).value;
                        this.invalidate();
                    }}
                />
                <button
                    onclick={() => {
                        this.newName = '';
                        onNameChange && onNameChange(newName);
                    }}
                >
                    Set new name
                </button>
            </div>
        );
    }
}

此示例部件的使用者可以通過傳入適當(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<string>('current-name') || 'Alice';
    return (
        <MyWidget
            name={currentName}
            onNameChange={(newName) => {
                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 (
            <MyWidget
                name={this.currentName}
                onNameChange={(newName) => {
                    this.currentName = newName;
                }}
            />
        );
    }
}

高級:提取和注入狀態(tài)

實現(xiàn)復(fù)雜功能時,在部件內(nèi)遵循狀態(tài)封裝模式可能會導(dǎo)致組件膨脹、難以管理。在大型應(yīng)用程序中也可能出現(xiàn)另一個問題,數(shù)百個部件跨數(shù)十個層級組合在一起。通常是葉部件使用狀態(tài)數(shù)據(jù),并不是 VDOM 層次結(jié)構(gòu)中的中間容器。讓數(shù)據(jù)狀態(tài)穿透這樣一個層次結(jié)構(gòu)復(fù)雜的部件需要增加脆弱、不必要的代碼。

Dojo 提供的 Store 組件 解決了這些問題,它將狀態(tài)管理提取到專用上下文中,然后將應(yīng)用程序中的相關(guān)狀態(tài)注入到特定的部件中。

最佳開發(fā)實踐

使用 Dojo 部件時,應(yīng)謹(jǐn)記一些重要原則,以避免在應(yīng)用程序代碼中引入反模式。試圖以不受支持的方式使用框架可能會導(dǎo)致意外的行為,并在應(yīng)用程序中引入難以發(fā)現(xiàn)的錯誤。

部件屬性

  • 部件應(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)。

    • 實現(xiàn)“純”部件是一個有效且通常是可取的模式,它不會產(chǎn)生副作用,并用屬性接收它們的所有狀態(tài),但這不是開發(fā) Dojo 部件的唯一模式。

使用基于類的部件

  • __render__, __setProperties__, and __setChildren__ 函數(shù)屬于框架內(nèi)部實現(xiàn)細(xì)節(jié),絕不允許在應(yīng)用程序中調(diào)用或覆寫。

  • 應(yīng)用程序不應(yīng)直接實例化部件——Dojo 完全接管部件實例的生命周期,包括實例化、緩存和銷毀。

虛擬 DOM

  • 虛擬節(jié)點的 key 應(yīng)在多次渲染調(diào)用中保持一致。

    • 如果在每次渲染調(diào)用中都指定一個不同的 key,則 Dojo 無法有效地將前一次渲染和本次渲染中的相同節(jié)點關(guān)聯(lián)上。Dojo 會將上一次渲染中沒有看到的新 key 當(dāng)作新元素,這會導(dǎo)致從 DOM 中刪除之前的節(jié)點并重新添加一套,即使屬性沒有發(fā)生變化,不需要重新更新 DOM。

    • 一個常見的反模式是在部件的渲染函數(shù)中為節(jié)點的 key 分配一個隨機(jī)生成的 ID(如 GUID 或 UUID)。除非生成策略是等冪的,否則不應(yīng)在渲染函數(shù)中生成節(jié)點的 key 值。

  • 應(yīng)用程序不應(yīng)存儲虛擬節(jié)點的引用,以便從部件的渲染函數(shù)返回它們后,進(jìn)行后續(xù)操作;也不應(yīng)嘗試通過使用單個實例跨多個渲染調(diào)用來優(yōu)化內(nèi)存分配。

    • 虛擬節(jié)點被設(shè)計成輕量級的,并且在每次部件渲染周期內(nèi)實例化新版本的開銷非常低。

    • 框架依賴于在兩次部件渲染函數(shù)調(diào)用中有兩個單獨的虛擬節(jié)點實例來執(zhí)行準(zhǔn)確的更改檢測。如果未檢測到任何變化,則不會產(chǎn)生進(jìn)一步的開銷、渲染等。

渲染到 DOM 中

  • 應(yīng)用程序不應(yīng)使用命令式的 DOM 操作調(diào)用。

    • 框架負(fù)責(zé)處理所有具體的渲染職責(zé),并且為部件作者提供了替代機(jī)制,以更簡單、類型安全和響應(yīng)式的方式使用各種 DOM 功能。

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Dojo部件怎么用”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI