最新发布《数智时代的AI人才粮仓模型解读白皮书(2024版)》,立即领取! 了解详情
写点什么

低成本可复用前端框架——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:003491

评论

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

探秘英伟达显卡的制造之路 | 英伟达断供GPU,中国大模型何去何从?

蓝海大脑GPU

TiDB x 北京银行丨新一代分布式数据库的探索与实践

TiDB 社区干货传送门

实践案例

机械加工行业云MES解决方案

万界星空科技

工业互联网 制造业 生产管理系统 mes 机械臂

Windows、Linux 和 Mac:操作系统之间的比较

小齐写代码

Mac电脑版红巨星粒子插件 Red Giant Trapcode Suite激活中文版

mac大玩家j

Mac软件 红巨星粒子插件 视觉效果插件

ATC汽车电子与软件技术周:汽车行业客户使用静态代码扫描工具的案例与建议

龙智—DevSecOps解决方案

ACT

安全审计 合约审计:代币开发公司的一个关键方面

区块链软件开发推广运营

交易所开发 dapp开发 链游开发 NFT开发

如何使用 NFTScan NFT API 在 Polygon 网络上开发 Web3 应用

NFT Research

NFT NFT\ NFTScan nft工具 API 接口

jar包的精细化运营,Java模块化简介 | 京东云技术团队

京东科技开发者

Java Java9 模块化 jar包

趋势:国产数据库发展的五个一体化

科技热闻

新形势下芯片研发如何实现数智化转型,革“芯”未来?龙智即将携手Perforce及Atlassian亮相ICCAD 2023

龙智—DevSecOps解决方案

ICCAD

大模型在代码缺陷检测领域的应用实践

百度Geek说

知识图谱 大模型 企业号11月PK榜

实用PCB拼版攻略,您的拼版合理吗?

华秋电子

拼板

自动化API测试工具ReadyAPI新增业务历史记录功能

龙智—DevSecOps解决方案

ReadyAPI

QCN9074 QCN9024|DR9074E Compatible with DR4019 Platform OpenWrt

wallyslilly

QCN9074 QCN9024

MegEngine 9-10 双月报:新版本发布,AI 生态升级,不容错过!

MegEngineBot

深度学习 开发者 开源社区 MegEngine

万界星空科技MES与WMS如何集成的?

万界星空科技

数字化转型 工业互联网 wms mes 3D仓库

您的代理记账企业的数字化保卫和合作伙伴-智慧云

知者如C

活动回顾|OpenTiny 参与线下大会:共创开源未来,助力低代码技术创新

OpenTiny社区

开源 前端 低代码 UI组件库

【TiDB 社区智慧合集】史上最全的 158篇 TiDB 用户实践合集来啦!千万不要错过!

TiDB 社区干货传送门

PCB设计安规丨电气间隙与爬电距离要点

华秋电子

vivo 网络端口安全建设技术实践

vivo互联网技术

网络端口风险治理 端口扫描 流量行为基线 端口管理规范 漏洞扫描平台

Nginx 知识点一网打尽:动静分离、压缩、缓存、跨域、高可用、性能优化

郑在暴富中

nginx 高可用 反向代理

TiDB基础运维操作汇总

TiDB 社区干货传送门

管理与运维 故障排查/诊断 6.x 实践

混音师必备软件:iZotope Ozone 11激活最新

胖墩儿不胖y

Mac软件 音频处理工具 母带处理软件

TiDB故障处理之让人迷惑的Region is Unavailable

TiDB 社区干货传送门

集群管理 故障排查/诊断 扩/缩容

观测云产品更新 | 单点登录新增 OIDC / Oauth2.0 协议、数据转发优化、场景优化等

观测云

可观测性 单点登录 #数据分析

【实践篇】一次Paas化热部署实践分享 | 京东云技术团队

京东科技开发者

热部署 PaaS平台化能力 企业数字化 PaaS 平台 企业号11月PK榜 Paas化

飞码LowCode前端技术:如何便捷配置出页面 | 京东云技术团队

京东科技开发者

前端 低代码 低代码平台 企业号11月PK榜

OpenTiny Vue 3.11.0 发布:增加富文本、ColorPicker等4个新组件,迎来了贡献者大爆发!

OpenTiny社区

开源 TinyVue 前端组件库

KiCon Asia 2023 深圳

华秋电子

KiCon

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