大前端时代以及即将到来的 5G 时代,使得 3D 可视化、音视频直播技术、IM 即时通讯等场景应用变得大有可为。无论是前段时间爆款换脸应用出现,还是近期时间头像加的火爆,似乎都在表明一个点“好事将近”。
不知不觉,九月已经过去,由于这个月工作上,被 C++ 折磨得很难受,而且其他时间都在学习,所以没有时间写文章,好在技术提升很大。今天准备好好谈一谈重型应用的架构以及技术选型,为接下来我的正式架构设计做个铺垫。
例如微信、QQ、Telegram 以及一些工具类的应用。
说到这些大家肯定觉得,为什么不说是游戏?当然游戏也算,可是我相信做出 1000 万人每天都在用的产品是大家的梦想,起码能吹一辈子吧。
工具类的东西其实是最难做的,比如 vsCode , Excel ,PhotoShop 这些。这也是为什么这么多年出现成功的工具类产品这么少。这里不得不提到 vsCode,它其实就是用 ELectron 开发,基于 TypeScript。当然肯定使用了不少 C++ 插件,说到这里,留下伤心的眼泪,最近也是被折磨得很难受。
国内移动端开发人员,在我看来已经人目前已经够多了,如果说你现在不会 React-Native 和 Flutter,我也不建议你去深入学习,特别是 Flutter。
为什么要这么说?
React-Native 刚出来的时候,坑多吧。现在 Flutter 也是,可是当你从 RN 最初的版本踩坑踩到现在,之前踩的坑大都没有意义。(说这些话想过被喷,但是… 此处省略一万字,建议去了解下原理和基本使用,不要耗费太多时间)
一个技术你去使用,并不是它多流行,只要它足够流行。— 来自某位国内大佬
技术的学习,应该多往底层钻研,如果你走错了路,钻错了方向,浪费了时间得不偿失,我之前有说过,前端最核心的几个基础知识点,应用层的东西从来不会很难。前提你的基础足够扎实。
既然说了移动端没有合适的重型跨平台应用开发框架,那么只有 PC 端了。还有多少小伙伴在 PC 端开发呢?
我不止一次提到过这个框架,我觉得它真是一个非常棒的框架,为什么这么说呢?
你甚至可以看成 Electron 给 web 网页套上一层壳,你可以在主进程写你的 Node.js 去实现功能,渲染进程你怎么写怎么写,还可以呼叫封装好的原生接口。遇到特别复杂的需求,用 C++ 插件去实现吧。
最终打包出来的安装包跟正常的桌面应用是一样的,正常安装卸载等,都已经封装好。
目前 GitHub 上已经有 77.2K 的 star。
应用层面的东西,大都不会太难,Electron 的文档已经非常全面,基于它出现了很多复杂,而且成功的工具类重型应用。我相信它。
WhatsApp 也是基于它,国外还有一些很 NB 的应用也是。这里不做过多阐述,可以确定它是一个成熟而且成功的框架。
可能很多人看到这里又要说标题党了,别急,下面来干货。
一个好的开发,它一定能懂一些产品,甚至测试,当然他也应该会炒河粉,35 岁以后好维持生活。
我们今天举一个例子,IM,即时通讯,Telegram,20 万人超级群端到端加密的核心卖点产品。
电报 Telegram:
现在回答上面三个问题:- 项目本身的最重要功能是什么?答案:即时通讯,信息的收发。
核心竞争力,往往代表了这个应用产物的技术最难点,因为谁都能做,那么就不是核心竞争力了
所以我们其他的都忽略,关注第三点,开始进行技术选型,架构。
要想写好这个架构,我觉得你首先在自身的擅长领域不能有太多的黑盒过程。例如框架源码,库原理实现,浏览器和 Node.js 的事件 EventLopp 以及他们的缺陷,你要熟知在心。因为像这种应用,一个小方向可能就会掏空你的技术栈,耗尽你的精力,例如音视频、图片处理等。
单线程的 Node.js 以及 js 主解析引擎,让我们又爱又恨。
这款应用的核心竞争力,是 20 万人超级群,那么数据量很大,大批量渲染压力和频繁加解密计算耗时、频繁数据库写入压力都是不可避免的,那么我们的 Node.js 擅长异步非阻塞,以及前端渲染进程的异步就显得尤为重要。
前端架构整体的核心除了技术选型以及大体框架外,就是任务调度。
这里的任务调度分两种:
我之前说过,应用层的东西都不难,只要你基础足够扎实,能手写出简单的框架,以及库。你绝对能非常轻松应对前端百分 80 以上的性能问题和需求,技术最终都是相似的。
上面只是说了别人的一些比较简单的优化方案,下面才是开始我们自己的渲染任务调度:
回到我们的 Telegram 架构设计方案。
渲染任务架构过程需要着重考虑的几个问题:
今天在学习一篇小册,里面有一句话引起了我的共鸣,在计算机的世界,如果有解决不了的问题,那就加一个中间层,如果还不行,那就加两个。 ——后面这句是我加的。
这个是我自己编写的 React, mini-react 源码地址点击这里。
PReact 源码中,是将需要更新的组件放入队列中,然后一次清空,伪代码:
if (setStateQueue.length === 0) {
// 清空队列的办法是异步执行
defer(flush);
}
setStateQueue.push({
stateChange,
component
});
function defer(fn) {
//requestIdleCallback 的兼容性不好,对于用户交互频繁多次合并更新来说,requestAnimation 更有及时性高优先级,requestIdleCallback 则适合处理可以延迟渲染的任务~
// if (window.requestIdleCallback) {
// console.log('requestIdleCallback');
// return requestIdleCallback(fn);
// }
// 高优先级任务
return requestAnimationFrame(fn);
}
while ((component = renderQueue.shift())) {
renderComponent(component);
}
上面这段代码其实很重要,核心思想就是:
每当进入这个函数,如果发现队列队列里没有任务就去执行 defer 函数。
defer 函数执行是异步,此时 defer 下面的 setStateQueue 已经被 push 了进去数据,这样达到一帧完成一次渲染任务调度。
当然上面仅仅一个小的任务调度,这个必须要了解,才能往下看
requestAnimationFrame 和 requestIdleCallback 使用:
你需要深入了解 React 框架的 Fiber 架构,这块尤其重要,是性能优化,任务调度的基础,上面有提到,React 在每次 diff 对比阶段,将任务分割成若干个小任务,此时如果有 RAF 和 RID 的任务,就要考虑去执行了。
RAF 的任务会每次在下一次小任务前执行。
RID 的任务只有在下一次小人物前,有空余时间才会执行,所以它不一定会执行。(特别高频必须执行的任务)
Fiber 架构配合单个任务分割已经介绍完毕,下面出思维导图出整体的任务调度。
核心的两点:
理清思路:
这里特别提示,为什么我一直强调不要使用定时器,一旦应用变得很复杂,如果任务调度不合理,定时器里的代码是要很久很久才能执行的。当然,只有重型应用会这样。
渲染任务调度这块,主要是微任务,RAF,RID 分片渲染以及同步代码,队列调度等手段。
核心思想跟渲染进程大概一致:1. 尽量释放主进程,保持空闲,让用户的操作即时得到反馈,因为很多操作会调用主进程的接口。 2. 异步调度任务,写入数据库异步,解密计算可以使用 nextTick 等方式去调度添加队列。
这里提到,任务调度的核心一点就是,频繁触发的任务必须加入队列,异步清空,否则像解密这种同步计算耗时,一旦被频繁触发就会引起阻塞。即使交给了其他进程,也要做队列。
本文转载自微信公众号:前端巅峰
领取专属 10元无门槛券
私享最新 技术干货