44

云凤蝶如何让你写更少的代码

 3 years ago
source link: https://zhuanlan.zhihu.com/p/116818068
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

云凤蝶如何让你写更少的代码

人在少年,梦中不觉,醒后要归去

云凤蝶中台应用使用带自由拖拽功能的 IDE 制作页面,内置了 Ant Design、AntV、TechUI 等 UI 资产,内建大量最佳实践等,让开发者快速开发出高质量中台应用。

云凤蝶既是一个 IDE,也自带一个自研的运行时,所以它也属于前端框架的范畴,只不过相比传统的框架,我们更关注简单,高效。


以前我们对前端框架的要求是稳定,高性能,API 设计简洁优雅,功能齐全。而现在我们要的远远不止于此,原因也很好理解,因为程序员更懒了,公司对效率的追求也更高了。

这篇文章会聊一聊在开发云凤蝶这个产品的过程中,我们整个团队对一些云凤蝶上细小的功能设计背后的思考,讨论与实现。

# 路由排序

熟悉 react-router 的开发者都知道,它是按你代码的先后顺序往下匹配且非贪婪的,意味着你需要非常小心的编排你的整个应用的路由列表的顺序,才能比较好的实现你预期的效果。

而当我们在做云凤蝶这个产品的时候,我们设计了下面这样一个朴实无华的路由功能:

如上图所示,在 1 的区域我们允许用户创建一堆的页面,在 2 的区域是每一个页面我们允许用户设置一个 path-to-regexp 规范的路由表达式。

于是上线之后到真实的业务场景里我们发现遇到一些小问题,比如某个用户同时定义了下面这两个路由:

  • /order/list : 订单列表页
  • /order/:orderId : 订单详情页

由于从配置到运行时代码的转换过程是个黑盒,掌控在云凤蝶手中,用户是无法干预这两个路由的优先级的,那么当用户访问 /order/list 的时候,会匹配到哪个页面就完全是听天由命了。

如何修这个 bug,我们讨论出三种解法:

  1. 吐槽用户说你这个路由定义的太不规范了,你改改 ==
  2. 吭哧吭哧做一个编排页面路由优先级的功能到我们的编辑器里,将控制权(甩锅)交给用户
  3. 云凤蝶运行时尝试对路由优先级做排序,自己吃掉这背后的复杂度

一直以来在团队讨论方案的过程中,我们或多或少都会遵循一条原则,就是云凤蝶作为一个低代码的开发工具,我们时刻要关注开发者体验和效率。
所以不意外最终我们选择了方案 3,做了一版路由精确到模糊的排序算法发上线,运行至今再没有遇到类似的用户咨询,你会发现这些用户也就慢慢地遗忘了优先级这件事情,好像在 Pro Code 下存在的这个复杂度凭空消失了。

后来我看到 react-router 的作者之一 ryanflorence 另起炉灶发布的 reach-router,有个主打的特性就是智能的路由排序:

具体来说是如下这样,你写的代码路由顺序无关紧要,reach-router 会按它的算法帮你再排一遍,就是这么主观。

出于好奇我去翻了一下它的源码,算法挺不错的,但它的 注释 初看到的时候还是更击中了我,核心就是一句话: “let the computers do it“。

这些小小的细节就是我要说的意思,可能这东西并不像 AI 那么“智能”,但是足够贴心和好用,我认为这是当下激烈的开源社区框架竞争里面,谁能赢得市场很重要的一个因素。



# loading

我们再来聊聊 loading 这件小事,相信是个前端都写过下面这种类似的代码。

function loadData() {  
    $model.loading = true;   
    await fetchData();   
    $model.loading = false; 
}  

<Loading isLoading={$model.loading}> <Table> </Loading> 

这样没什么大问题,工作照样做得完,但就是很烦,不能早点下班,毕竟复制粘贴了好多代码 ==

后来社区出现了 dva-loading ,由于 dva 规范化了 effects 这个异步的横切面,它有机会掌管所有的异步状态,于是 dva 很贴心的帮你准备好这个 loading 状态的池子,你得以省略掉对 $model.loading 的手动维护,可以少写几行代码,还是挺开心的,但 Loading 组件的包裹以及对状态的关联还是你需要自己手动做的。

再回到云凤蝶,一看到这种模式化的样板代码,这正是我们这种高度封装的框架和工具的用武之地。

第一阶段的想法,我们通过配置式的高级渲染属性的功能,我们来自动帮开发者包裹 Loading 这个父组件。

