业务背景

目前团队内的开发模式多是面向组件的,UI层和逻辑层均强耦合在一起,由于业务的差异性,往往很难完全复用。

  • 闲鱼前端业务处在高速发展不断尝试的阶段,如何能更快更稳定地完成需求,更好的支撑业务发展绝对是一个值得探索的问题。

  • 在接手一个复杂的老业务代码时,经过较多人的修改,往往可维护性较差,有时只想修改某个小地方却需要较大的理解成本,所以用一套统一的组件开发规范在长期维护中显得格外重要。

  • 闲鱼技术体系经历了从weex、rax0.x到现在rax1.x的变更,中间有过一些前端资产的积累,但是由于迁移的成本后期都不再维护,如何用更小的成本让业务层平稳过渡到新的技术体系?

对于以上的问题我们希望能用框架一并解决,对于该框架的目标主要包括:

  • 提高代码可复用性

  • 规范代码,降低长期维护成本

  • 降低业务层与技术体系的关联

思路

关于提效,其中比较重要的是相同的代码不要重复写,做更细的区分和提取,提高可复用的颗粒度。另一方面是解决现有开发下比较影响开发效率的问题。

组件的分层

所以我们将面向组件的开发模式分为UI层View和逻辑层Store,以Interface进行隔离和耦合。

图一:组件构成

在UI层无需关心状态的流转,只负责展示和交互方法的调用,DOM相关的动画交互等行为逻辑也会放到该层中。 

图二:组件分工

在确认了分层的逻辑后自然就引入了Interface,主要分为两部分:一部分是IProps,申明该组件所需的Props,在使用者调用该组件时进行对应的提示和约束;另一部分则负责连接Store和View,其中包括状态state和交互方法;见下面的Interface示例:

export interface IMultiScrollerProps {tabs: string[];onTabChange?(i: number): void;
}export interface IMultiScroller extends IBase {readonly tabIndex: number;readonly tabSource: ITabItem[];readonly children: any[];onSwiperChange(i: number): void;
}

总结一下:所有的state和交互方法都在store中管理,供View消费;View中只负责和dom相关的逻辑操作,View和store的职责分界线就是View和store分别单独使用时其交互和效果都能保持不变;以此实现View和store分别能有更多的复用。

状态管理

现有的业务开发中基本所有的需求都是基于hooks的状态管理,主要存在以下问题:

  • 对于较复杂的组件hooks在多次迭代后的维护成本会非常高;

    • 有时候,你的useEffect依赖某个函数的不可变性,这个函数的不可变性又依赖于另一个函数的不可变性,这样便形成了一条依赖链。一旦这条依赖链的某个节点意外地被改变了,那么useEffect就被意外地触发了后面的情况就会变得不可控。

  • 异步陷阱

    • 状态的修改是异步的 useState返回的修改函数是异步的,并不会直接生效,所以此时读取该值获取到的是旧值。要在下次重绘才能获取新值。不要试图在更改状态之后立马获取状态。

const [value, setValue] = useState(0);
setValue(100);
console.log(value); // <- 0

•timeout指向的是旧值

timeout指向的是旧值,即使在外部已经重新设置,由于闭包所有在setTimeout中获取到的都是之前的值。

const [value, setValue] = useState(0);
window.setTimeout(() => {console.log('setAnotherValue', value) // <- 0
}, 1000);
setValue(100);

•何时使用useCallback/useMemo等对于新手来说存在一定的门槛。

关于 Hook 中的闭包:useEffect、useMemo、useCallback都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state、props)。所以每一次这三种 Hook 的执行,反映的也都是当时的状态,无法获取最新的状态。对于这种情况,应该使用 ref 来访问。

对于状态管理react体系中最受欢迎的应是redux与mobx 

图三:Redux Flow

redux的特点从上图可以总结得到下面的三大原则:

•单一数据源

•state 是只读的

•使用纯函数来执行修改

但是redux的问题也是十分明显的:开发者需要写更多附加的样板代码,并且留下更多需要我们维护的代码。

与 Redux 相似的,另一个状态管理方案是 MobX: 

图四:Mobx Flow

相比 Redux 的强规则约定,MobX 更简单灵活,核心原理是通过 action 触发 state 的变化,进而触发 state 的衍生对象(Computed value & Reactions)。开发者只需要定义需要 Observe 的数据和由此衍生的数据(Computed value)或者操作 (Reactions),剩下的更新就交给 MobX 去做就可以了。一句话总结就是:

任何源自应用状态的东西都应该自动地获得。

