React 16

一.特性

fragment

模版支持fragmentstring类型,对应ReactElement数组和字符串

v16.2.0还提供了JSX的fragment支持<></>

error boundary

组件级错误处理,支持捕获子组件树内部异常,UI层的兜底方案

portal

允许组件树与DOM树结构不一致,用于hovercards,tooltips等场景

例如tooltip在DOM结构上target与tip一般是兄弟关系(布局需要),而逻辑上tip是属于target的,是父子关系,portals特性用来处理这种场景

特殊的,事件冒泡经过处理,portals组件的父组件仍然能接到冒泡通知(React 16之前就内置了用来抹平DOM事件冒泡差异的事件系统,这里顺便支持拐弯冒泡 示例

support for custom DOM attributes

之前内置了HTML/SVG属性名白名单,自定义属性会被拦截并忽略掉,React 16去掉了这个限制

去掉该限制有2个原因,其一,这层内置的属性过滤对于非标准的(比如proposal阶段的)新属性和其它库/框架(比如Angular、Polymer)很不友好;其二,bundle里要内置一份体积不小的属性白名单,维护起来还挺麻烦

improved server-side rendering

号称比React 15快3倍(benchmark场景,某业务场景据说1.3倍),做了几件事:

  • 支持stream

  • 构建时去掉了多余的process.env(在node环境访问这个变量很耗时)访问

  • client不再计算checksum,而是尽量复用现有DOM(与贯彻落实DOM node复用的inferno神似,但inferno复用好像遇到一些问题,现在只作为可选项而不是亮点特性提供)

注意:React 16貌似也存在一些DOM node复用的问题

However, it’s dangerous to have missing nodes on the server render as this might cause sibling nodes to be created with incorrect attributes.

P.S.具体要注意哪些危险场景,官方后面可能会给出专门的博客,暂时没有说清楚

reduced file size

React bundle瘦身(重构,扁平化打包策略,并换用rollup),体积小了30%

二.SSR

变化最大的应该是SSR,这次认真实现了一下(之前的SSR像是地上捡的)

1.新API

server侧新增了renderToNodeStream, renderToStaticNodeStream分别对应renderToString, renderToStaticMarkup

client侧新增了hydrate

2.宽松的一致性校验

client侧校验没那么严格了:

  • React 15中,client会对拿到的SSR结果做字符级的一致性校验,一点不匹配就由client重新生成并整个替掉

  • React 16允许属性顺序不一致,而且不给自动修复不一致的属性,而且遇到不匹配的标签结构,会做子树级修改,而不是整个替掉

另外,还去掉了Server HTML结构上的checksum(data-react-checksum)以及id(data-reactid),响应体大小会降低不少:

<!-- react 15 -->
<div data-reactroot="" data-reactid="1"
    data-react-checksum="122239856">
  <!-- react-text: 2 -->This is some <!-- /react-text -->
  <span data-reactid="3">server-generated</span>
  <!-- react-text: 4--> <!-- /react-text -->
  <span data-reactid="5">HTML.</span>
</div>

<!-- react 16 -->
<div data-reactroot="">
  This is some <span>server-generated</span> <span>HTML.</span>
</div>

3.性能优化

默认去掉了多余的process.env.NODE_ENV访问,不需要手动编译去掉

SSR不再创建一次性的virtual DOM,整个快了不少

支持stream,带来的性能优势如下:

  • server边造边发,而不用等待SSR完毕才一次完整发过来,TTFB(the time to first byte)更快

  • client边接边画,而不用等到响应内容完整了才开始绘制,解析、绘制、外部资源加载等时间点都提前了

4.不支持Error Boundary和Portal

React 16 SSR不支持Error BoundaryPortal

服务端子组件渲染出错,不会被Error Boundary拦住。为了stream性能优势,牺牲了Error Boundary:

This is intentional / a known limitation. With streaming rendering it’s impossible to “call back” markup that has already been sent, and we opted to keep renderToString and renderToNodeStream’s output identical.

不仅renderToNodeStream, renderToStaticNodeStream不支持Error Boundary,renderToString也不支持,如上面说的,为了保持输出结果一致,没有维护2套机制

P.S.关于SSR Error Boundary的更多信息,请查看componentDidCatch doesn’t work in React 16’s renderToString

而Portal特性可能造成“回流”,与Error Boundary是一个道理,在stream机制下无法支持(想要往已经发送出去的stream里插入Portal内容,当然不可能)

三.Fiber

全新的核心架构,(花了2年)整个重写了组件渲染机制,最关键的特性是异步渲染(async rendering),实现了可调度渲染(彻底解决mount流程一旦开始就无法中断的问题)

重构过程

这样庞大的一个东西,伤筋动骨的重构执行过程很有意思。简单地说:

  • 不拉新分支去搞。而是通过useFiber特性开关切换,说是为了简化日常维护/冲突处理等

  • 第一步做个骨架(skeleton)出来。支持部分API,再慢慢跑通所有测试用例(所谓TDD,测试驱动开发,最后搞出来2000个case)

  • 工程化辅助手段。包括进度追踪、单测结果追踪(便于发现一次提交修复了什么,干坏了什么,手段非常简单:把tests-failing.txt, tests-passing.txt添加到git追踪)、持续的生产环境验证(所谓dogfooding,从早期到最后单测通过的整个过程都在不断地进行“实战”验证,算是一种看得见的信念

  • 找块合适的业务作为试验田(testbed)。差不多稳定后,通过实际业务来证明ready for production,通过A/B测试出数据,让2亿用户帮忙感受一下,后来再切到全量;同时对内系统全切,扩大验证场景;最后再对React Native应用来个灰度

  • 不直接上新机制。暂时仍然以同步方式执行(虽然Fiber支持异步),准备再憋几个月,好平滑过渡

P.S.具体重构过程见React 16: A look inside an API-compatible rewrite of our frontend UI library

所以暂时不支持异步渲染:

This initial React 16.0 release is mostly focused on compatibility with existing apps. It does not enable asynchronous rendering yet. We will introduce an opt-in to the async mode later during React 16.x. We don’t expect React 16.0 to make your apps significantly faster or slower, but we’d love to know if you see improvements or regressions.

优势

  • 新特性

    组件级错误处理、render返回多组件等之前不太容易实现的特性,重构之后都可以造出来了

  • 体验上的优势

    Fiber并不一定更快,但会更流畅(拆分渲染任务并均衡调度,避免长时间占用主线程),另外还有任务优先级控制,让动画等优先执行

    差异相当明显,见React Stack vs Fiber

参考资料

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code