原理类似如下这样,在云凤蝶的 DSL 里面,Runtime 会解析出来云凤蝶特殊定义的 __yfd_advance 这种框架特殊配置,然后二次转换用户的代码成为真正的可运行 React 代码,可谓是用更高的信息密度和抽象去描述和生产 React 代码。

<Table __yfd_advance={{ loading: $model.loading }} />

这样效果不错,用一个开关省掉了好几行 React 代码,美中不足的是用户还是要自己维护一个 $model.loading 的变量,于是下一阶段我们干脆想,不如直接一行代码都不写算了?

想想确实是可行的,有 loading,说明有异步过程,那么代码里面必定存在一个 async 的方法,而对于云凤蝶来说,所以方法都集中存放在一个 Page Model 的 class 文件里面,那么我们只需要下面这样就更爽了:

为啥还是交互稿呢?因为缺人还没排上期,招聘中欢迎联系 :)

具体背后的原理用伪代码来解释类似下面这样:

// 这是云凤蝶的 DSL 源码
<Table 
    __yfd_advance={{ 
        loading: { 
            mode: 'auto', // 自动模式,对应以前那种 manual 的手动控制
            config: {
                // 自动模式下,用户只需要通过云凤蝶的属性面板选择一个模型里面的异步方法即可
                asyncFunctionSelector: "$model.fetchData",
            }
        }
/>

对于云凤蝶的 Runtime 来说,又要多干点活了,我们要全局维护一个类似 dva-model 的 loading 状态池子,然后给所有 model 里面的 async 方法用 decorator 包裹一下,在开始和结束的时候分别去更新状态,然后以用户配置的 asyncFunctionSelector 这个路径去状态池里面就可以直接寻址到 loading 结果了。

不过没关系,所有脏活累活交给框架是我们乐意的,只要能消灭所有不必要的样板代码,那么一切都是值得的。

# 受控组件

写过 antd 或者 react 代码的开发者应该对受控,非受控这件事情不陌生,比如以下是 antd 的 FAQ

这个体系是挺完备的,在 Pro Code 下面写起来也问题不大,但是当搬到云凤蝶的属性面板上去的时候,就感觉复杂度还是太高了,经过了漫长时间的探索和实践迭代,无数次团队内部的激烈讨论,如今我们自认是找到了一个比较完备的理论,方案是是通过“人工智能”的方式来消灭这个复杂度。

首先,在云凤蝶上接入一个新组件的时候,我们要求你声明出类似 props.value , props.onChange 这样的成对的状态属性元信息,这是一切优化的前提,即先让云凤蝶的编辑器和运行时更好的认识你这个组件。

你可以注意到,在上面的描述中,我没说要让开发者声明 props.defaultValue ,因为我们自作主张决定砍掉这个东西,取而代之的是下面这样一个推定:

如果用户给 value 配置了静态值,那么我们认为诉求其实是 defaultValue

细想这个推断“大概率”是没问题的,让一个可交互的组件变成死的这件事情不多见。

用如下的伪代码解释背后的实现原理:

// 源码
<Input value="foo">
  
// 编译产物,ControllerHOC 消费 value 作为基本值,然后会维护一个内部的 local state
<ControllerHOC value="foo">
  <Input>
</ControllerHOC>  
  

上面也说了是大概率,那么小概率的需求,如果我非要设置 value 为静态值让这个组件不可变呢?(==杠精)

对于这种低频需求,我们给的解决方案就是,请切换到单向绑定,并返回一个静态值。

至于单向绑定和双向绑定这两个功能,属于前端框架里非常经典的用来减少样板代码的抽象了,具体:

  • 单向绑定等价于 vue 的 v-bind
  • 双向绑定则等价于 vue 的 v-model

# 状态管理

这一部分的话题在 云凤蝶可视化搭建的推导与实现 里面已经细聊过,这里就不展开了。
核心思路是在特定的高约束场景下,我们无需过于担心如何解决大型项目工程化和代码组织的问题,所以云凤蝶可以去掉一切概念,只要求你写最必要的那一部分业务代码,比如可以简单成下面这样:

// 一个云凤蝶的状态管理长这样
export default class {
  // 定义状态
  count = 0;
  // 修改状态
  desc() {
    this.count = this.count - 1;
  }
}

// 消费状态
<Input value={$model.count} />
<Button onClick={() => $model.desc()} > desc <Button/>

聚沙成塔,涓滴成河,一切都还在路上。


未来已来,时不我待!
云凤蝶招聘前端、Java、PD、设计岗位,未来等你共创!
如果你感兴趣,欢迎联系 [email protected][email protected]


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK