这个教程里,我们将使用自定义控件功能实现一个具有如下功能的定时器:

  1. 开启定时任务。
  2. 能够清除当前设置的定时任务。
  3. 能够动态的增加可执行任务次数上限。

控件定义

基本信息

首先我们创建一个变量叫 TMyTimer。变量名遵循 JS 的命名规范即可,但按照传统,我们习惯以 T 作为开头(T 代表 Type)。

  1. const TMyTimer = {};

接下来我们需要定义控件的基本信息。type 代表控件的类型,按照传统一般为全大写并以 _WIDGET 结尾)。icon 代表控件的图标。这里使用官方提供的一个定时器图标 'icon-widget-timer'。如果需要使用自定义的图标,需要额外上传图片资源(暂不支持)。title 代表控件的名称,会在拖拽图标的下方以及创建控件实例时作为控件的默认名称显示。

  1. const TMyTimer = {
  2. type: 'TIMER_WIDGET',
  3. icon: 'icon-widget-timer',
  4. title: '定时器',
  5. };

控件属性

接下来我们需要定义控件的属性。首先添加字段 properties。我们通过一个对象定义属性,具体每个字段的含义请参考 自定义控件开发指南。这里我们定义了一个只读的布尔值属性叫 running,代表当前的定时器是否正在执行定时任务。我们还定义了一个可以读写的属性叫 limit,代表任务的执行次数上限,默认为5次。

  1. const TMyTimer = {
  2. type: 'TIMER_WIDGET',
  3. icon: 'icon-widget-timer',
  4. title: '定时器',
  5. properties: [
  6. {
  7. key: 'running',
  8. label: '是否启用',
  9. valueType: 'boolean',
  10. defaultValue: false,
  11. readonly: true,
  12. },
  13. {
  14. key: 'limit',
  15. label: '任务执行次数上限',
  16. valueType: 'number',
  17. defaultValue: 5,
  18. },
  19. ], properties: [
  20. {
  21. key: 'running',
  22. label: '是否启用',
  23. valueType: 'boolean',
  24. defaultValue: false,
  25. readonly: true,
  26. },
  27. ],
  28. };

控件方法

接下来我们需要定义控件的方法。首先添加字段 methods。和属性类似,我们通过一个对象定义方法的基本属性,具体每个字段的含义请参考 自定义控件开发指南。这里我们定义了两个方法 setInterval
clearInterval,一个用于开启定时器,一个用于清除当前的定时任务。

  1. const TMyTimer = {
  2. type: 'TIMER_WIDGET',
  3. icon: 'icon-widget-timer',
  4. title: '定时器',
  5. properties: [
  6. {
  7. key: 'running',
  8. label: '是否启用',
  9. valueType: 'boolean',
  10. defaultValue: false,
  11. readonly: true,
  12. },
  13. {
  14. key: 'limit',
  15. label: '任务执行次数上限',
  16. valueType: 'number',
  17. defaultValue: 5,
  18. },
  19. ],
  20. methods: [
  21. {
  22. key: 'setInterval',
  23. label: '设置定时任务',
  24. params: [
  25. {
  26. key: 'time',
  27. type: 'number',
  28. }
  29. ],
  30. },
  31. {
  32. key: 'clearInterval',
  33. label: '清除定时任务',
  34. params: [],
  35. },
  36. ],
  37. };

控件事件

接下来我们需要定义控件的事件。首先添加字段 events。和属性、方法类似,我们通过一个对象定义事件的基本属性,具体每个字段的含义请参考 自定义控件开发指南。这里我们定义了一个事件 OnTimeIsUp。通过
setInterval 开启定时器后,每隔一定的时间就会触发该事件,我们可以在该事件积木下拼接其他积木来实现回调函数的效果。我们还定义了事件 onLimitChange,用于监听属性 limit 的变化。当通过积木改变
limit 的值时,我们可以通过该事件的返回参数拿到最新的 limit 数值。

  1. const TMyTimer = {
  2. type: 'TIMER_WIDGET',
  3. icon: 'icon-widget-timer',
  4. title: '定时器',
  5. properties: [
  6. {
  7. key: 'running',
  8. label: '是否启用',
  9. valueType: 'boolean',
  10. defaultValue: false,
  11. readonly: true,
  12. },
  13. {
  14. key: 'limit',
  15. label: '任务执行次数上限',
  16. valueType: 'number',
  17. defaultValue: 5,
  18. },
  19. ],
  20. methods: [
  21. {
  22. key: 'setInterval',
  23. label: '设置定时任务',
  24. params: [
  25. {
  26. key: 'time',
  27. type: 'number',
  28. }
  29. ],
  30. },
  31. {
  32. key: 'clearInterval',
  33. label: '清除定时任务',
  34. params: [],
  35. },
  36. ],
  37. events: [
  38. {
  39. key: 'onTimeIsUp',
  40. label: '时间到了',
  41. params: [],
  42. },
  43. {
  44. key: 'onLimitChange',
  45. label: '上限改变',
  46. params: [
  47. {
  48. key: 'limit',
  49. label: '任务执行次数上限',
  50. type: ['number'],
  51. },
  52. ],
  53. },
  54. ],
  55. };

