我是一名大前端架构师,今年过完,我就是一个在互联网行业沉浮八年的老兵了。
我的成长历程,几乎贯穿了前端行业的一半历程。
我亲身体会过IE6 彻底衰败
html5 快速崛起
jQuery 淡出视线
React 一路高歌
Angular 高开低走
Vue 后来居上
我见证了前端工程化从 0 到 1 的兴盛。
很幸运的是,经历了那么多,我依然还没有停止前进的脚步。
2020 这一年,我取得了很大的进步。技术能力用突飞猛进来形容也不为过。
const aspirant = 'persistence'
一、在大型项目中,对封装与解耦有了更深刻的体会
只有在大型项目中,我们才会有意识的把可维护性的重视程度提高到最高级别。
可维护性:在需求调整时,尽量不改底层代码,即使修改也能让风险在可预判的范围之内。而这里的底层代码,通常就是指封装好的代码,不会轻易变动。
而当我们把可维护性作为代码质量的判断标准时,我们自己肯定会有一个长足的进步。
大型项目代码庞大复杂,任何一处改动,它对项目造成的影响,很有可能超出我们的认知范围。
可怕的是,改动一处逻辑,会引发新的问题,而新的问题,我们自己极有可能意识不到。
因此每一处改动的风险很大。因为风险大,验证成本也会特别高。
当变动过大,甚至会造成项目重构。
我们整个团队,遭受了大量的重构毒打。
为了在重构中降低开发成本,减少 bug 率,我在如何封装与解耦上做了大量的思考与总结。
封装是一个基础概念。在初学的时候,就知道应该把逻辑封装起来,以方便使用。
而要在各种场景下都找到合适的封装方式,却绝非易事。
特别是 JavaScript 非常灵活,解决同样的问题,有许多不同的方案可供选择。但并不是所有的方案,就具备高可维护性。
在遵循可维护性的思维指导下,我发现了很多以前的解决方案跟我现在的认知是恰好相反的。
这也让我对许多设计模式的运用有了更深刻的认知。
设计模式,说到底,就是为了解决可维护性的问题。
例如我们的小程序非常复杂。特别是入口场景非常复杂。
不同的场景目的不一样,进入小程序时传入的参数也会不一样,对应的逻辑也会不同。虽然从大的方向上来说,都是登陆逻辑。但是登陆之后,要干的事情却差异越来越大。
在常规的思维中,登陆逻辑是所有入口场景共同的逻辑,那么登陆之后需要处理的事情虽然有一些差异,但是封装在一起,使用条件判断区分开,影响不大,也不需要在每个页面去单独思考这个问题。
// 场景一
if (params.router) {
}
// 场景二
if (params.shopId) {
}
但是后来慢慢发现,在需求扩展时,条件判断用于区分场景的方式非常容易影响已经存在的场景的逻辑。
例如场景三种,也能传入 shopId,但是它与场景二还是有一些差别,那此时风险就来了。我必须在场景二中去处理场景三。
// 场景一
if (params.router) {
}
// 场景二
if (params.shopId) {
// toto
if (params.sence == '1212') {}
}
此时尽管场景二与场景三大多数逻辑都类似,但是有一点小差别。但是我代码的扩展已经影响到了场景二,我代码的修改到底会不会导致场景二出问题,我不确定。
因此,常规的统一处理逻辑,在这里就变成了一种风险很大的方案。
将不同的场景拆分重组,才是良策。即使他们相似程度很高。
虽然会导致代码量增加,但是风险几乎没有,不对已有的场景造成任何影响,才是最终目的。
二、在大型项目中,探索出来了完全摒弃 redux、mobx 的方案
自从 19 年初见识了 React hooks 的魅力之后,我一直在研究如何在项目中,完全不使用 redux、mobx 这类的全局状态管理器。
全局状态管理并非最好的方案。
特别是在大型项目中,我们将所有的页面状态,都放在全局的 store 进行管理的时候。项目占用的内存会在项目使用过程中持续升高。因为全局 store 中的数据并不会释放。
而且 redux 的代码复杂度问题,在 hooks 出来之后,就显得尤为突出。
因此,探索更简洁的代码实现,是我这两年一直在做的事情。我希望能够完全摒弃全局状态管理工具,或者只是轻量的使用。仅仅只用于解决一些全局的需求,例如皮肤修改,缓存登陆信息等。
以其中一个后台管理系统为例。
最开始的版本是使用 antd pro 推荐的方案,dva 来做状态管理。
分析之后发现,真正需要全局共享的状态 在 antd pro 中几乎没有。
具体的页面中,也只是部分页面会共享状态。大多数都是,列表页与详情页这种关系,第一步第二步这样的关系。
于是我意识到,完全可以舍弃 dva。
并且我在去年基于 context 搞出了一个极大减少代码量的状态管理工具 mozz 用于解决局部状态共享的问题。
与 context 相比,它最大的优势是理解更简单,并具备完整的 ts 类型推导。
const App = () => {
const { counter, decrease, increase } = useStore();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<a className="App-link" href="/" rel="noopener noreferrer">
Learn moz
</a>
<div>
<p>{counter}</p>
<button onClick={increase}>+</button>
<button onClick={decrease}>-</button>
</div>
<Comp />
</header>
</div>
);
};
能够具备完整的类型推导是它最具吸引力的地方,这样我们就可以不用专门针对自定义的 hooks 写一堆类型声明,感觉就像是没有使用 ts,但是依然具备了 ts 的特性。
后来我在公众号,写了一系列文章来专门介绍这个事情。
当然,它还有一部分遗留问题没有解决。比如不同的路由页面之间的状态共享问题。
于是针对这些问题我做了很多尝试。
例如,轻度依赖通过路由改变页面。页面的变化自己控制状态来实现。
例如,通过 localStorage 解决一部分共享的问题。
例如,借助发布订阅模式,监听部分页面的状态。
例如,将 hooks 与发布订阅模式结合起来封装自定义 hooks,让监听在使用时处于无感状态。
// 一个简化版封装
export default function useLoginEffect(cb: () => any, options: LoginOptions = {}) {
const [refresh, setReresh] = useState(true)
useEffect(() => {
if (!refresh) {
return
}
let isLaunch = false;
const curPageStack = Taro.getCurrentPages()
const launchPageInfo = Taro.getLaunchOptionsSync()
if (curPageStack.length === 1 && launchPageInfo.path == curPageStack[0].route) {
isLaunch = true
}
if (!isLaunch) {
setReresh(false)
cb()
return
}
if (isLaunch) {
const info = Taro.getStorageSync('userinfo')
if (!info || !info._id) {
// 登陆返回之后,执行一次回调,因此在这里监听,在登陆成功之后需要出发该监听
Taro.eventCenter.on(`${launchPageInfo.path}/login`, cb)
Taro.navigateTo({url: '/pages/login/index'})
return
}
setReresh(false)
cb()
}
}, [refresh])
return {refresh, setReresh}
}
他们都非常有效果。
三、学会了原子操作概念,并运用于项目实践
原子操作是在群里跟大家讨论时学到的新概念。
意思为:不可分割的一系列操作。
要么都做,要么都不做。不存在操作一部分的情况。
虽然平时在实践中,我们都在大量的解决类似的问题。但是一直没有将这样的现象提炼为一个概念。
当然从产品逻辑的角度来说,原子操作有非常多可以扩展的案例。我们面临的工业软件,有大量的这种需求的场景,一旦中断就会造成处理成本成倍的提高。
如果仅前端的角度来说,通常就是复杂表单的处理。
当表单非常复杂的时候,有的人就希望能够存一个草稿,用于下一次继续操作。
甚至有的时候还希望每选择一项,都能够往后端存储一项。
还希望能够复制上一次的填写结果,能够节省大量的编辑时间。
在原子操作的思维指导下,要满足这些需求,前端缓存是工作量最小的解决方案。能够极大的减少数据库设计的复杂度。
最关键的是,有专业术语做支撑,在方案选择的沟通会议中,具备极强的说服力。
这是我通过原子操作get到的最重要的收获。
四、尝试将一些前端概念,运用于数据库设计
可以通过一个案例跟大家分享。
我们在做数据报表统计时,有很多难题。
例如,我要统计上个月公司有多少人是销售顾问。并且还查看明细。
在常规的数据库设计中,我们要查询当前公司有多少名销售顾问,很简单。
但是要知道一个小时以前有多少名销售顾问,就很难。知道一个月之前的,就更难,误差更大。
原因就是因为实际情况的人事变动会导致结果出现变化。
因此数据报表的常规方案就是抽取数据。每一天留一个备份,或者每一个月留一个备份。
备份结果很简单,但是要备份明细,消耗就很大。
因此我灵机一动,结合前端的一个重要概念,不可变数据,来尝试解决这个难题。
不可变数据是什么意思呢。
例如我有一个数据如下
var state = {
list: [],
loading: true
}
我想要修改该数据中的 loading 为 false。常规的思路很简单
state.loading = false
但是不可变数据的思路是怎么做的呢
// 第一步,基于原有对象,创建一个新对象
var newState = {
list: state.list,
loading: false
}
// 第二步,让 state 等于新对象
state = newState
所以,不可变数据的指导思想是什么呢,就是原有的数据对象,不能被改变,如果想要得到新的结果,就必须创建一份新的数据。
这是在前端应用很广泛的一个概念。
而这个思路,刚好可以解决我们上面提到的问题。
人事变动时,我不修改之前的数据,而是生成一条新的数据。
人员 | 职位 | 状态 | 创建时间 | 改变时间 |
---|---|---|---|---|
张三 | 销售顾问 | false | 2018.10.11 10:00:00 | 2019.10.11 10:00:00 |
张三 | 销售组长 | false | 2019.10.11 10:00:00 | 2020.10.11 10:00:00 |
张三 | 店长 | true | 2018.10.11 10:00:00 | |
李四 | 销售顾问 | true | 2018.10.11 10:00:00 |
在新的数据生成时,只需要把之前的数据状态,调整成 false,这样,我们就可以知道张三当前的职位是什么。
同时,也能够知道张三的历史职位有哪些。
于是,当我们想要统计过去某个时间里,公司有多少销售顾问时,就变得非常简单。这样的方案,比抽取数据更优秀,适应性更强。
当然,这只是一个思路指导,实践场景的数据远没有这么简单。灵活运用可以简化大量工作。
五、解决问题的能力得到更大的提升
通过上面几个总结,大家也能感受到,这一年里我只是一个探索的角色。
我一直在试图寻找更简单的方案解决实际问题。
这也符合是我对架构师的定义:做好基础服务,让其他同事的开发成本更低,开发体验更好,出错的概率更低。
在这个过程中,我也面临了更多新的问题。
而促使我解决问题的能力得到提升的一个关键原因,是因为我已经熟练的掌握了产品经理的思维去看待问题。
这一年里,我深度参与到产品的设计与讨论中,大量的时间都在思考产品的必要性与合理性。
也深刻的理解了工业软件的复杂度。
对线上与线下的大趋势,有了更清晰的认知。
这也促使我看待问题的角度发生了一些变化。
当我拉通UI、前端、后端、数据库设计、产品思维贯通全局来思考自己的项目时,往往会产生新的理解。
解决问题的能力也有了很大的提高。
六、帮助其他很多人取得了进步
帮助他人成长,是自我成长的重要手段。
因为公众号的原因,我维护了两个技术群。
我会不定期的组织免费的公开课。
卡颂卡总的视频课「自顶向下学习 React 源码」就是以我们的一次免费公开课为契机推出的高质量源码课程。
在群里跟大家聊天,帮助大家解决问题。往往会让我对同样的问题产生新的理解。
对于同样的问题,这会促使我去思考如何表达才更加通俗易懂。
在这个思考过程中,很容易让自己对某一个知识点的理解,变得更深刻。
例如,我现在已经能够非常简单的让不懂闭包的同学懂得闭包是怎么回事。很少有人能做到这一点。
当然,我也有机会接触到别人的思考过程,例如原子操作,都是在群里的沟通里学习到的。这也能让我得到极大的提高。
七、输出了一套质量过硬的 JavaScript 进阶课程的文章
几年前我曾经出过一本书,「JavaScript 核心开发解密」。
已经卖完了,也没准备再印刷,大家不用去关注
因为对很多知识和概念,有了更深刻的理解,因此决定在此基础上,结合自己这几年的沉淀,重构一版。
重构的过程也是一个不断思考的过程。
思考如何帮助阅读者能够更加容易的掌握我想要传递的知识。
思考如何在结合书中的知识传递一种学习方法。
思考如何帮助阅读者明白知识体系的重要性,以及如何构建前端知识体系。
在这样一个反复的思考过程中,我自己又有了新的体会和提高。
输出:也是成长的重要手段。
所以很多大厂的招聘信息中,都会写明一条:有写博客习惯的优先考虑。
如果不出意外,未来的某一天这一套文章会以小册的形式与大家见面。
计划一共有72篇文章,目前已经完成了50篇。
八、2021 年目标
1. 学习 Vue 3.0
虽然没有深入学习过,但是通过简单的了解,我觉得 Vue 3.0 应该也是一套颠覆性比较强的解决方案。加上工作中的技术栈更多的是对 React 的研究。而对于 Vue 的掌握还比较片面。
因此希望明年能够从 0 开始重新学习 Vue 3.0。扩充自己的技术栈。
2. 输出一套最好的 React 学习教程
很多人想要学习如何使用 React,但是往往只能学到皮毛,无法将 React 深入运用到项目中。我之前也写了很多关于 React 的文章,但是都零散不成体系。对于基础薄弱的同学帮助有限。
因此明年会写一套 React 学习教程,也算是对自己学习的一个总结。
除了基础理论,还包括有一个完整的实践项目。目前项目已经准备好,就是基于 Taro + React + typescript 开发的小程序「码易」,大家可以搜索简单体验。
与此同时,这套书已经与清华大学出版社签了合同。最迟明年八月可以完成。
3. 锻炼身体,重视健康,重视睡眠
懂的都懂。爱惜身体健康才是王道。
最后,天赋异禀的人不可怕,一直在持续进步的人才可怕。
愿我们都能成为持续进步的人。与大家共勉。
我的公众号:不知非攻,期待您的关注。