分析闲鱼的业务特色并不存在5个以上同学同时维护一个项目的超大型需求,强约定的redux对我们来说收益有限,而MobX 确实比 Redux 上手更容易些,并且不需要写很多样板代码,可以提供更高效的选择。

实现

我们给框架取名:Linke,来自switch的游戏塞尔达,希望它能像林克一样点亮一个个神庙。

基于上面的分析思路结合实际业务中的技术体系(Rax)最后我们设计了下面的研发体系:UI部分也就是View还是沿用原有的Rax,UI用到的状态也直接在View中管理。业务逻辑部分也就是Store用Mobx的能力解决上面提到的现有hooks开发遇到的问题,两者没有强关联。

Linke做为中间耦合层对他们进行约束和桥接。 

图五:基于Linke的研发体系

API

为保证开发者最低的学习成本,Linke在设计时尽可能地减少API,最终只有一个方法和4个Store内置方法,详见:

observer(baseComponent, Store)

保证组件能响应store中的可观察对象(observable)变更,即store更新,组件视图响应式更新

Store内置方法

•成员方法 - $$set: 所有状态变化必须通过$$set来完成,与微信的setData()类似

•成员方法 - $$setProps:处理外部传入的组件props,View初始化或者props发生变化时调用

•成员方法 - $$didMount:提供View的生命周期,View被插入DOM时调用

•成员方法 - $$unMount:提供View的生命周期,View被移除DOM时调用 可以看出Store内置方法中除了$$set其他三个都是生命周期方法,其调用顺序为:$$setProps -> $$didMount -> $$unMount

demo

Interface.ts

import { IBase } from '@ali/idlefish-linke';
export interface IComponentProps { // 组件所需propstabs: string[];onTabChange?(i: number): void;
}
export interface IComponent extends IBase { // 连接view和store的state&交互方法readonly items: any[];handleLoadmore(): void;
}

index.tsx

import { observer } from "@ali/idlefish-linke";
import Store from './store';
import { IComponent, IComponentProps } from './interface';
function Component({items, handleLoadmore}: IComponent) {return (<View>{items.map(item => {return <Text>{item.title}</Text>})}<View onClick={handleLoadmore}>load more</View></View>)
}
export default observer<IComponentProps>(Component, Store);

store.ts

import { makeAutoObservable } from "@ali/idlefish-linke";
import { IComponent } from './interface';
export default class ComponentStore implements IComponent {/*** 所有状态变化必须通过$$set来触发Effect* $$set赋值来自于makeAutoObservable(this);* this.$$set('items', [])*/$$set;/*** 带初始值的属性会自动被观测*/items: any[] = [];page: 1;constructor() {// 自动observable该类makeAutoObservable(this);}$$setProps(props) {... // 对props的处理可以放到这里}$$didMount() { // 通过 $$didMount / $$unMount 来感知view的生命周期this.fetch();}fetch () {mtop.request('mtop.xxx', {page}).then(d => {this.$$set('items', d.list);})}handleLoadmore = () => {this.$$set('page', this.page++);this.fetch();}
}

上面就是一个完整的组件demo。

对比

现在的组件开发模块模式如下图六所示,以组件为单位所有的逻辑是耦合在一起的,相互之间没有分界,即便是相同的样式也很难实现复用。无论是在代码理解还是二次开发上都存在较大的成本和不稳定性风险。 

图六:原组件的开发模式

基于Linke的组件开发模式如下图所示:  图七:基于Linke的组件开发模式

View和Store相对独立没有强耦合性,这样的好处显而易见:

• 通过阅读Interface就能知道Store/View的基本逻辑,减少理解成本

• 数据逻辑和View逻辑分别在Store和View中管理,真正实现各司其职,减少维护成本。

• 最重要的一点是通过分离让Store和View分别实现了复用,组合不同的Store/View生成不同的组件 

图八:Store分别和不同的View组合

图九:不同的Store和同一个View组合

应用

目前Linke已经应用在了闲鱼前端各个新项目中,包括2个线上项目和3个正在开发的项目收益明显,什么功能的代码在什么位置一目了然配合Interface中的注释大大减少了接手项目的理解成本。

通用基础组件和业务组件都在有序的抽离中,同时随着View/Store库的不断丰富,可以复用的物料资源增加,不同业务和同一业务不同场景中可以复用的View/Store越来越多,在一定程度上大大减少开发成本提高效率。

低成本可复用前端框架——Linke相关推荐

  1. Bifrost微前端框架及其在美团闪购中的实践

