大型前端项目降低复杂度新思路(1),web前端开发笔试题

});

}

还会发生出请求出错的情况:

function onSearch(keyword) {

this.setState({

isLoading: true,

});

fetch(SEARCH_URL + “?keyword=” + keyword)

.then((data) => {

this.setState({ data, isLoading: false });

})

.catch((e) => {

this.setState({

isError: true,

});

});

}

当然,不能忘记把 Loading 关掉:

function onSearch(keyword) {

this.setState({

isLoading: true,

});

fetch(SEARCH_URL + “?keyword=” + keyword)

.then((data) => {

this.setState({ data, isLoading: false });

})

.catch((e) => {

this.setState({

isError: true,

isLoading: false,

});

});

}

我们每次搜索时,还需要把错误清除:

function onSearch(keyword) {

this.setState({

isLoading: true,

isError: false,

});

fetch(SEARCH_URL + “?keyword=” + keyword)

.then((data) => {

this.setState({ data, isLoading: false });

})

.catch((e) => {

this.setState({

isError: true,

isLoading: false,

});

});

}

这就结束了么,是不是我们把所有的 Bug 都考虑进去了?并没有。当用户在等待搜素请求的时候,不应该再去搜索,所以搜索结果返回前,禁止再次发送请求:

function onSearch(keyword) {

if (this.state.isLoading) {

return;

}

this.setState({

isLoading: true,

isError: false,

});

fetch(SEARCH_URL + “?keyword=” + keyword)

.then((data) => {

this.setState({ data, isLoading: false });

})

.catch((e) => {

this.setState({

isError: true,

isLoading: false,

});

});

}

可以看到,应用的复杂度在不断变大,可能你经历的场景比这个小示例还要复杂的多的多。如果因为搜索接口特别慢,用户希望有一个中断搜索的功能,那么新的需求又来了:

function onSearch(keyword) {

if (this.state.isLoading) {

return;

}

this.fetchAbort = new AbortController();

this.setState({

isLoading: true,

isError: false,

});

fetch(SEARCH_URL + “?keyword=” + keyword, {

signal: this.fetchAbort.signal,

})

.then((data) => {

this.setState({ data, isLoading: false });

})

.catch((e) => {

this.setState({

isError: true,

isLoading: false,

});

});

}

function onCancel() {

this.fetchAbort.abort();

}

不能落下对 catch 的特殊处理,因为中断请求会触发 catch:

function onSearch(keyword) {

if (this.state.isLoading) {

return;

}

this.fetchAbort = new AbortController();

this.setState({

isLoading: true,

isError: false,

});

fetch(SEARCH_URL + “?keyword=” + keyword, {

signal: this.fetchAbort.signal,

})

.then((data) => {

this.setState({ data, isLoading: false });

})

.catch((e) => {

if (e.name == “AbortError”) {

this.setState({

isLoading: false,

});

} else {

this.setState({

isError: true,

isLoading: false,

});

}

});

}

function onCancel() {

this.fetchAbort.abort();

}

最后还要处理没有值的情况:

function onSearch(keyword) {

if (this.state.isLoading) {

return;

}

this.fetchAbort = new AbortController();

this.setState({

isLoading: true,

isError: false,

});

fetch(SEARCH_URL + “?keyword=” + keyword, {

signal: this.fetchAbort.signal,

})

.then((data) => {

this.setState({ data, isLoading: false });

})

.catch((e) => {

if (

e &&

e.name == “AbortError”

) {

this.setState({

isLoading: false,

});

} else {

this.setState({

isError: true,

isLoading: false,

});

}

});

}

function onCancel() {

if (

this.fetchAbort.abort &&

typeof this.fetchAbort.abort == “function”

) {

this.fetchAbort.abort();

}

}

仅仅这么简单的一个小需求,从开始几行代码就可以完成,到最终判断各种边界完成的代码,对比一下,如下图所示:

7faac2c19cec0fde4801d918c2132464.png

可以看到,这种包含各种 flag 变量和嵌套着各种 if/else 的代码,会越来越难维护,所有的逻辑只存在于你的脑子里。当你写测试的时候必须从头再梳理一遍代码逻辑,才能写出来。

由于业务的高频变化,很多业务开发人员是不写单元测试的,因为成本太高太高,这也导致了交接代码时,别人去理解你的代码是一件很困难的事。写久了,你自己都可能读不懂代码里面的逻辑了。

