当前期刊数: 224
继前一篇 精读《Records & Tuples 提案》,已经有人在思考这个提案可以帮助 React 解决哪些问题了,比如这篇 Records & Tuples for React,就提到了许多 React 痛点可以被解决。
其实我比较担忧浏览器是否能将 Records & Tuples 性能优化得足够好,这将是它能否大规模应用,或者说我们是否放心把问题交给它解决的最关键因素。本文基于浏览器可以完美优化其性能的前提,一切看起来都挺美好,我们不妨基于这个假设,看看 Records & Tuples 提案能解决哪些问题吧!
概述
Records & Tuples Proposal 提案在上一篇精读已经介绍过了,不熟悉可以先去看一下提案语法。
保证不可变性
虽然现在 React 也能用 Immutable 思想开发,但大部分情况无法保证安全性,比如:
1 |
|
归根结底,我们不会总使用 freeze
来冻结对象,大部分情况下需要人为保证引用不被修改,其中的潜在风险依然存在。但使用 Record 表示状态,无论 TS 还是 JS 都会报错,立刻阻止问题扩散。
部分代替 useMemo
比如下面的例子,为了保障 apiFilters
引用不变,需要对其 useMemo
:
1 |
|
但 Record 模式不需要 memo,因为 js 引擎会帮你做类似的事情:
1 |
|
用在 useEffect
这段写的很啰嗦,其实和代替 useMemo 差不多,即:
1 |
|
你可以把 apiFilters
当做一个引用稳定的原始对象看待,如果它确实变化了,那一定是值改变了,所以才会引发取数。如果把上面的 ##
号去掉,每次组件刷新都会取数,而实际上都是多余的。
用在 props 属性
可以更方便定义不可变 props 了,而不需要提前 useMemo:
1 |
|
将取数结果转化为 Record
这个目前还真做不到,除非用性能非常差的 JSON.stringify
或 deepEqual
,用法如下:
1 |
|
即利用 Record 提案的 JSON.parseImmutable
将后端返回值也转化为 Record,这样即便重新查询,但如果返回结果完全不变,也不会导致重渲染,或者局部变化也只会导致局部重渲染,而目前我们只能放任这种情况下全量重渲染。
然而这对浏览器实现 Record 的新能优化提出了非常严苛的要求,因为假设后端返回的数据有几十 MB,我们不知道这种内置 API 会导致多少的额外开销。
假设浏览器使用非常 Magic 的办法做到了几乎零开销,那么我们应该在任何时候都用 JSON.parseImmutable
解析而不是 JSON.parse
。
生成查询参数
也是利用了 parseImmutable
方法,让前端可以精确发送请求,而不是每次 qs.parse
生成一个新引用就发一次请求:
1 |
|
还提到一个有趣的点,即到时候配套工具库可能提供类似 qs.parseRecord(search)
的方法把 JSON.parseImmutable
包装掉,也就是这些生态库想要 “无缝” 接入 Record 提案其实需要做一些 API 改造。
避免循环产生的新引用
即便原始对象引用不变,但我们写几行代码随便 .filter
一下引用就变了,而且无论返回结果是否变化,引用都一定会改变:
1 |
|
要避免这个问题就必须 useMemo
,但在 Record 提案下不需要:
1 |
|
作为 React key
这个想法更有趣,如果 Record 提案保证了引用严格不可变,那我们完全可以拿 item
本身作为 key
,而不需要任何其他手段,这样维护成本会大大降低。
1 |
|
当然这依然建立在浏览器非常高效实现 Record 的前提,假设浏览器采用 deepEqual
作为初稿实现这个规范,那么上面这坨代码可能导致本来不卡的页面直接崩溃退出。
TS 支持
也许到时候 ts 会支持如下方式定义不可变变量:
1 |
|
那我们就可以真的保证 usersFilters
是不可变的了。因为在目前阶段,编译时 ts 是完全无法保障变量引用是否会变化。
优化 css-in-js
采用 Record 与普通 object 作为 css 属性,对 css-in-js 的区别是什么?
1 |
|
由于 css-in-js 框架对新的引用会生成新 className,所以如果不主动保障引用不可变,会导致渲染时 className 一直变化,不仅影响调试也影响性能,而 Record 可以避免这个担忧。
精读
总结下来,其实 Record 提案并不是解决之前无法解决的问题,而是用更简洁的原生语法解决了复杂逻辑才能解决的问题。这带来的优势主要在于 “不容易写出问题代码了”,或者让 Immutable 在 js 语言的上手成本更低了。
现在看下来这个规范有个严重担忧点就是性能,而 stage2 并没有对浏览器实现性能提出要求,而是给了一些建议,并在 stage4 之前给出具体性能优化建议方案。
其中还是提到了一些具体做法,包括快速判断真假,即对数据结构操作时的优化。
快速判真可以采用类似 hash-cons 快速判断结构相等,可能是将一些关键判断信息存在 hash 表中,进而不需要真的对结构进行递归判断。
快速判假可以通过维护散列表快速判断,或者我觉得也可以用上数据结构一些经典算法,比如布隆过滤器,就是用在高效快速判否场景的。
Record 降低了哪些心智负担
其实如果应用开发都是 hello world 复杂度,那其实 React 也可以很好的契合 immutable,比如我们给 React 组件传递的 props 都是 boolean、string 或 number:
1 |
|
比如上面的例子,完全不用关心引用会变化,因为我们用的原始类型本身引用就不可能变化,比如 18
不可能突变成 19
,如果子组件真的想要 19
,那一定只能创建一个新的,总之就是没办法改变我们传递的原始类型。
如果我们永远在这种环境下开发,那 React 结合 immutable 会非常美妙。但好景不长,我们总是要面对对象、数组的场景,然而这些类型在 js 语法里不属于原始类型,我们了解到还有 “引用” 这样一种说法,两个值不一样对象可能是 ===
全等的。
可以认为,Record 就是把这个顾虑从语法层面消除了,即 ##{ a: 1 }
也可以看作像 18
,19
一样的数字,不可能有人改变它,所以从语法层面你就会像对 19
这个数字一样放心 ##{ a: 1 }
不会被改变。
当然这个提案面临的最大问题就是 “如何将拥有子结构的类型看作原始类型”,也许 JS 引擎将它看作一种特别的字符串更贴合其原理,但难点是这又违背了整个语言体系对子结构的默认认知,Box 装箱语法尤其别扭。
总结
看了这篇文章的畅想,React 与 Records & Tulpes 结合的一定会很好,但前提是浏览器对其性能优化必须与 “引用对比” 大致相同才可以,这也是较为少见,对性能要求如此苛刻的特性,因为如果没有性能的加持,其便捷性将毫无意义。
讨论地址是:精读《Records & Tuples for React》· Issue ##385 · dt-fe/weekly
如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
关注 前端精读微信公众号
版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)