如何评价React v16.0?

关注者
640
被浏览
42381

18 个回答

北京时间2017年9月27日,Facebook 官方发布了 React v16.0。相较于之前的 v15.x 版本,v16 版本在异常处理,服务端渲染和核心架构方面(引入 Fiber)都有了重大更新。

让我们先来浏览一下核心的更新点:

  1. render 函数支持返回数组和字符串:终于不需要再将多个同级元素包裹在一个冗余的 DOM 元素中了,但每个同级元素还是需要唯一的 key 值方便 react 进行更新。而且在未来,react 可能还会提供一个特殊的 jsx 片段来支持无 key 值的 DOM 元素。
  2. 更好的异常处理:在老版本的 react 中,某个组件在 render 阶段的运行错误可能会 break 掉整个应用,而且抛出的异常信息含义也非常模糊,难以确定错误的发生位置。在 v16.0 中,如果某个组件在执行 render 或其他生命周期函数时出错,整个组件将被从根节点上移除掉,方便开发者快速定位异常组件。在定位到异常组件后,开发者可以为该组件添加 componentDidCatch 方法,并在这个方法中为组件定义一个备用视图用于渲染异常状态下的组件。当然,在这个新的生命周期函数中,开发者也可以获得更加有帮助的错误信息进行 debug。这被称作组件的错误边界,大家可以理解为组件层面的 try catch 声明。
  3. 新的组件类型 portals:ReactDOM.createPortal(child, container) 可以将子组件直接渲染到当前容器组件 DOM 结构之外的任意 DOM 节点中,这将使得开发对话框,浮层,提示信息等需要打破当前 DOM 结构的组件更为方便。
  4. 更好的服务端渲染:与之前 renderToString 方法不同,新版本提供的 renderToNodeStream 将返回 Readable,可以持续产生字节流(a stream of bytes)并在下一部分的 document 生成之前将之前已生成的部分 document 传回给客户端。通常来讲,新的服务端渲染将比老的快3倍以上。在 document 到达客户端之后,新版本的 react 也将不会再去将客户端的初次渲染结果与服务端的渲染结果进行比较,而是尽可能地去重用相同的 DOM 元素。
  5. 支持自定义 DOM 元素:新版本将不会再抛出不支持的 DOM 元素错误,而是将所有开发者自定义的 DOM 元素都传递到相应的 DOM 节点上。
  6. 更小的打包大小:总体体积减少 30%
    1. react is 5.3 kb (2.2 kb gzipped), 老版本 20.7 kb (6.9 kb gzipped)
    2. react-dom is 103.7 kb (32.6 kb gzipped), 老版本 141 kb (42.9 kb gzipped)
    3. react + react-dom is 109 kb (34.8 kb gzipped), 老版本 161.7 kb (49.8 kb gzipped)
  7. MIT 许可:除了最新的 16.0 版本外,Facebook 还发布了使用 MIT 许可的 15.6.2 版本,以方便无法立刻升级的使用者。
  8. 新的核心架构 Fiber:新版本将使用 Fiber 作为底层架构。正是得益于 FIber,上述提到的支持返回数据及错误边界等功能才变得可能。Fiber 相较于之前最大的不同是它可以支持异步渲染(async rendering),这意味着 React 可以在更细的粒度上控制组件的绘制过程,从最终的用户体验来讲,用户可以体验到更流畅交互及动画体验。而因为异步渲染涉及到 React 的方方面面甚至未来,在 16.0 版本中 React 还暂时没有启用,并将在未来几个月陆续推出。

===

过去一段时间关于专利问题的讨论让 React 着实失去了一部分忠实的支持者,也让更多的前端开发者开始思考前端开发的地基及本质。大家探讨的问题也从选择哪一个前端框架转移到了

  1. 前端框架是否应当成为前端开发的最底层依赖,vanilla javascript 是否应该被开发者彻底遗忘?
  2. 使用 vanilla javascript 来做组件库,并在上层使用各个框架来进行封装以达到无框架倾向是否是一个好的选择?
  3. 站在 React 的基础上,一个更优秀的前端框架到底应该具备哪些功能及特点?

对于前端开发的未来,React v16.0 的这次更新应该已经给出了一个明确的信号,那就是从微观上以更细的粒度去控制整个渲染过程,以达到流畅的交互与动画效果;从宏观上让渲染本身与平台解耦,使用相同的核心算法及架构去适配不同平台的接口,以达到最终横跨所有终端的目的。

对于未来永远都没有一个正确答案,而 React 的探索值得所有人尊重。

说个很多人没有提到的——对大量的React-like库的影响。像inferno/preact/anu/react-lite,他们都在v16上面临了一个重大拐点。


正如很多文章提到的,这次换Fiber Reconciler带来了全新的特性,比如render函数可以返回数组、字符串、数字。这在Stack Reconciler上是很难做到的,而上述的react-like库,无一例外都是使用的Stack Reconciler。


现在大家刚开始从v15迁移,无论是React@16 还是上述React-like库们都保持了对v15的高度兼容,因此暂时没什么问题。


等到Fiber的特性慢慢推广,甚至开放出更多可能性(比如异步模式)的时候,上述React-like们未来如何跟进就成了问题。


可能有人要说,React-like们重写成fiber架构就行了呗。


这么说可能忽视了Fiber这东西的复杂度,以前StackReconciler其实只是简单的递归函数调用,不到300行代码就能演示个大概,而现在的Fiber,React团队号称搞了一年,虽然已经正式发布,但目前还是只开放了同步模式,异步渲染的调度到现在也还没完全搞定,复杂度完全不在一个数量级


个人开发者自己从头撸React-like的时代,从v16开始可能要划上句号了


不是说不可能,我相信还是有很多牛人"能"自己撸的,但是这种复杂度高度集中,重写一遍很难带来好处的东西,重复造轮子是不划算的。


写React-like库,最大的优化点其实是renderer——即ReactDOM的部分,无论是去掉浏览器兼容代码还是事件系统,说白了都是和DOM相关。把整个React都重写一遍,既有历史原因(以前ReactDOM只是个空壳子),也因为Stack Reconciler并不复杂且无从复用,只能一锅端。


而在Fiber中,不仅React和Renderer高度解耦,Reconciler也是纯调度、和宿主环境无关、可复用的。

在Fiber中一个Renderer是这样初始化的:


ReactDOMRenderer = ReactFiberReconciler({
  getRootHostContext() {},
  createTextInstance() {},
  //...
  appendChild: appendChild,
  appendChildToContainer: appendChild,
  insertBefore: insertBefore,
  insertInContainerBefore: insertBefore,
  removeChild: removeChild,
  removeChildFromContainer: removeChild,
  //...
});


把宿主环境相关的操作函数以配置的形式注入到FiberReconciler中即可。具体可参考官方的ReactNoopEntryReactDOMFiberEntry

也就是说,第三方完全可以使用Fiber的官方Reconciler来构建自己的Renderer——不仅是优化DOM Renderer,也可以用到其它宿主环境,比如渲染一个docx。

这里推荐:nitin42/Making-a-custom-React-renderer ,感兴趣的可以看下。


简单的总结,以后写react-like会非常困难,但只写Renderer的话,却是大大简化了,第三方的Renderer生态,会变得更加繁荣。


这也应该会是react-like们下一步的发展方向。


最后:目前v16正式发布版没有提供ReactFiberReconciler(之前的alpha版可以通过react-dom/lib访问到),有相应的issue和讨论,最终可能会以单独的create-react-renderer的方式出现,感兴趣的可以先用alpha练手。