这样会导致:

  • 难以测试

  • 难以阅读

  • 可能含有隐藏的 Bug

  • 难以扩展

  • 新功能增加时还会使逻辑进一步混乱

1.3.3. 基于状态机

看一下我们用状态机的做法。记住流程:梳理出有哪些状态,每个状态有哪些事件,经历了这些事件又会转换到什么状态。

下面是用 XState 状态机工具的 JSON 描述:

{

“initial”: “空闲”,

“states”: {

“空闲”: {

“on”: {

“搜索”: “搜索中”

}

},

“搜索中”: {

“on”: {

“搜索成功”: “成功”,

“搜索失败”: “失败”,

“取消”: “空闲”

}

},

“成功”: {

“on”: {

“搜索”: “搜索中”

}

},

“失败”: {

“on”: {

“搜索”: “搜索中”

}

}

}

}

没错,就这几行代码就描述清楚所有的关系了。并且,可以把它可视化出来,如下图所示:

8e618bd853c663670cc4d18393267871.png

可以看到状态之间表达的非常清晰,结合到 View 中,也不需要再去编写复杂的 flag 及 if/else 了,View 中只需要知道当前是什么状态,已及将事件发送到状态机就可以了,其他什么都不需要做。在新增或者修改需求的情况下,只需要对状态进行新增或者编排就可以了。

而且可视化后,有以下变化:

  • 清晰的看到有哪些状态

  • 清晰的看到每个状态可以接受哪些事件

  • 清晰的看到接受到事件后会转移到什么状态

  • 清晰的看到到达某个状态的路径是怎么样的

2. 解决协作的问题

===========

另一个很大的问题是解决协作问题,主要包括:

  • 与测试开人员的协作沟通

  • 与 PD 人员的协作沟通

  • 与其他前端开发人员的协作沟通

  • 与用户的协作沟通

这里就需要引用一个可视化的概念了。「可视化,是利用人眼的感知能力对数据进行交互的可视表达以增强认知的技术」 。

所以很大程度上,可视化可以解决一大部分协作问题。当然,必须要确定把什么进行可视化才是有意义的。

要想可视化,状态工具就需要具备可序列化的能力。这也是 Redux 之类的状态管理工具缺乏的,主要有以下几方面问题:

  • 不具备可视化的能力

  • 状态和数据混在一起

  • 所有的状态都是平级的,无法描述状态之间的关系

2.1. 状态图


回到状态机。你单纯用状态机去写代码,需求数量上去了,状态多了,会面临 “状态爆炸” 问题,依然很难维护,且阅读成本巨大。

当然,这个场景其实很早之前就有人考虑到了,1987 年,Harel 就发表论文,解决复杂状态机可视化的问题,在状态机的基础上进一步增强,提出状态图的概念。随后,由微软、IBM、惠普等多家公司,从 2005 到 2015 年花了 10 年时间制定了规范,并推出了 W3C 的 State Chart XML (SCXML) 规范,至此基本稳定,各家编程语言也基于此规范进行了状态图的封装。

看一下,状态机、状态图和手写代码复杂度的对比,如下图所示:

0fe48216e2af037fb4305c023ebc97b2.png

从图中可以看到:

  • 传统编码方式,随着状态和逻辑的增加,复杂度是线性增长的。

  • 使用状态机,前期复杂度很底,但随着状态的增多,“状态爆炸”现象的出现,复杂度也急剧增长。

  • 使用状态图,虽然前期成本略高,但后期的状态和逻辑的增长,基本不太会影响它的复杂度。

前面给状态机画的图,就是状态图。

状态图大概长这样,如下图所示:

beb044bec8068a8a34b1674b366925de.png

主要包括:

  • 状态

  • 原子状态

  • 复合状态

  • 条件状态

  • 最终状态

  • 历史状态

  • 初始状态

  • 并行状态

  • 伪/瞬间状态

  • 转换

  • 自动转换

  • 延迟转换

  • 自身转换

  • 内部转换

  • 操作

  • 自定义操作

  • 进入操作

  • 退出操作

  • 数据操作

  • 日志操作

  • 事件

  • 生成事件

  • 延迟时间

  • 条件

  • 数据

  • 调用

即使状态非常复杂,也可以通过状态图的模式进行聚合、分组、细化,还可以通过 Actor 模型进行划分,不会发生 “状态爆炸” 现象。

2.2. 文档化


目前对项目需求的描述主要有:

  • 产品需求文档(PRD)

  • 设计稿

