写点什么

低成本可复用前端框架——Linke

  • 2021-04-19
  • 本文字数:3831 字

    阅读完需:约 13 分钟

低成本可复用前端框架——Linke

业务背景

目前团队内的开发模式多是面向组件的,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 { // 组件所需props  tabs: 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 越来越多,在一定程度上大大减少开发成本提高效率。

展望

目前新财年除了现有的 H5 业务外,最大的特点是会对各个小程序做一些流量探索,比如淘系轻应用、微信小程序、支付宝轻应用等,这些应用的特点是与端内的 H5 业务及其相似,但是会有各自的细微差异。所以我们也在探索基于 Linke 对此类业务场景的提效。


本文转载自:闲鱼技术(ID:XYtech_Alibaba)

原文链接:低成本可复用前端框架——Linke

2021-04-19 07:003920

评论

发布
暂无评论
发现更多内容

软件测试/测试开发丨Python 装饰器 学习笔记

测试人

Python 程序员 软件测试 装饰器 测试开发

设计原则 — KISS & YAGNI

Lemoon Can

设计原则 KISS YAGNI

如果你在选型低代码平台,可以从这5个角度去分析抉择

互联网工科生

源码 低代码 系统集成 私有化部署

实时数仓:Iceberg

腾讯云大数据

数据仓库

点云标注在自动驾驶中的实时性要求

数据堂

香港VPS大揭秘:轻松打造超高流量网站

一只扑棱蛾子

VPS 香港VPS

我的 Obsidian 笔记跨设备同步方案

专注前端开发

工具 笔记 Obsidian

鸿煦科技刘敏:小程序云开发降本增效实践之路

TRaaS

小程序 支付宝 开发

九州八荒录H5游戏详细图文架设教程

echeverra

游戏开发

2 种方式查找极狐GitLab 容器镜像 Tag,几分钟快速构建私有化部署实例

极狐GitLab

DevOps gitlab Helm 容器镜像 Omnibus package

门槛一降再降,易用性大幅提升!Milvus 2.2.12 持续升级中

Zilliz

Milvus Zilliz 向量数据库

程序员必读十大电子书

六月的雨在InfoQ

电子书 Java工程师成神之路

腾讯云 Cloud Studio 实战训练营活动招募中

CODING DevOps

活动 cloudstudio 云端 IDE

你了解Vue3组合式API吗?

OpenTiny社区

Vue 前端框架 开源组件库

来聊点认证与授权的话题

为自己带盐

.net core OAuth 2.0 Openiddict

当流计算邂逅数据湖:Paimon 的前生今世

Apache Flink

大数据 flink 实时计算

给世界一个更好的选择,“龙蜥+超级探访”首期嘉宾预告片震撼来袭!

OpenAnolis小助手

开源 操作系统 龙蜥社区 统信软件 超级探访

QQ开展外挂专项整治,守护用户社交环境安全

极客天地

OpenSSL 3.0.0 设计(一)|介绍、术语与架构

铜锁开源密码库

开源 算法 信息安全 密码学 铜锁

HuntingNFT 全新玩法引爆市场,GameFi掘金新热土?

鳄鱼视界

灵动AI推出业内首个工业级“AI商品图”生成工具 并获小米联合创始人黎万强天使投资

TE智库

提高代码质量!详解在Gradle项目中使用PMD的正确姿势

树上有只程序猿

Gradle

git撤销某一次commit提交

树上有只程序猿

git

【福利活动】深度体验OpenHarmony对接华为云IoT

OpenHarmony开发者

OpenHarmony

Java基础!Java反射机制!!

java易二三

Java 编程 互联网 计算机

一文帮你全面认识方天视窗引擎

openEuler

Linux 开源 操作系统 openEuler 视窗引擎

低成本可复用前端框架——Linke_大前端_闲鱼技术_InfoQ精选文章