您好,登錄后才能下訂單哦!
翻譯自:https://github.com/dojo/framework/blob/master/docs/en/middleware/supplemental.md
Dojo 提供了渲染中間件的概念,以幫助銜接響應式、函數部件與底層的命令式 DOM 結構。
如果部件能夠訪問 DOM 信息,某些 web 應用程序需求就更容易實現。常見的例子有:
但是,中間件并非必須與 DOM 綁定;這個概念還適合部件的渲染生命周期等更常用的情況。此類需求的常見示例如下:
一個中間件組件一般公開的某些功能與部件渲染的 DOM 元素有關;大多是部件的根節點。中間件系統為部件在瀏覽器中的展示和交互提供了更高級的控制,并且允許部件以一致的方式使用幾個新興的 Web 標準。
如果部件在其底層的 DOM 元素存在之前訪問中間件的某些屬性,則返回合理的默認值。還有一些中間件可以暫停部件的渲染,直到滿足某些條件。使用這些中間件,部件能避免不必要的渲染,直到所需的信息可用為止,然后 Dojo 將在數據可用時獲取中間件的正確屬性值,自動重新渲染受影響的部件。
中間件是使用 @dojo/framework/core/vdom
中的 create()
工廠方法定義的。這與創建函數部件的過程類似,但是中間件工廠返回的并不是 VDOM 節點,而是允許訪問中間件功能集的 API。簡單的中間件只需要一個函數調用來實現它們的需求,也可以直接返回一個函數,而不需要將中間件包裝在一個對象中。
下面介紹一個中間件組件,它有一個簡單的 get()
和 set()
API:
src/middleware/myMiddleware.ts
import { create } from '@dojo/framework/core/vdom';
const factory = create();
export const myMiddleware = factory(() => {
return {
get() {},
set() {}
};
});
export default myMiddleware;
中間件主要用在函數部件中,但也可以通過組合形成其他中間件,以實現更復雜的需求。這兩種情況下,任何用到的中間件都會作為屬性傳給 create()
方法,然后通過部件或中間件工廠實現函數中的 middleware
參數使用這些中間件。
例如,在部件中使用上面的 myMiddleware
中間件:
src/widgets/MiddlewareConsumerWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import myMiddleware from '../middleware/myMiddleware';
const render = create({ myMiddleware });
export const MiddlewareConsumerWidget = render(({ middleware: { myMiddleware } }) => {
myMiddleware.set();
return <div>{`Middleware value: ${myMiddleware.get()}`}</div>;
});
export default MiddlewareConsumerWidget;
以下示例演示了用中間件組合出新的中間件,以實現更有用的需求:
src/middleware/ValueCachingMiddleware.ts
import { create, defer, invalidator } from '@dojo/framework/core/vdom';
import { cache } from '@dojo/framework/core/middleware/cache';
const factory = create({ defer, cache });
export const ValueCachingMiddleware = factory(({ middleware: { defer, cache, invalidator }}) => {
get(key: string) {
const cachedValue = cache.get(key);
if (cachedValue) {
return cachedValue;
}
// Cache miss: fetch the value somehow through a promise
const promise = fetchExternalValue(value);
// Pause further widget rendering
defer.pause();
promise.then((result) => {
// Cache the value for subsequent renderings
cache.set(key, result);
// Resume widget rendering once the value is available
defer.resume();
// Invalidate the widget for a re-render
invalidator();
});
return null;
}
});
export default ValueCachingMiddleware;
由于中間件是通過 create()
工具函數定義的,因此為中間件指定屬性接口的方式,與為函數部件指定屬性接口的方式相同。主要的區別是中間件屬性會被添加到所有消費者部件的屬性接口中。這意味著屬性值是在實例化部件時設置的,而不是在部件使用中間件時。在整個組合層次結構中,屬性被看作是只讀的,因此中間件不能修改屬性值。
下面是具有屬性接口的中間件示例:
src/middleware/middlewareWithProperties.tsx
import { create } from '@dojo/framework/core/vdom';
const factory = create().properties<{ conditional?: boolean }>();
export const middlewareWithProperties = factory(({ properties }) => {
return {
getConditionalState() {
return properties().conditional ? 'Conditional is true' : 'Conditional is false';
}
};
});
export default middlewareWithProperties;
在部件中使用中間件及其屬性:
src/widgets/MiddlewarePropertiesWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import middlewareWithProperties from '../middleware/middlewareWithProperties';
const render = create({ middlewareWithProperties });
export const MiddlewarePropertiesWidget = render(({ properties, middleware: { middlewareWithProperties } }) => {
return (
<virtual>
<div>{`Middleware property value: ${properties().conditional}`}</div>
<div>{`Middleware property usage: ${middlewareWithProperties.getConditionalState()}`}</div>
</virtual>
);
});
export default MiddlewarePropertiesWidget;
然后,當創建 MiddlewarePropertiesWidget
實例時,指定中間件的 conditional
屬性值,例如:
src/main.tsx
import renderer, { tsx } from '@dojo/framework/core/vdom';
import MiddlewarePropertiesWidget from './widgets/MiddlewarePropertiesWidget';
const r = renderer(() => <MiddlewarePropertiesWidget conditional={true} />);
r.mount();
Dojo 提供了多種可選的中間件,當部件需要實現特定需求時,可以包含這些中間件。
cache
提供了一個簡單的、部件內的緩存,可以在部件的多次渲染間保留少量數據。
API:
import cache from '@dojo/framework/core/middleware/cache';
cache.get<T = any>(key: any): T | null
key
獲取當前緩存值,如果緩存未命中則返回 null
。cache.set<T = any>(key: any, value: T)
value
存儲在緩存中,并與指定的 key
關聯。cache.clear()
icache
組合了 cache
和 invalidator
中間件功能,以提供一個緩存,支持延遲值的解析,并在值可用時自動讓部件失效。
API:
import icache from '@dojo/framework/core/middleware/icache';
icache.getOrSet<T = any>(key: any, value: any): T | undefined
key
獲取的值,否則就將 key
值設置為 value
。在這兩種情況下,如果緩存值尚未解析,則返回 undefined
。icache.get<T = any>(key: any): T | undefined
key
獲取緩存值,如果未設置值或者該值處在掛起狀態,則返回 undefined
。icache.set(key: any, value: any)
value
設置給指定的 key
。如果 value
是一個函數,則將調用它以獲取要緩存的實際值。如果函數返回的是 promise,則會先緩存一個“pending”值,直到解析出最終的值。在所有場景中,一旦一個值可用并存儲到緩存中,該部件將被標記為無效,這樣就可以使用最終的值重新渲染。clear()
theme
允許部件渲染時為 CSS 樣式類設置主題,并且允許為應用程序設置主題以及確定當前設置的主題,如果有設置的話。
在 Dojo 的樣式和主題參考指南中有詳細說明。
API:
import theme from '@dojo/framework/core/middleware/theme';
theme.classes<T extends ClassNames>(css: T): T
theme.set(css: Theme)
theme.get(): Theme | undefined
undefined
。通常在應用程序的根部件中使用。i18n
允許在渲染部件時,將消息文本本地化,也允許應用程序進行區域設置,以及獲取當前設置的區域,如果有設置的話。
在 Dojo 的國際化參考指南中有詳細說明。
API:
import i18n from '@dojo/framework/core/middleware/i18n';
i18n.localize<T extends Messages>(bundle: Bundle<T>, useDefaults = false): LocalizedMessages<T>
bundle
中返回根據當前設置的區域而本地化的一組消息。useDefaults
用于控制當前區域對應的值不可用時,是否返回來自默認語言的消息。默認值為 false
,在這種情況下返回的是空值,而不是默認語言的消息。i18n.set(localeData?: LocaleData)
i18n.get()
undefined
。通常在應用程序的根部件中使用。dimensions
提供部件底層節點的各種大小和位置信息。
API:
import dimensions from '@dojo/framework/core/middleware/dimensions';
dimensions.get(key: string | number): Readonly<DimensionResults>
key
屬性標識的 DOM 元素的尺寸信息。如果當前部件中不存在此節點(尚未渲染或指定的 key 無效),則返回的值都是 0
。返回的 DimensionResults
包含以下屬性,這些屬性映射到指定 DOM 元素的相關屬性:
Property | Source |
---|---|
client.left |
node.clientLeft |
client.top |
node.clientTop |
client.width |
node.clientWidth |
client.height |
node.clientHeight |
position.bottom |
node.getBoundingClientRect().bottom |
position.left |
node.getBoundingClientRect().left |
position.right |
node.getBoundingClientRect().right |
position.top |
node.getBoundingClientRect().top |
size.width |
node.getBoundingClientRect().width |
size.height |
node.getBoundingClientRect().height |
scroll.left |
node.scrollLeft |
scroll.top |
node.scrollTop |
scroll.height |
node.scrollHeight |
scroll.width |
node.scrollWidth |
offset.left |
node.offsetLeft |
offset.top |
node.offsetTop |
offset.width |
node.offsetWidth |
offset.height |
node.offsetHeight |
intersection
使用 Intersection Observer API 提供關于節點在特定可視區域是否可見等信息。
因為 Intersection Observer API 是一個新興的 Web 標準,因此在不支持此 API 的瀏覽器中運行應用程序時,框架會自動確保底層的 API 可用。注意,Dojo 6 版本不支持 Intersection Observer API v2
API:
import intersection from '@dojo/framework/core/middleware/intersection';
intersection.get(key: string | number, options: IntersectionGetOptions = {}): IntersectionResult
key
屬性標識的 DOM 元素的交叉(intersection)信息。如果當前部件中不存在此節點(尚未渲染或指定的 key 無效),會返回一個結果,表示無交叉。option
參數允許對如何計算交叉做更多控制。可用字段與 intersection observer API options 相同。
IntersectionResult
屬性:
屬性 | 類型 | 說明 |
---|---|---|
intersectionRatio |
number |
與根元素的可視區域相交的元素邊界框的比率,從 0.0 到 1.0 ,默認的根元素是瀏覽器的可視區域,除非通過 options.root 元素指定了一個元素。 |
isIntersecting |
boolean |
值為 true 時表示目標元素與根元素的可視區域交叉(表示過渡到了交叉狀態)。值為 false 時表示從交叉過渡到了不交叉。 |
resize
允許部件使用 ResizeObserver
響應 DOM 節點的 resize 事件,并且在調整大小時提供節點新大小的更新信息。使用這個中間件是創建適配各種視窗大小的響應式應用程序的有效方法。
因為 Resize Observer 是一個新興的 Web 標準,因此在不支持此 API 的瀏覽器中運行應用程序時,框架會自動確保底層的 API 可用。
API:
import resize from '@dojo/framework/core/middleware/resize';
resize.get(key: string | number): DOMRectReadOnly | null
key
屬性標識的 DOM 元素的尺寸信息。如果當前部件中不存在此節點(尚未渲染或指定的 key 無效),則返回 null
。返回的對象是一個標準的 DOMRectReadOnly
結構。breakpoint
允許部件確定一個指定的寬度斷點,該斷點與其中一個虛擬節點的當前寬度匹配。此中間件在創建能夠適配各種顯示寬度的部件時非常有用,比如在移動端和桌面分辨率下同時使用的部件。
與 resize
中間件組合使用,以獲取元素的寬度,并在調整寬度時自動讓部件失效。
注意: 如果沒有設置自定義的寬度斷點,Dojo 將默認使用以下集合:
SM
: 0MD
: 576LG
: 768XL
: 960API:
import breakpoint from '@dojo/framework/core/middleware/breakpoint';
interface Breakpoints {
[index: string]: number;
}
breakpoint.get(key: string | number, breakpoints: Breakpoints = defaultBreakpoints)
key
標識)匹配的斷點。可以通過 breakpoints
參數設置自定義的斷點。返回的值是一個包含 breakpoint
屬性的對象,它標識出了匹配的斷點名稱,以及一個 contentRect
屬性,它包含的值與 resize.get(key)
返回的值相同。當要在很多位置使用同一個斷點集時,該集合只需定義一次,而不必在每一次調用 breakpoint.get()
時傳入此集合。應用程序可以通過以下方式使用適當的默認值定義自己的自定義斷點中間件:
src/middleware/myCustomBreakpoint.ts
import { createBreakpointMiddleware } from '@dojo/framework/core/middleware/breakpoint';
const myCustomBreakpoint = createBreakpointMiddleware({ Narrow: 0, Wide: 500 });
export default myCustomBreakpoint;
store
當使用 Dojo store 組件時,部件能訪問外部的狀態。
在 Dojo Store 參考指南中有詳細說明。
API:
import store from '@dojo/framework/core/middleware/store';
store.get<U = any>(path: Path<S, U>): U
path
從 store 中獲取值。當關聯的值更改后,組合部件也會失效并重新渲染。store.path(path: any, ...segments: any): StatePaths<S>
store.at<U = any>(path: Path<S, U[]>, index: number)
store.executor<T extends Process<any, any>>(process: T): ReturnType<T>
process
并返回結果。focus
組合使用 VDOM focus 原生方法 ,允許部件檢查和控制輸出的 DOM 間的焦點。
API:
import focus from '@dojo/framework/core/middleware/focus';
focus.shouldFocus(): boolean
true
。將只返回一次 true
,后續調用將返回 false
,直到再次調用 focus.focus()
。這個函數通常作為 focus
屬性值傳給指定的 VDOM 節點,允許部件指出焦點應該應用到哪里。focus.focus()
onfocus
事件處理函數,允許部件響應用戶驅動的焦點變更事件。focus.isFocused(key: string | number): boolean
key
標識的 VDOM 節點當前獲取焦點,則返回 true
。如果相關的 VDOM 節點沒有焦點或者部件中不存在此 VDOM 節點,則返回 false
。下面展示一個例子,在部件層次結構內和輸出的 VNode 之間委托和控制焦點:
src/widgets/FocusableWidget.tsx
import { create, tsx } from '@dojo/framework/core/vdom';
import focus from '@dojo/framework/core/middleware/focus';
import icache from '@dojo/framework/core/middleware/icache';
/*
The input's `onfocus()` event handler is assigned to a method passed in
from a parent widget, via the child's create().properties<MyPropertiesInterface>
API, allowing user-driven focus changes to propagate back into the application.
*/
const childFactory = create({ focus }).properties<{ onfocus: () => void }>();
const FocusInputChild = childFactory(function FocusInputChild({ middleware: { focus }, properties }) {
const { onfocus } = properties();
return <input onfocus={onfocus} focus={focus.shouldFocus} />;
});
const factory = create({ focus, icache });
export default factory(function FocusableWidget({ middleware: { focus, icache } }) {
const keyWithFocus = icache.get('key-with-focus') || 0;
const childCount = 5;
function focusPreviousChild() {
let newKeyToFocus = (icache.get('key-with-focus') || 0) - 1;
if (newKeyToFocus < 0) {
newKeyToFocus = childCount - 1;
}
icache.set('key-with-focus', newKeyToFocus);
focus.focus();
}
function focusNextChild() {
let newKeyToFocus = (icache.get('key-with-focus') || 0) + 1;
if (newKeyToFocus >= childCount) {
newKeyToFocus = 0;
}
icache.set('key-with-focus', newKeyToFocus);
focus.focus();
}
function focusChild(key: number) {
icache.set('key-with-focus', key);
focus.focus();
}
return (
<div>
<button onclick={focusPreviousChild}>Previous</button>
<button onclick={focusNextChild}>Next</button>
<FocusInputChild
key="0"
onfocus={() => focusChild(0)}
focus={keyWithFocus == 0 ? focus.shouldFocus : undefined}
/>
<FocusInputChild
key="1"
onfocus={() => focusChild(1)}
focus={keyWithFocus == 1 ? focus.shouldFocus : undefined}
/>
<FocusInputChild
key="2"
onfocus={() => focusChild(2)}
focus={keyWithFocus == 2 ? focus.shouldFocus : undefined}
/>
<FocusInputChild
key="3"
onfocus={() => focusChild(3)}
focus={keyWithFocus == 3 ? focus.shouldFocus : undefined}
/>
<FocusInputChild
key="4"
onfocus={() => focusChild(4)}
focus={keyWithFocus == 4 ? focus.shouldFocus : undefined}
/>
</div>
);
});
injector
允許從 Dojo 注冊表中獲取注入器(injector),然后將其分配給失效的回調函數。
注意: 注入器和注冊表是高階概念,在編寫 Dojo 應用程序時通常用不到。它們主要由框架使用,以實現更高級的面向用戶的功能,如 Dojo store。
API:
import injector from '@dojo/framework/core/middleware/injector';
injector.subscribe(label: RegistryLabel, callback: Function = invalidator)
label
指定的注入器(如果存在的話)訂閱給定的 callback
失效函數。如果未指定 callback
,則默認使用 invalidator
中間件,以便當注入器使其數據可用時,將當前部件標記為失效并重新渲染。injector.get<T>(label: RegistryLabel): T | null
label
關聯的注冊器,如果注冊器不存在則返回 null
。block
在構建時,允許部件在 Node.js 中執行稱為 blocks 的模塊。通常用于構建時渲染。
在構建(build)參考指南中有詳細說明。
API:
import block from '@dojo/framework/core/middleware/block';
block<T extends (...args: any[]) => any>(module: T)
@dojo/framework/core/vdom
模塊中包含基礎中間件,大多數 Dojo 應用程序都會用到。這些主要用于構建其他自定義中間件(框架提供的附加中間件就是由他們構成的),但在一般的部件開發中也偶爾會用到。
invalidator
這是最重要的中間件,在部件的失效生命周期中設置了一個鉤子。調用了 invaludator()
后,會將要渲染的部件排列到下一次的渲染計劃中。
API:
import invalidator from '@dojo/framework/core/vdom';
invalidator()
node
支持通過節點的 key
,訪問部件底層的 DOM 節點。當被請求的 DOM 節點是有效的,但還不可用時,Dojo 就立刻重新渲染部件,直到 DOM 節點變為可用。
API:
import node from '@dojo/framework/core/vdom';
node.get(key: string | number): HTMLElement | null
key
屬性,返回部件中指定的 DOM 元素。如果當前部件中不存在指定的 DOM 元素,則返回 null
。diffProperty
通過為指定的屬性注冊自己的 diff 函數,以允許部件對差異檢測進行細粒度控制。當嘗試重新渲染部件時,框架將調用該函數,以確定是否發生了變化,從而需要進行完全的重新渲染。如果在部件的屬性集中沒有檢測到差異,將跳過更新,并且現有的所有 DOM 節點都保持原樣。
編寫自定義的 diff 函數時,通常需要與 invalidator
中間件組合使用,以便需要更新部件的 DOM 節點時,將當前部件標記為無效。
注意: 在組合部件或中間件的生命周期中,只能為指定的屬性注冊一個 diff 函數,后續的調用將被忽略。渲染引擎有一套默認算法,該算法對對象和數組進行 shallow 對比,忽略函數,而對其他所有屬性進行相等檢查。為屬性設置了自定義的 diff 函數后,將會覆蓋 Dojo 默認的差異檢測策略。
API:
import diffProperty from '@dojo/framework/core/vdom';
diffProperty(propertyName: string, diff: (current: any, next: any) => void)
diff
函數,該函數用于確定部件的 propertyName
屬性的 current
和 next
值之間是否存在差異。destroy
指定一個在部件銷毀時調用的函數,可以銷毀占用的任何資源。
注意: 每一個組合的部件或中間件只能調用一次 destroy()
,之后再調用會被忽略。對于需要在移除部件時有條件地添加執行句柄的高級方案,應該注冊一個可以跟蹤并迭代地銷毀所有必要資源的銷毀函數。
API:
import destroy from '@dojo/framework/core/vdom';
destroy(destroyFunction: () => void)
destroyFunction
。設置的函數將覆蓋之前為部件設置的任一銷毀函數。getRegistry
通過處理器接口(handler interface),支持訪問部件自身的 Registry
實例,如果需要的話,也可以訪問應用程序一級的 Registry
。
注意: Registry 是一個高階概念,在編寫 Dojo 應用程序時通常用不到。它主要在框架內部使用,以實現更高階的面向用戶的功能,如 Dojo store。
API:
import getRegistry from '@dojo/framework/core/vdom';
getRegistry(): RegistryHandler | null
RegistryHandler
,如果部件未完全初始化,則返回 null
。defer
允許部件暫定和恢復渲染邏輯;在特定條件滿足之前短路部件的渲染時也很有用。
API:
import defer from '@dojo/framework/core/vdom';
defer.pause()
defer.resume()
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。