有了这部分的定义,我们就能够生成编辑态下的控件定义,包括控件选择界面,属性面板和积木外观。
1234.png

截屏2021-09-30 上午9.45.19.png

接下来我们需要定义定时器在运行时的行为表现,也就是调用了上面的积木后,控件会做出的响应。这里我们需要实现一个叫 MyTimerJS 类。

运行态定义

自定义的控件类首先需要继承 InvisibleWidget。在定义构造函数 constructor 的时候,我们可以通过
props 拿到控件在编辑态被设置的属性,我们需要调用 super(props) 初始化 InvisibleWidget 里自带的一些属性。对于那些没有暴露给外部设置的属性,我们需要在初始化时给一个默认值。

  1. class MyTimer extends InvisibleWidget {
  2. // 编辑态可以修改的属性通过 props 传给运行态的控件实例
  3. constructor(props) {
  4. super(props);
  5. this.timerId = undefined;
  6. this.running = false;
  7. this._limit = props.limit;
  8. }
  9. }

这里的 limit 属性值可以通过积木进行读取和设置,在某些情况下,我们希望能够监听到 limit 属性值的变化。这时,我们可以结合 JS 语法中的 settergetter 以及事件发送来实现上述的效果。

对于需要设置 gettersetter 的属性,我们有特殊的命名规范:在原名字前加上下划线 _limitgetter 很简单,只是存粹的返回 _limit 的属性值。在 setter 中,我们首先判断了新的 limit 数值是否与当前相等且是否大于0,如果两个条件都满足,则更新 limit 的值并且发送 onLimitChange 事件。

  1. class MyTimer extends InvisibleWidget {
  2. constructor(props) {
  3. super(props);
  4. this.timerId = undefined;
  5. this.running = false;
  6. this._limit = props.limit;
  7. }
  8. get limit() {
  9. return this._limit;
  10. }
  11. set limit(newLimit) {
  12. if (this._limit !== newLimit && newLimit > 0) {
  13. this._limit = newLimit;
  14. this.emit('onLimitChange', this._limit);
  15. }
  16. }
  17. }

接下来我们需要定义两个方法 setIntervalclearInterval 的代码实现。这两个方法会在执行相应积木的时候被调用。setInterval 方法调用了 JS 原生的 setInterval 方法,根据用户传入每隔一段时间发送 onTimeIsUp 事件。重复调用 setInterval 会清空上一次设置的定时任务,并开启一个新的定时任务。当任务执行次数达到上限时,会自动清除当前设置的定时任务。clearInterval 则是清除当前设置的定时任务。

  1. class MyTimer extends InvisibleWidget {
  2. constructor(props) {
  3. super(props);
  4. this.timerId = undefined;
  5. this.running = false;
  6. this._limit = props.limit;
  7. }
  8. get limit() {
  9. return this._limit;
  10. }
  11. set limit(newLimit) {
  12. if (this._limit !== newLimit && newLimit > 0) {
  13. this._limit = newLimit;
  14. this.emit('onLimitChange', this._limit);
  15. }
  16. }
  17. setInterval(time) {
  18. if (this.timerId) {
  19. clearInterval(this.timerId);
  20. }
  21. this.running = true;
  22. let count = 0;
  23. this.timerId = setInterval(() => {
  24. if (count < this._limit) {
  25. this.emit('onTimeIsUp');
  26. count++;
  27. } else {
  28. this.clearInterval();
  29. }
  30. }, time * 1000);
  31. }
  32. clearInterval() {
  33. if (this.timerId) {
  34. clearInterval(this.timerId);
  35. this.timerId = undefined;
  36. this.running = false;
  37. }
  38. }
  39. }

最后我们需要将这些定义通过两个变量导出,types 对应控件的基本定义,widget 对应用于生成运行态实例的控件类。

  1. ...
  2. exports.types = TMyTimer;
  3. exports.widget = MyTimer;

将写好的内容保存到 timer.js 中,通过 Web 编辑器导入,我们就可以正常的使用自定义的定时器控件啦!

截屏2021-09-30 上午10.36.10.png

参考资料

  1. 定时器控件 JS 源码:timer.js