xflow-command赏析

380 阅读7分钟

image.png

命令的定义

在xflow中,命令是对图形界面中一组操作,包括但不限于对x6的API的调用。

如何使用

这是xflow官网给的基本用法,但是通过梳理源码还发现命令的其它用法。

  1. 传参

    调用命令时第一个参数是命令ID,第二个就是参数,命令支持传入哪些参数需要看源码中ts定义,比如packages\xflow-core\src\command-contributions\node\node-add.ts中定义的NsAddNode -> IArg,或者通过导出的NsNodeCmd也可以看到参数的ts类型。

  2. 运行时hook

    调用命令时第三个参数运行时hook和普通hook一样,运行时hook最后会加入到普通hook的集合中一起执行。hook会有一篇文章专门讲。

  3. 批量执行命令

    就是可以执行一组命令,每个命令都可以单独传参和运行时钩子函数,这个示例用法在examples\quick-start-cra-js\src\xflow\dag\config-cmd.js。

  4. 自定义命令

    这个其实在dag示例中使用了,在源码的examples\quick-start-cra-js\src\xflow\dag\cmd-extensions\cmd-deploy.js位置定义了一个自定义命令,然后配置到xflow上就可以了。

    命令让我们的操作能撤销和反撤销,上面的示例中有undo和redo方法就是这个作用。命令还能:定义命令对应的hook;调用其它命令。

  5. 给命令的执行添加hook(钩子函数)

    在xflow中,一个命令其实是一个hookHub(钩子函数集合),命令的主逻辑执行完就会执行自己的钩子函数,如何给命令添加钩子函数其实也有示例,在源码的examples\quick-start-cra-js\src\xflow\dag\config-cmd.js

command对比x6原生API多了哪些东西

  1. hook,xflow的command还允许开发者对command的hook扩展,hook就是执行x6的api前可以执行一些钩子函数。
  2. xflow的命令还可以批量执行。
  3. xflow的命令的执行还能通知到xflow的model层。
  4. 具体到某个命令上对x6的api前的数据配置进行优化和定制处理。

下面的实现过程会体现这些特点。

实现方案

image.png

实现过程-添加节点命令

过程的讲解比较细会涉及源码,但是源码只是辅助理解,核心逻辑都会解读出来。

开始执行

  1. 在【如何使用】xflow的官网给的示例中可以看到命令执行入口在Application的实例上,或者某个事件的参数中也能获取,实际上事件的参数给也是Application实例的成员。Application是暴露出给开发者用的对象,它负责暴露一些内部API,比如commandService实际上是command模块的实例。

图1: image.png

图1中FrontendApplication就是我们通过useXFlowApp获取的app的类 

图2: image.png

图2中其实就是我们配置toolbarItem时定义的onClick函数的执行逻辑,可以看到参数中的
commandService就是来自于FrontendApplication的实例app

2. 执行executeCommand实际上执行的command模块成员方法,这个成员方法会根据第一个参数命令ID,找到命令对应的工厂实例。工厂的主要目的就是返回命令ID对应的命令实例。

图3: image.png

图3中getFactory实际是在command的实例的成员方法factories中找到commandId对应的工厂,
找到工厂就能让工厂创建对应的命令,工厂其实就是是设计模式中工厂方法,看这里
https://juejin.cn/post/7373482464721731621,
由它统一创建命令实例。下面看下工厂如何创建命令实例以及它还干了啥。

命令工厂

命令工厂顾名思义就是创建命令,它的存在简化了命令的创建过程,只需告诉它命令ID它就会返回命令对象,比如我们自定义命令,我们只用关注命令的执行逻辑即可,其它的上下文由工厂来搞定。

图4: image.png

useFactory和child.register、child.getNamed都是mana-syringe方法,看这里
https://juejin.cn/post/7353442647766810635

