中间件基本原理
Dojo 提供了渲染中间件的概念,以帮助衔接响应式、函数部件与底层的命令式 DOM 结构。
如果部件能够访问 DOM 信息,某些 web 应用程序需求就更容易实现。常见的例子有:
- 响应式 UI 不与特定的设备类型绑定,而是根据可用的页面区域改变元素的大小。
- 仅当某些元素在用户可视区域可见时,才延迟加载需要的数据——例如无限滚动列表。
- 引导元素获取焦点,并在用户变更焦点后进行响应。
但是,中间件并非必须与 DOM 绑定;这个概念还适合部件的渲染生命周期等更常用的情况。此类需求的常见示例如下:
- 如果获取数据的开销大,则在渲染间缓存数据
- 根据特定条件暂停和恢复部件的渲染;在所需信息不可用时,避免无用的渲染
- 将函数部件标记为无效,以便 Dojo 可以重新渲染部件
一个中间件组件一般公开的某些功能与部件渲染的 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();