Dojo 中间件进阶

发布时间:2020-08-06 13:51:11 作者:blocklang
来源:网络 阅读:249

翻译自: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';

icache

组合了 cacheinvalidator 中间件功能,以提供一个缓存,支持延迟值的解析,并在值可用时自动让部件失效。

API:

import icache from '@dojo/framework/core/middleware/icache';

theme

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

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

API:

import theme from '@dojo/framework/core/middleware/theme';

i18n

允许在渲染部件时,将消息文本本地化,也允许应用程序进行区域设置,以及获取当前设置的区域,如果有设置的话。

在 Dojo 的国际化参考指南中有详细说明。

API:

import i18n from '@dojo/framework/core/middleware/i18n';

dimensions

提供部件底层节点的各种大小和位置信息。

API:

import dimensions from '@dojo/framework/core/middleware/dimensions';

返回的 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';

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';

breakpoint

允许部件确定一个指定的宽度断点,该断点与其中一个虚拟节点的当前宽度匹配。此中间件在创建能够适配各种显示宽度的部件时非常有用,比如在移动端和桌面分辨率下同时使用的部件。

resize 中间件组合使用,以获取元素的宽度,并在调整宽度时自动让部件失效。

注意: 如果没有设置自定义的宽度断点,Dojo 将默认使用以下集合:

API:

import breakpoint from '@dojo/framework/core/middleware/breakpoint';
interface Breakpoints {
    [index: string]: number;
}

当要在很多位置使用同一个断点集时,该集合只需定义一次,而不必在每一次调用 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';

focus

组合使用 VDOM focus 原生方法 ,允许部件检查和控制输出的 DOM 间的焦点。

API:

import focus from '@dojo/framework/core/middleware/focus';

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>
    );
});

injector

允许从 Dojo 注册表中获取注入器(injector),然后将其分配给失效的回调函数。

注意: 注入器和注册表是高阶概念,在编写 Dojo 应用程序时通常用不到。它们主要由框架使用,以实现更高级的面向用户的功能,如 Dojo store。

API:

import injector from '@dojo/framework/core/middleware/injector';

block

在构建时,允许部件在 Node.js 中执行称为 blocks 的模块。通常用于构建时渲染。

在构建(build)参考指南中有详细说明。

API:

import block from '@dojo/framework/core/middleware/block';

核心渲染中间件

@dojo/framework/core/vdom 模块中包含基础中间件,大多数 Dojo 应用程序都会用到。这些主要用于构建其他自定义中间件(框架提供的附加中间件就是由他们构成的),但在一般的部件开发中也偶尔会用到。

invalidator

这是最重要的中间件,在部件的失效生命周期中设置了一个钩子。调用了 invaludator() 后,会将要渲染的部件排列到下一次的渲染计划中。

API:

import invalidator from '@dojo/framework/core/vdom';

node

支持通过节点的 key,访问部件底层的 DOM 节点。当被请求的 DOM 节点是有效的,但还不可用时,Dojo 就立刻重新渲染部件,直到 DOM 节点变为可用。

API:

import node from '@dojo/framework/core/vdom';

diffProperty

通过为指定的属性注册自己的 diff 函数,以允许部件对差异检测进行细粒度控制。当尝试重新渲染部件时,框架将调用该函数,以确定是否发生了变化,从而需要进行完全的重新渲染。如果在部件的属性集中没有检测到差异,将跳过更新,并且现有的所有 DOM 节点都保持原样。

编写自定义的 diff 函数时,通常需要与 invalidator 中间件组合使用,以便需要更新部件的 DOM 节点时,将当前部件标记为无效。

注意: 在组合部件或中间件的生命周期中,只能为指定的属性注册一个 diff 函数,后续的调用将被忽略。渲染引擎有一套默认算法,该算法对对象和数组进行 shallow 对比,忽略函数,而对其他所有属性进行相等检查。为属性设置了自定义的 diff 函数后,将会覆盖 Dojo 默认的差异检测策略。

API:

import diffProperty from '@dojo/framework/core/vdom';

destroy

指定一个在部件销毁时调用的函数,可以销毁占用的任何资源。

注意: 每一个组合的部件或中间件只能调用一次 destroy(),之后再调用会被忽略。对于需要在移除部件时有条件地添加执行句柄的高级方案,应该注册一个可以跟踪并迭代地销毁所有必要资源的销毁函数。

API:

import destroy from '@dojo/framework/core/vdom';

getRegistry

通过处理器接口(handler interface),支持访问部件自身的 Registry 实例,如果需要的话,也可以访问应用程序一级的 Registry

注意: Registry 是一个高阶概念,在编写 Dojo 应用程序时通常用不到。它主要在框架内部使用,以实现更高阶的面向用户的功能,如 Dojo store。

API:

import getRegistry from '@dojo/framework/core/vdom';

defer

允许部件暂定和恢复渲染逻辑;在特定条件满足之前短路部件的渲染时也很有用。

API:

import defer from '@dojo/framework/core/vdom';
推荐阅读:
  1. Django中间件
  2. 什么是中间件

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

dojo middleware hook

上一篇:什么是常对数组进行的两种基本操作

下一篇:python中捕获错误和异常的方法

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》