    Bifrost(英 ['bi:frɔst])原意彩虹桥,北欧神话中是连通天地的一条通道.而在漫威电影<雷神>中,Bifrost是神域--阿斯加德(Asgard)的出入口,神域的人通过它自由 ...

  2. 滴滴开源 LogicFlow:专注流程可视化的前端框架

    桔妹导读:LogicFlow 脱胎于滴滴技术团队在客服业务下的实践,是由滴滴智能中台体验平台研发的一款流程可视化的前端框架,提供了一系列流程图交互.编辑所必需的功能和灵活的节点自定义.插件等拓展能力, ...

  3. 程序员Web面试之前端框架等知识

    基于前面2篇博客: 程序员Web面试之jQuery 程序员Web面试之JSON 您已经可以顺利进入Web开发的大门. 但是要动手干,还需要了解一些已有的前端框架.UI套件,即要站在巨人肩膀上而不是从轮 ...

  4. Web前端框架与类库的思考

    说起前端框架,我也是醉了.现在去面试或者和同行聊天,动不动就这个框架碉堡了,那个框架好犀利. 当然不是贬低框架,只是有一种杀鸡焉用牛刀的感觉.网站技术是为业务而存在的,除此毫无意义,框架也是一样.在技 ...

  5. 前端框架Vue、angular、React的优点和缺点,以及应用场景

    学习web前端开发中,会有很多的框架,那么目前流行的框架有哪些,以及他们的优缺点和应用场景有哪些呢? 一.Vue.js: 其实Vue.js不是一个框架,因为它只聚焦视图层,是一个构建数据驱动的Web界 ...

  6. 写在2021: 值得关注/学习的前端框架和工具库

    前言 最近在知乎看到了这么个问题:学完Vue还有必要学习React和Node吗?[1], 有很奇妙的感觉,因为我在最开始入门前端时,也是以Vue入的门,在"学完"Vue之后, 我也 ...

  7. 2021 年 Angular vs. React vs. Vue 前端框架对比

    2021 年 Angular vs. React vs. Vue 前端框架对比 一个是 UI 库(React),另一个是成熟的前端框架(Angular),而其中最年轻的(Vue)则可以称之为渐进式框架 ...

  8. 前端框架这么火,还有必要学好原生 JavaScript 吗?

    作为一名前端工程师,JavaScript 你一定每天都在用.但是,即便工作 5 年以上的前端也不一定用得非常熟,甚至很多前端对 JavaScript 的掌握程度仅仅停留在会用的层面. 而且 Vue/R ...

  9. Web 组件即将取代前端框架?!| 技术头条

    [CSDN 编者按]提起前端开发,不少开发者首先会对主流技术框架如 Vue.React.Angular 进行一番对比之后,选择相应的技术架构. 在此,随着前端框架的不断升级,其也变得越来越臃肿与复杂, ...

最新文章

  1. nacos 配置动态刷新_nacos配置中心修改后刷新
  2. GPU — CUDA 编程模型
  3. java oop_Java实现OOP(面向对象编程)
  4. 顺丰gis产品经理_线上面试季丰图科技—顺丰旗下专注GIS领域
  5. 如何查询日志文件中的所有ip,正则表达式
  6. oc - runtime运行机制
  7. sql server 群集_SQL Server中的Microsoft群集
  8. JavaScript getBoundingClientRect()
  9. 《第一本Docker书(修订版)》——第1章_简介_1.1Docker简介
  10. 京东大数据平台产品体系揭秘
  11. 影响力在职场的重要性
  12. 前端的CSP CSP如何落地,了解一下
  13. 图像宽度所占字节必须是4的倍数详解
  14. Mob平台获取手机验证码
  15. 【版权】软著和专利的申请过程
  16. 怎么看计算机的网络ping值,Win7怎么查看ping值 win7测试ping值的方法
  17. 用CSS3实现无限循环的无缝滚动
  18. 【教学】手写汉字识别,含训练代码,界面演示,1w字教学
  19. 【备忘】AAD Intune维护
  20. 零样本学习和小样本学习(转)

热门文章

  1. 在 Linux 中使用ImageMagick命令行操作图像文件
  2. 一起聊聊好玩的Openresty
  3. acrh17华硕固件_华硕全新手游路由器RT-AC85P首发:雷达定向传输
  4. 【哈渡谱】带你玩转Hadoop之《主机映射、镜像挂载与SSH免密篇》
  5. HDU4635(强连通分量+Kosaraju算法)
  6. P1444 [USACO1.3]虫洞 wormhole,新手向超详细讲解(搜索、判环、拆点、好题)
  7. mysql分页 redis_分页查询和redis
  8. B - Dungeon Master POJ - 2251
  9. php上传漏洞绕过gd库,jQuery File Upload任意文件上传漏洞
  10. socket心跳机制图片_WebSocket心跳检测和重连机制