优维低代码技术专栏,是一个全新的、技术为主的专栏,由优维技术委员会成员执笔,基于优维7年低代码技术研发及运维成果,主要介绍低代码相关的技术原理及架构逻辑,目的是给广大运维人提供一个技术交流与学习的平台。
连载第十五期
《高级指引: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"
⊙ NOTE
从 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 构件的内置属性、方法和事件。
关于内置属性、方法和事件的文档
# 内置属性
# 内置方法
Provider 构件内置有几个方法(method),列举如下:
#内置事件
Provider 构件在执行execute 后(包括 setArgsAndExecute 和 updateArgsAndExecute),将根据执行成功与否分别发送 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
# 基于事件驱动的示例
⊙ NOTE
现在更推荐使用 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;
}
有疑问加站长微信联系(非本文作者)