注册

  1. 命令扩展 注册就是命令扩展把命令工厂对象存储到命令注册机对象上,关于命令扩展和命令注册机会有一篇专门的文章讲解。

    在xflow中内置了一些命令比如"addNode"还允许开发者自定义一些命令,这些命令的ID都会有对应工厂对象,看【命令工厂】会发现最后是通过child.getNamed获取的实际的命令处理函数,而命令则通过@injectable把自己注入到mana-syringe容器,这样才能通过child.getNamed获取。

图5: image.png

5就是命令扩展对象上的registerGraphCommands成员方法,这个方法中hookHubList就是内置命令,而
this.commandConfig.getConfig()就是开发者自定义的命令,{createCommand: this.commandFactory}
其实就是工厂了。hookHubList的命名是个Hub,Hub其实是集合的意思,就是每个命令都是个HookHub实例,
每个hookHub都包含若干hook。

2. 自定义命令

开发者通过XFlow的属性commandConfig可以配置自定义命令,配置的方法在【如何使用】->【自定义命令】有说。配置的时候我们需要通过xflow暴露的一个配置命令方法,这个方法接受一个回调函数,回调函数接受一个参数config,config其实就是上面说的this.commandConfig.getConfig() 获取的对象。

图6: image.png

6就是开发者在业务代码中引入的createCmdConfig方法,这个方法用了js中函数柯里化的形式,
createCmdConfig接受的参数是一个函数,这个函数就是开发者写逻辑的地方,而这个函数会传入
一个config参数,这个参数是CommandConfig的实例,而这个实例也会被命令扩展在注册命令时获取。

3. 命令注册机 命令注册机保存了所有的内置命令扩展(其实就一个),然后在应用初始化时把命令扩展中的hookHub以及自定义命令的命令Id和命令工厂保存到自己的成员属性上。

图7: image.png

在XFlow和vscode的源码中都涉及了Dispoable的概念,意思是回收闲置的资源,但是具体的逻辑还没搞懂。另外
还看到doRegisterCommand这个方法把命令也注册到注册机了,但是实际执行命令不通过它,它的目的目前不明。

4. 把命令注册到容器 在【命令工厂】也就是图4中看到工厂方法通过命令ID在mana-syringe容器中获取了命令,能获取就得先注册,得通过mana-syringe的API把命令对象注册到容器中。

图8: image.png

token方法有两个属性token和named,目的是这样的,如果只通过属性token那么能获取所有值是
ICommandHandler的对象,这应该获取了所有命令,而加上named,哪就只能获取名称是NsAddNode.
command.id的命令。

继续执行

通过上一步我们已经得到了命令对象,而在【开始执行】也就是图2中看到获取命令对象后掉了它的execute方法。这里面会把命令作为hookHub。

  1. 以hookHub方式执行命令

hookHub的call方法会执行hub中的hooks,第一个参数是开发者执行命令时传过来的参数,这个参数会传给命令所有hook以及命令主逻辑,第二个参数就是命令主逻辑。下面会讲解什么时候命令变成hookHub的。

图9:image.png

args和hooks并不是直接从参数获取,这个没搞明白,通过ctx获取的好处也没想明白。

2. 把命令注册为一个hookHub 因为命令执行可能有些钩子函数,所以这里把每个命令都注册为一个hookHub。hookHub会保存到hook注册机上,这个有专门的文章讲。

图10: image.png

这个函数是命令扩展对象的成员方法,hookHubList和this.commandConfig就是内置和自定义命令。

hook

hook的逻辑放到专门的文章。

总结

xflow实现的命令很灵活,命令可以自定义、命令对应的hook也可以定义。 通过工厂简化了命令的创建过程。 命令注册机和命令扩展之间是xflow很多模块都采取的模式,利用了mana-syringe的对象管理模式。注册机会启动扩展的注册方法,扩展的注册方法会把自己的数据注册到注册机上,很绕,但是这样是否注册、注册什么的主动权都在扩展上,每个扩展都可以灵活的变化这部分。