编程随想 · 2021 年 08 月 07 日 0

周拱一卒(1)

一篇关于 React 性能分析的文章 Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior
总结了导致 React 组件 re-render 的各种原因,并给出了优化方案,非常值得一读。

实际上,这些问题,在学习 React 的时候就应该自己总结出来,可以在写代码的时候少走许多弯路

摘录其全文总结部分:
– React always recursively renders components by default, so when a parent renders, its children will render
– Rendering by itself is fine – it’s how React knows what DOM changes are needed
– But, rendering takes time, and “wasted renders” where the UI output didn’t change can add up
– It’s okay to pass down new references like callback functions and objects most of the time
– APIs like React.memo() can skip unnecessary renders if props haven’t changed
– But if you always pass new references down as props, React.memo() can never skip a render, so you may need to memoize those values
– Context makes values accessible to any deeply nested component that is interested
– Context providers compare their value by reference to know if it’s changed
– A new context values does force all nested consumers to re-render
– But, many times the child would have re-rendered anyway due to the normal parent->child render cascade process
– So you probably want to wrap the child of a context provider in React.memo(), or use {props.children}, so that the whole tree doesn’t render all the time when you update the context value
– When a child component is rendered based on a new context value, React keeps cascading renders down from there too
– React-Redux uses subscriptions to the Redux store to check for updates, instead of passing store state values by context
– Those subscriptions run on every Redux store update, so they need to be as fast as possible
– React-Redux does a lot of work to ensure that only components whose data changed are forced to re-render
– connect acts like React.memo(), so having lots of connected components can minimize the total number of components that render at a time
– useSelector is a hook, so it can’t stop renders caused by parent components. An app that only has useSelector everywhere should probably add – React.memo() to some components to help avoid renders from cascading all the time.

今天与同事讨论给每个可能变化的 Provider 的 children 加一层 memo,可以有效防止从消费 Context 的组件往上的其他组件 re-render,同事说最好是不要让 Provider value 发生变化,开始还很纳闷,value 不变化,如何用其跨层级共享变化的值呢。

猛然想起 Recoil 这个流派,不正是类似的实现方式么:

  1. Provider 只是个对象容器,持有不变的对象,或者叫事件中心
  2. useValue 时订阅数据变化,并 update 内部 state 从而触发对应组件 re-render
  3. setValue 触发事件更新,通知订阅组件

数据变化过程中,通知直达需要更新的组件。


来源
如何高效地将前端 JavaScript 生成的 GB 级数据保存到硬盘?

方案一:放进一个非常大的 ArrayBuffer,包成 Blob,然后生成 URI。
弊端:所有数据存在内存中,十分占内存。并且通常有 2GB 上限(手机更小)。

方案二:利用 FileSystem API 写到浏览器的沙盒存储中,再生成 URI(MEGA 网盘所使用的方案)。
弊端:FileSystem API 是 Chrome 独有的,已经废弃了。并且 MDN 也删掉了所有写入相关的 API 文档。

方案三:File Sysem Access
弊端:今年才出的草案,还没有浏览器实现。

最终方案:StreamSaver.js

大致原理:
1. 在 iframe 里起了一个 Service Worker 模拟出一个文件下载请求
2. 父页面通过 postMessage 将数据分片发给 worker

优点:
1. 不占内存
2. 实时写入,没有产生「另存」交互
3. 既支持已知长度的下载(并显示进度条),也支持无限长度下载

缺点:
1. 不支持随机访问(也就是只能从头写到为,不能回过头更改前面已经下载的数据)