Provider 构件
Provider 构件是一种特殊类型的构件,它不提供任何界面展示的能力,仅提供数据处理的能力。配合 useResolves 可以快速实现页面依赖数据的绑定,配合 Events 事件可以实现动态的数据处理。
在 Brick Next 中,将数据与 UI 分离非常重要,一方面可以提升业务构件的可复用性,同时也可以提升产品的可配置能力、可编排能力。
创建 Provider 构件
主要有两种快速创建 Provider 构件的方法。
使用脚手架工具创建
创建 Provider 构件最简单的方式是使用脚手架工具 yarn yo ,选择 a new package of providers,然后选择一个 API SDK。工具将自动生成一个该 SDK 相关的 Provider 构件库。
初始生成的构件库不包含任何构件,需要在生成的项目文件 providers.json 的 providers 列表中添加对应的接口方法名,例如 "CmdbObjectApi.list"。
使用 createProviderClass 创建
脚手架工具仅提供 API SDK 的 Provider 构件封装,如果想要对 SDK 返回的数据做加工,或者使用其它的个性化数据来源,这时可以使用 createProviderClass 快速创建一个 Provider 构件。例如:
import { createProviderClass } from "@next-core/brick-utils";
import { CmdbObjectApi } from "@sdk/cmdb-sdk";
function filteredList(params) {
return CmdbObjectApi.list(params).filter((model) => !model.private);
}
customElements.define(
"micro-app-store.provider-app-detail",
createProviderClass(filteredList)
);
createProviderClass 支持传入任意 Function,它的实现细节请参考本文后面的章节。
使用方法
Provider 构件一般用于 useResolves 和 Events 事件。
定时更新
现在更推荐使用 Provider 轮询 来实现定时更新的能力。
点击展开旧方式的定时更新的示例
在 Provider 构件的属性里声明 interval ,可以实现每隔一段时间就重新拉取数据并通知到构件,例如:
brick: "providers-of-pipeline.project-api-list"
bg: true
properties:
interval:
delay: 3000
ignoreErrors: true
delay 指定每次更新的间隔时间,单位毫秒。默认情况下如果数据更新发生错误,系统将弹出错误警告。设置 ignoreErrors: true 可以不弹出警告。无论何种设置,发生错误时始终会停止接下来的更新。
interface IntervalSettings {
delay: number;
ignoreErrors?: boolean;
}
当需要停止定时更新时,调用 Provider 构件的 $stopInterval() 即可,例如:
brick: "my-button"
events:
click:
target: "my-provider"
method: "$stopInterval"
从 brick_next 2.18.14 开始支持 $stopInterval。
主动更新
现在更推荐使用 Events 事件和 Context 上下文来实现主动更新数据的能力。
点击展开旧方式的主动更新的示例
Provider 构件在运行时会对外暴露方法 $refresh(),可以在 TS 中调用或在 Storyboard 中绑定到指定事件,例如:
events:
click:
target: 'providers-of-pipeline\.project-api-list'
method: "$refresh"
$refresh() 执行时如果发生错误,系统将弹出错误警告,但不会抛出错误。可以传递选项参数,设置 throwErrors: true 时可以将错误以 Rejected Promise 形式抛出。设置 ignoreErrors: true 时则完全忽略错误,既不弹出错误警告,也不抛出错误 。
interface RuntimeProviderBrick {
// 使用 `$` 作前缀表明这是运行时追加的属性/方法。
$refresh: (options?: {
ignoreErrors?: boolean;
throwErrors?: boolean;
}) => Promise<void>;
内置属性、方法和事件
现在更推荐使用 Provider 异步回调和 Context 上下文来实现数据分步更新及异步处理的能力,无需使用 Provider 构件的内置属性、方法和事件。
点击展开关于内置属性、方法和事件的文档
内置属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
args | any[] | [] | 关联参数,在 execute 执行 Provider 的关联函数时将传递这个参数 |
内置方法
Provider 构件内置有几个方法(method),列举如下:
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
setArgs | object | void | 设置(更新) Provider 的 args 属性的指定部分。可以多次调用以更新参数的不同部分 |
setArgsAndExecute | object | Promise | 设置(更新) Provider 的参数的指定部分,接着执行 execute 方法 |
updateArgs | CustomEvent | void | 接收一个事件对象,等于 setArgs(event.detail) |
updateArgsAndExecute | CustomEvent | Promise | 接收一个事件对象,等于 setArgsAndExecute(event.detail) |
execute | - | Promise | 调用 Provider 绑定的函数,参数为当前 Provider 的 args 属性(可通过 setArgs/updateArgs 更新),将发送 response.* 事件 |
saveAs | string, any[] | Promise | 调用 Provider 绑定的函数,参数为要保存的文件名及当前 Provider 的 args 属性。该方法仅对返回类型为 Blob 的 Provider 有效 |
executeWithArgs | any[] | Promise | 调用 Provider 绑定的函数,参数为事件配置里指定的参数 args,将发送 response.* 事件 |
resolve | any[] | Promise | 调用 Provider 绑定的函数,参数为事件配置里指定的参数 args |
内置事件
Provider 构件在执行 execute 后(包括 setArgsAndExecute 和 updateArgsAndExecute),将根据执行成功与否分别发送 response.success 和 response.error 事件。
| 事件类型 | event.detail | 说明 |
|---|---|---|
response.success | 接口返回内容 | 请求成功 |
response.error | 接口错误对象 | 请求失败 |
Provider 异步回调
bricks:
- brick: "your-brick"
events:
something.happen:
useProvider: "your-provider"
args:
- "${EVENT.detail.id}"
callback:
success:
target: "another-brick"
properties:
instanceData: "${EVENT.detail}"
error:
action: "console.error"
finally:
target: "your-button"
properties:
disabled: false
在以上配置中,如果构件 your-brick 发生了事件 something.happen:
- 系统将使用 Provider 构件
your-provider,调用它的resolve,传递参数${EVENT.detail.id}; - 当该异步函数完成后:
- 如果成功,找到构件
another-brick,设置它的属性instanceData为上述函数返回的结果; - 如果失败,使用
console.error打印上述函数返回的失败原因。 - 无论成功失败,最后都将
your-button构件的disabled属性设置为false
如果给事件处理器配置了 callback,那么系统在事件触发后,除了执行指定 method 外,还将根据该函数执行结果进行回调调用:method 将被视作异步函数调用,当该函数成功时(resolved),将触发 callback.success 回调,反之(rejected)将触发 callback.error,而无论成功或失败,最后都将触发 callback.finally。这些回调的配置本身又是一个事件配置,因此它可以执行内置动作、或设置其他构件的属性、甚至继续调用其他构件的方法。这些回调传递的 event.detail 在成功时为函数执行的 resolved result,失败时为函数执行的 rejected reason。
基于异步回调驱动的模式相比基于事件驱动的好处在于在同样实现了异步操作的前提下,回调使得操作与响应可以一一对应。事件无法将操作与响应一一对应,对于事件接收方,在收到事件时,很难判断它是自己触发的还是其它对象触发的。
因此事件通常用于旁路处理的场景,例如打印日志、广播通知等。而回调则用于需要将操作和响应对应起来的场景,例如实例的增、删、改、查。
Provider 轮询
使用 useProvider 作事件处理时,可以激活轮询模式,系统将间隔指定时间轮询对应的接口。
useProvider: "my-provider"
args:
- "some args"
# 通常应为轮询接口指定一个系统额外的参数,以避免在每次轮询时显示加载条
- interceptorParams:
ignoreLoadingBar: true
poll:
enabled: true
# 通常应指定一个校验函数 `expectPollEnd`,该函数接收一个参数:轮询执行结果,
# 并返回轮询是否应该结束。
expectPollEnd: '<% (result) => result.status === "done" %>'
callback:
progress:
# 每次轮询得到结果时会触发 `callback.progress`
success:
# 轮询完成时会触发 `callback.success`,
# 即 `expectPollEnd(result)` 返回 true 时
error:
# 轮询失败时会触发 `callback.error`
finally:
# 轮询完成或失败时触发 `callback.finally`
除了使用 progress 来获得接口的实时结果外,也可以让轮询接口对外表现得像普通接口一样。例如假设有一个查询工具执行结果的接口需要轮询以得到最终执行结果,可以这样配置:
useProvider: "my-provider.get-tool-result"
args:
- "some args"
- interceptorParams:
ignoreLoadingBar: true
poll:
enabled: true
expectPollEnd: |
<%
(result) =>
result.status === "success" || result.status === "failed"
%>
delegateLoadingBar: true
callback:
success:
target: "tool-result"
properties:
- output: "<% EVENT.detail.output %>"
error:
action: handleHttpError
| Poll 选项 | 默认值 | 说明 | |||
|---|---|---|---|---|---|
| enabled | false | 是否启用轮询 | |||
| interval | 3000 | 轮询间隔时间(毫秒) | |||
| expectPollEnd | - | 轮询是否应结束的校验函数,该函数接收一个参数:当前轮询的执行结果,轮询结束时将触发 callback.success 事件 | |||
| expectPollStopImmediately | - | 轮询是否应立即结束的校验函数,还在等待或进行中的轮询将失效,不会触发 `progress | success | error | finally` 等事件 |
| delegateLoadingBar | false | 是否代理系统加载条的显示与隐藏,当启用时,在轮询初始时,会显示加载条,轮询完成或失败时,将隐藏加载条。应配合 expectPollEnd 与 interceptorParams.ignoreLoadingBar 使用。 | |||
| continueOnError | false | 当某次轮询失败时,是否仍继续发起轮询。 |
基于事件驱动的示例
现在更推荐使用 Context 上下文来实现如下的分步表单数据更新。
点击展开基于事件驱动的示例
bricks:
- brick: "your-provider"
bg: true
properties:
args:
- null
- name: "unknown"
events:
response.success:
# ...
response.error:
# ...
- brick: "your-brick-a"
events:
something.happen:
target: "your-provider"
method: "setArgs"
args:
- "0": "${EVENT.detail.id}"
- brick: "your-brick-b"
events:
something.else.happen:
target: "your-provider"
method: "setArgs"
args:
- "1.name": "${EVENT.detail.name}"
- brick: "your-brick-c"
events:
submit:
target: "your-provider"
method: "execute"
- 当构件
your-brick-a发生something.happen事件时,系统将找到your-provider构件,并更新它关联参数的第一项,更改为该事件 detail 中的id对应的值; - 而当构件
your-brick-b发生something.else.happen事件时,系统将找到your-provider构件,并更新它关联参数的第二项的name字段,更改为该事件 detail 中的name对应的值 - 接着,当构件
your-brick-c发生submit事件时,系统将调用your-provider的execute方法,使用前面更新好的关联参数来执行它的关联函数; - 最后,关联函数执行后,将根据执行成功与否分别触发
response.success和response.error事件。
注意 setArgs 接收的参数为一个键值对:patch,系统将遍历这些键值对,分别执行 _.set(provider.args, key, value),因此我们可以实现在不同的事件下更新同一个 Provider 的关联参数的不同部分(例如分步表单里分别更新最终发送的数据)。
相关源码:
import { set } from "lodash";
interface ProviderElement<P extends any[], R> extends HTMLElement {
args: P;
updateArgs: (event: CustomEvent<Record<string, any>>) => void;
updateArgsAndExecute: (event: CustomEvent<Record<string, any>>) => R;
setArgs: (patch: Record<string, any>) => void;
setArgsAndExecute: (patch: Record<string, any>) => R;
execute(): R;
saveAs(filename: string, ...args: P);
executeWithArgs(...args: P): R;
resolve(...args: P): R;
}
变更历史
| 组件 | 版本 | 变更 |
|---|---|---|
| brick_next | 2.30.3 | 支持 expectPollStopImmediately |
| - | 2.30.2 | 支持 Provider 轮询 |
| - | 2.18.14 | 支持 $stopInterval |
| - | 1.19.8 | Provider 异步回调支持 callback.finally |
| - | 1.15.20 | 支持 Provider 异步回调:callback |
| - | 1.15.5 | 支持:setArgs, setArgsAndExecute |
| - | 1.13.9 | 支持:executeWithArgs |