而这两个,在描述页面行为上都不够细致,PRD 几乎不会去描述过于细节的交互行为,设计稿大概率也不会(因为业务交付周期上不允许在这上面花费太多的时间)。而对于这些不清楚的、模糊的点,就带来了后面的问题,针对于这些细节点,各个角色之间的沟通成本和拉通成本。

还有一个很严重的问题,就是同步问题。很多时候在开发过程中,进行需求变动,而大多数情况下,这些变动不会重新对 PRD 和设计稿进行修改,不同角色之间去对焦及未来回顾,都是问题。

而如果你使用状态机开发,那这两个问题就可以迎刃而解。状态机方式,要求你在开发之前必须把所有可能的状态都罗列出来,状态之间的关联关系必须描述清晰。基于生成的状态图,是可以完全表达清楚所有的状态交互及变化,且它是来源于代码的,所以它是实时同步的,你代码中怎么运行的,这个状态图就是怎么表达的。

2.3. 角色影响


回到前面说的,与不同角色协作的问题上。有了状态图的加持,会发生什么变化:

  • 设计师可以根据状态图中的不同状态,来确定哪种状态合适用什么样的 UI。

  • 对于 PD,可以查看状态图,以了解系统行为,并验证是否满足要求。

  • 对于测试和用户,状态图完全充当说明书用,以前不知道如何才能到达某个状态,现在一目了然。

  • 对于测试还有一个很大的区别,因为基于状态机去写的,所以可以使用 Model-Based Testing,而这部分测试,可以由某些状态机工具自动化掉。

  • 对于交接的前端开发来说,有说明书在手,每个状态都十分清晰,能做的事也十分清晰,在具备状态机基础的情况下,是可以快速上手的。

2.4. 提升用户体验度:用户操作链路追踪和分析


除了解决复杂度的问题,基于状态机的特性,还可以带来一些新的思路,如用户操作链路追踪和分析。

2.4.1. 常见分析用户操作链路方法

目前,针对于分析用户操作链路的方法,主要是在页面中的可操作标签上进行埋点,如,Button、Tab Item 等。有手动埋点和自动埋点。

  • 手动埋点,可以按照你的意愿来收集特定区域的操作数据,但成本偏高,需要一个一个的手动接入,还可能需要自行上报数据。

  • 自动埋点,通常是自动在一些常用的标签上埋点,但会存在具体的标签变更的问题,且不能覆盖所有可操作的区域,数据精度不够。

无论使用哪种埋点,都存在 「回放噪音」 的问题。

如,上报信息里包含,“查看详情” 按钮的操作,那么对应的 “详情对话框” 一定会出来么?这个时候链路回放,只能去猜测,认为点击了这个按钮,就意味着这个对话框出来了。其实是不准确的。

如果,页面上新增加了一个功能,要判断这个新功能用户的使用量,及用户做了哪些操作才找到这个新功能。通过这个数据来判断新的交互设计是否存合理。在这种不精准数据及 “噪音” 的回放中也是不准确的。

同样,分析页面中的哪些部分是高频操作,也有类似的问题。

2.4.2. 基于状态机的链路分析方法

状态机做这种用户链路分析,是天然合适的。因为用户的所有操作,所有行为,本质上就是 “状态在接收了什么事件,要变换到什么状态” 上的过程。这是在 View 上埋点的方式缺乏的。

我们只需要在每次 “状态” 发生转换时,把状态图数据上报到分析平台就可以。完全可以基于状态的方式, 1:1 的回放用户操作链路。

3. 总结

======

最后,总结一下状态机方式带来的好处和不足。

3.1. 优势


  • 比传统的编码方式,更容易理解。

  • 基于行为建模,与视图解耦。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

核心竞争力,怎么才能提高呢?

成年人想要改变生活,逆转状态?那就开始学习吧~

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

JavaScript

性能

linux

前端资料汇总

完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可免费领取了

前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。

提高呢?

成年人想要改变生活,逆转状态?那就开始学习吧~

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

JavaScript

性能

linux

前端资料汇总

完整版PDF资料免费分享,只需你点赞支持,动动手指点击此处就可免费领取了

前端工程师岗位缺口一直很大,符合岗位要求的人越来越少,所以学习前端的小伙伴要注意了,一定要把技能学到扎实,做有含金量的项目,这样在找工作的时候无论遇到什么情况,问题都不会大。

  • 16
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值