dojo dragon main logo

可用的中间件

Dojo 提供了多种可选的中间件,当部件需要实现特定需求时,可以包含这些中间件。

icache

该中间件在 invalidator 中间件功能的基础上提供了缓存功能,支持值的延迟解析,并在值可用时自动让部件失效。默认情况下,往缓存中设置一个值时,缓存就会失效;但是使用 set API 上的第三个可选参数,在需要时可让缓存不失效。

API:

import icache from '@dojo/framework/core/middleware/icache';
  • icache.getOrSet<T = any>(key: any, value: any, invalidate: boolean = true): T | undefined
    • 如果存在的话,则返回根据 key 获取的值,否则就将 key 值设置为 value。在这两种情况下,如果缓存值尚未解析,则返回 undefined
  • icache.get<T = any>(key: any): T | undefined
    • 根据 key 获取缓存值,如果未设置值或者该值处在挂起状态,则返回 undefined
  • icache.set(key: any, value: any, invalidate: boolean = true): any
    • 将提供的 value 设置给指定的 key。如果 value 是一个函数,则将调用它以获取要缓存的实际值。如果函数返回的是 promise,则会先缓存一个“pending”值,直到解析出最终的值。在所有场景中,一旦一个值可用并存储到缓存中,该部件将被标记为无效,这样就可以使用最终的值重新渲染。
  • icache.has(key: any): boolean
    • 根据 key 是否已在缓存中设置,来返回 truefalse
  • icache.delete(key: any, invalidate: boolean = true): void
    • 从缓存中移除对应的项。
  • clear(invalidate: boolean = true)
    • 清除当前在部件本地缓存中存储的所有值。

当将函数传给 icache.set 时,可在函数中访问当前的缓存值,下面的示例演示如何递增当前值。

icache.set('key', (current) => {
    if (current) {
        return current + 1;
    }
    return 1;
});

可以使用两种方式为 icache 设置类型。一种方式是使用泛型来在调用的地方指定返回类型,对于 getOrSet,可以根据值类型推断出返回的类型,如果 getOrSetvalue 是一个函数,则使用函数返回的类型推断出值类型。

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

const factory = create({ icache });

interface FetchResult {
    foo: string;
}

const MyIcacheWidget = factory(function MyIcacheWidget({ middleware: { icache } }) {
    // `results` will infer the type of the resolved promise, `FetchResult | undefined`
    const results = icache.getOrSet('key', async () => {
        const response = await fetch('url');
        const body: FetchResult = await response.json();
        return body;
    });

    return <div>{results}</div>;
});

但是,这种方式没有为缓存的 key 提供任何类型信息。为 icache 设置类型的首选方式是使用 createICacheMiddleware 创建一个预先设置了类型的中间件。这样就允许传入一个接口来创建一个明确指定了类型的 icache 中间件,并为缓存的 key 提供了类型安全。

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

interface FetchResult {
    foo: string;
}

interface MyIcacheWidgetState {
    key: FetchResult;
}

const icache = createICacheMiddleware<MyIcacheWidgetState>();
const factory = create({ icache });

const MyIcacheWidget = factory(function MyIcacheWidget({ middleware: { icache } }) {
    // `results` will be typed to `FetchResult | undefined` based on the `MyIcacheWidgetState`
    const results = icache.getOrSet('key', async () => {
        const response = await fetch('url');
        const body: FetchResult = await response.json();
        return body;
    });

    return <div>{results}</div>;
});

theme

允许部件渲染时为 CSS 样式类设置主题,并且允许为应用程序设置主题以及确定当前设置的主题,如果有设置的话。

Dojo 的样式和主题参考指南中有详细说明。

API:

import theme from '@dojo/framework/core/middleware/theme';
  • theme.classes<T extends ClassNames>(css: T): T
    • 为部件传入一个或多个 CSS 类名,然后接收根据当前设置的主题而修改后的名字,以便在返回部件的虚拟节点时使用。
  • 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.01.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: 0
  • MD: 576
  • LG: 768
  • XL: 960

API:

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;

inert

支持通过 key 为节点设置 inert 属性。这将确保相关节点不会响应聚焦、鼠标事件等操作。对于一些场景,如隶属于 document.body 的对话框,inert 会被倒转设置到 key 节点的所有兄弟节点上。

API:

import inert from '@dojo/framework/core/middleware/inert';
  • inert.set(key: string | number, enable: boolean, invert: boolean = false): void;
    • 为节点设置 inert 值。如果传入 invert,则会将值设置给节点的所有兄弟节点。

src/widgets/Dialog.tsx

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

import * as css from './App.m.css';

const dialogFactory = create({ inert, icache }).properties<{
    open: boolean;
    onRequestClose: () => void;
}>();

const Dialog = dialogFactory(function Dialog({ children, properties, middleware: { inert } }) {
    const { open } = properties();

    inert.set('dialog', open, true);

    if (!open) {
        return null;
    }

    return (
        <body>
            <div
                key="dialog"
                styles={{
                    background: 'red',
                    width: '400px',
                    height: '400px',
                    marginLeft: '-200px',
                    marginTop: '-200px',
                    position: 'absolute',
                    left: '50%',
                    top: '50%'
                }}
            >
                <button
                    onclick={() => {
                        properties().onRequestClose();
                    }}
                >
                    Close
                </button>
                {children()}
            </div>
        </body>
    );
});

const factory = create({ icache });

export default factory(function App({ middleware: { icache } }) {
    return (
        <div classes={[css.root]}>
            <input />
            <button
                onclick={() => {
                    icache.set('open', true);
                }}
            >
                Open
            </button>
            <Dialog
                open={icache.getOrSet('open', false)}
                onRequestClose={() => {
                    icache.set('open', false);
                }}
            >
                <div>
                    <input />
                    <input />
                    <button>button</button>
                    Content
                </div>
            </Dialog>
        </div>
    );
});

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 路径。
  • store.at<U = any>(path: Path<S, U[]>, index: number)
    • 当访问存储的数组值时,返回数字索引指向的值的 store 路径
  • store.executor<T extends Process<any, any>>(process: T): ReturnType<T>
    • 在组合部件的 store 中执行给定的 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()
    • 能够被调用,以指示部件或者一个子部件在下一次渲染周期时获取焦点。这个函数通常传给输出的 VDOM 节点的 onfocus 事件处理函数,允许部件响应用户驱动的焦点变更事件。
  • focus.isFocused(key: string | number): boolean
    • 如果部件中,指定的 key 标识的 VDOM 节点当前获取焦点,则返回 true。如果相关的 VDOM 节点没有焦点或者部件中不存在此 VDOM 节点,则返回 false

Focus 委托示例

下面展示一个例子,在部件层次结构内和输出的 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>
    );
});

validity

专用于获取节点的 有效性状态(validity state) 信息,当使用浏览器内置的方法来校验表单输入和提供本地的错误信息时,这将非常有用。

API:

import validity from '@dojo/framework/core/middleware/validity';
  • validity.get(key: string, value: string) - 返回由节点的 key 属性确定的 DOM 元素的有效性状态。如果当前部件中不存在指定的 DOM 元素,则返回 { valid: undefined, message: '' };否则返回一个 ValidityState 对象。

ValidityState 对象包含以下属性:

属性 类型 描述
valid boolean 节点的 validity.valid 属性值,说明节点的值是否满足所有验证约束。
validationMessage string 节点的 validationMessage 属性值,节点值违反约束时的本地化消息。

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)
    • 执行指定的 block 模块,并返回执行结果