从可视化搭建说起
页面可视化搭建零碎从 16 年开始如雨后春笋般涌现而出,从流动页搭建到中后盾搭建,有开源有仅公司外部应用的,都致力于将前端从简约的体力劳动中解脱进去,进步页面生产效率。优酷外部也有一套营销流动搭建零碎,每年生产 2K+ 流动页;可能满足这么多页面的需要,除了积淀了大量可复用的组件外,围绕着搭建零碎的前端研发每天都在不停地保护降级老的组件,同时生产新的组件。
痛点
页面生产能力下来了,研发还是始终埋头在组件开发需要中。这些须要都是从哪里来的呢?其实下面也有提到,就是两点:
老的组件须要增加新的能力,可能是 UI 改变,可能是逻辑变更
新的业务组件开发
需要是永远也做不完的,为了进步开发效率,研发侧一直地积淀通用的根底库,与服务端约定标准化的接口,以此来缩小保护老本,但基于现有模式精益求精的优化远远不够。说白了,现有的可视化搭建效率和研发效率都曾经达到瓶颈了,咱们急需一种新的生产模式,给咱们带来生产效率的突破性晋升。
解法
咱们思考一下,页面可视化搭建是如何解放生产效率的。它将残缺的页面进行拆解,拆分为能够复用的组件,研发负责组件生产,产品经营负责组件配置,造成了一种简略的流水线作业的模式,这种模式的益处在于:
业务组件复用,防止反复开发,研发只须要专一于单个组件
产品经营能够染指页面生产过程,缩小沟通损耗的同时分担了局部先前研发承当的工作
当初的瓶颈已从页面开发效率转变到组件开发效率上,咱们做一个构想,让非研发角色也能染指到组件生产过程中,进一步提高生产效率。在此之前,咱们须要先基于现有模式进一步拆分组件构造:
组件能够拆解为 UI + 逻辑。UI 能够通过细粒度的文字、图片、slider 等组件搭建进去;逻辑次要波及到接口申请、数据处理、能力调用,咱们将一些罕用的 api 调用比方跳转、罕用的接口申请比方查问登录态等进行封装积淀,加上语义化的形容,非研发人员能够将他们拖拽绘制成流程图,实现业务逻辑的编排。两者联合,就能够生产出残缺的业务组件。而对于业务逻辑的组合编排,咱们称之为逻辑编排。
举个栗子
我当初有个需要,须要实现一个简略的抽奖流动。页面分为两块,第一块是奖池,五个奖品横铺开,第二块是抽奖按钮,点击按钮,如果用户没登录,拉起登录面板,如果用户已登录,则进行抽奖并高亮展现对应的奖品。
我当初把 UI 撘进去了,就差逻辑来让对应的坑位展现了,咱们把欠缺的逻辑局部拆成两段:
- 进入页面,也就是 componentDidMount 阶段,查问奖池
- 点击抽奖按钮时,查问是否登录,未登录拉起登录面板,已登录则调用抽奖接口
除了开始和完结以外的这种逻辑片段都是咱们曾经积淀下来的,能够间接拖拽进来,绘制流程图。绘制完了后,咱们抉择模块的 页面被动触发(设计给非研发用的,他们必定不了解 didMount),将它和 查问奖品 关联上;抉择抽奖按钮的 点击事件, 关联上 抽奖 逻辑,这样模块就能在不同的机会做正确的事件了。可能你还有点小纳闷,我查问了奖品后,数据是如何映射到 UI 展现上的呢?要想晓得如何映射,你得先晓得逻辑是怎么运行的。
下图是咱们在暑假战斗中的一个利用场景:
Logic is Core
扯一点远的,不违心看能够间接跳到“逻辑如何运行”大节。前端的逻辑编排相比服务编排要更简单一点,并不是说前端逻辑编排更难,而是因为:场景更加多样化,C 端模块搭建和中后盾搭建的编排差别就很大;人员更加多样化,除了服务研发人员还要服务非研发人员;除此以外,还须要和 UI 进行联合,可能性就更多了。
这段时间比拟火的 iMove,初始被设计服务优酷流动搭建平台的逻辑编排,前面服务 imgcook 后,与优酷的逻辑编排在状态上曾经截然不同,所以在前端比拟难像服务编排一样,平台化、中心化而后去服务多种多样的业务。如果你真的很想去服务多样化的场景及业务,提供一个轻量级的库,让它足够灵便、可定制,这也是 iMove 正在走的路。
后面讲了很多,都是在说,逻辑编排须要 follow 不同的平台做对应的定制,然而不论状态怎么变,它的外围是不会变的,咱们肯定是在围绕着“逻辑”去做包装,将之打造为不同的产品状态。所以,逻辑才是外围!
逻辑如何运行
说了半天逻辑多重要,倘若我当初抽离了一个函数进去,把它公布了,我又用它拖了个流程图,它怎么能力执行呢?
逻辑最初想要运行,无论你是出码(to code) 还是在线执行,你都须要一个“逻辑编排解释器”,这个解释器能够读懂编排后的逻辑并且去执行逻辑,这个解释器咱们称之为 Runtime。代码是寒冷的,解释器是没有智商的,它并不能真的读懂逻辑,只是因为咱们约定了一个规定,定好了编排后的逻辑可能有几种状况,每种状况应该如何做,Runtime 只是在依章办事,而这个规定就是 DSL。所以逻辑想要运行,它依赖于 DSL + Runtime,要做一个逻辑编排平台,也肯定是先定好 DSL,实现 runtime,前面才是去做平台。
每个积淀下来的逻辑,咱们将其定义为逻辑元件,加上语义化的元件名称及具体的形容,而后公布到元件市场。使用者只须要依据元件名称来筛选须要的逻辑,通过连接线将他们连接起来,就能够组合成一个流程图,这个流程图也就是一段残缺的逻辑。
可是流程图导出来的 graph json 都是点和线的汇合:
这种 json 如果间接用 runtime 执行有两个问题:
- 流程走向不直观,每个元件执行完后须要浪费时间去查找下一个元件是哪个
- 有效信息太多导致 json 体积太大进而影响加载性能,比方坐标信息(x/y)、标签信息(label) …
所以咱们须要先去约定 DSL,保障足够直观且只蕴含必要信息,所以咱们设计了一个转换器将 graph json 转换为 DSL。每个逻辑流程图对应一个 DSL 产物,借助 runtime 的 interpret 办法,它可能会运行在 useEffect 中,也有可能运行在某个元素的点击事件中,或者是页面的滚动事件中。
逻辑元件生产与生产的分工
逻辑元件从被编排到被运行的过程,也是它被生产的过程。文章到这里,置信大家能够感触到这个生产的过程,非研发同学的确是能够进入的,因为咱们设计的足够简略。咱们把整个页面的生产当做一条流水线的话,以前是 模块生产 –> 模块市场 –> 页面搭建,有了逻辑编排后,咱们的流水线比之前划分的更细了。
这张图是产品进入到中期的一个状态,后期的时候产品的角色更多是由研发来承当的,而后逐步过渡到产品,到了前期呢,产研联结给经营同学做培训,两头这部分会逐步得过渡到经营同学那边。
因为整条线上大家须要关注的事件越来越细了,页面生产线出错的几率也会低一些。
逻辑编排
下面始终在从业务角度去聊,接下来就要深刻到逻辑编排里去了,讲一讲逻辑编排的设计思维。逻辑编排最次要是分为三块,元件、编排器、runtime,我将它们称作逻辑编排三板斧。那问题就转换成了:
- 元件该怎么做?
- 编排器该怎么做?
- runtime 该怎么做?
这三块各自拆解出基本要素,用基本要素来形容它们,相互之间建设起连贯关系,这些形成了逻辑编排的标准协定。这样不论什么业务进来,只有遵循这套标准,它们的底层就是统一的,各业务也不会显得散乱。
DSL
在探讨咱们的 DSL 之前,肯定要先聊一聊 DAG(有向无环图),因为咱们的 DSL 设计实质上就是 DAG。
DAG
DAG 的 G 指 Graph(图),图是数据结构中最为简单的一种,咱们在理解 DAG 之前,回顾图的几个要点:
- 顶点(vertices):图中的一个点
- 边(edge): 连贯两个顶点的线段
- 度数(degree): 从一个顶点登程有几条边,这个顶点的度数就是几
图就是由一些顶点和边组成,边就是顶点间的关联关系。DAG 中的 D 是 Directed,代表是有方向的,就是说顶点之间的边带箭头,常见的比方食物链,就是有向的;A 是 Acyclic,代表无环,从某个顶点登程,无论走那条路,都不会回到那个顶点。
基于有向无环图来约定咱们的 DSL 正合适不过:
- 咱们须要有向边来通知咱们逻辑的走向
- 流程从开始节点登程肯定要遇到完结节点才完结
- 咱们的逻辑元件可能有多个进口,就像顶点可能会有好几个度,比如说判断是否登录,就算是 2 度
- 逻辑编排中咱们没法要求流程图肯定绘制成树(一个顶点到另一个顶点,只有一条门路)那样
基于 DAG 的 DSL
每一个顶点都是逻辑元件的实例,继承自逻辑元件,也能够批改本身属性。逻辑元件分了两大类 – 根底元件和业务元件。根底元件目前只有开始和完结,其余所有须要研发开发的都是业务元件,这么设计是为了升高编排应用门槛,你不须要有任何编程根底。
- type: 目前只有三种类型,Start | End | Custom
- func: Custom 类型专用,能够是函数体,能够是函数名,如果是函数名,须要提前在 runtime 中注册
- payload: 元件会凋谢配置项给经营配置,form 表单数据会作为 payload 传入给顶点对应的函数
- next: 每个顶点都会有一或多个出度(进口),next 指向指标顶点的 uuid
跟 Flow-based programming 不一样的是,咱们移除了顶点之间的值的传递,经营不是研发,他们很难了解计算值如何流动以及如何操作它们。
Runtime
后面说到数据如何映射到 UI 时,不是故意卖关子,切实是要配合着 runtime 一起给您解说一下。逻辑与 UI 的联合这里也只是粗略提一下,要留到前面的文章细讲。
Runtime 与 UI
React 本身定位是用于构建 UI 的 Javascript 库,它做的就是通过 data 驱动 UI 展现,react 的 data 通常都存储在 state 中,咱们方才曾经通过逻辑编排拿到了奖品数据,对此 runtime 惟一要做的就是在外部 data 与内部 react 两头架一座桥。
将逻辑外部生成的数据全副存储在外部 context 中,利用公布订阅模式,每当 context 有更新时,告诉 UI 组件执行 setState,state 更新后,React 自动更新 UI。
runtime.subscribe('context', (val) => {
// handle data
this.setState({data});
})
轻量的 Runtime
YOHO 的 runtime 很小,不到 10k,麻雀虽小,五脏俱全,接下里咱们看看这小麻雀是如何撑持起流程编排的最初一公里 –“执行逻辑”,又是如何和业务打交道的😊。
逻辑元件实质上就是基于一段代码,给它加了一些形容信息,可是我的页面中是不会内置这一段段代码的,runtime 也不可能内置这些代码,也就是说咱们的执行上下文中是没有元件对应的函数的;咱们的业务是 No Code 模式,不出码,所以 Runtime 外部实现了一个元件管理器,你能够通过他进行元件的注册,在 DSL 执行过程中,它也会帮你进行元件查看。
Runtime 对每一次编排实例(流程)的调用都是在沙箱中进行,实例的执行是互不烦扰的,而执行过程中每个元件输入的后果咱们也做了隔离;其实咱们还给元件之间相互通信提供了办法,然而目前不倡议应用,因为元件之间随便通信有很大的副作用,咱们目前还没有足够的产品状态去束缚它。
在 runtime 设计过程中,咱们预感业务会一直地有各种需要进来,可是咱们不心愿 runtime 过于业务定制化,导致未来积重难返,所以设计了生命周期。在编排实例执行的各个阶段,业务都能够进行干涉。
应用公布订阅模式,而不是观察者模式的起因,也是咱们心愿 runtime 足够灵便。举例来说,上下文因为数据隔离,咱们把它拆解为各个子上下文,你能够把每一个 childContext 当做一个 topic 来订阅,其余 childContext 更新并不会附带影响以后的。childContext。
除了以上,runtime 只做了一个事件 —— 逻辑走向的调度,就是有向无环图中的“有向”。正是基于以上的设计,咱们的 runtime 足够小,然而又足够灵便,便于定制。
三板斧在本文中就先讲这第一斧😄
后话
在做逻辑编排平台的过程中,计划做过好几版——从一开始两周搞出 iMove 初版给老板去展现,而后想要拥抱团体,和优良的编排平台 Logic Force 想着共建前端编排,因为咱们最终的产品状态差别过大,LF 想要反对也须要投入很大的精力,同时咱们的业务想要疾速验证,所以又从新自研了一个轻量的逻辑编排平台。期间有很多感悟,咱们也都在逻辑编排的设计中增加进去了。
逻辑编排讲到这里还没有完结,咱们前面还会有系列文章,元件和编排器是如何设计的呀,和 UI 进行联动,UI 侧又是怎么设计的呀,咱们在逻辑编排方面又做了哪些前端特色的货色呀?如果你也感到好奇,敬请关注~