Preact 对 React 有多兼容?

Preact 对 React有多兼容?

背景

最近收到部门通知,需要求替换 Facebook 旗下所有开源项目。而作为 Facebook 最大的开源项目之一的 React 在微信支付一些内部项目有应用到,虽然 React 的开源协议已经替换为 MIT 协议了,但替换 React 还是被提到了日程。

鉴于在微信支付内部用到 React 的项目数量比较多,我们首选的并不是用 Vue 等框架来重构这些项目,而是选用 API 与 React 一样的无缝替代方案来替代 React。前期我们初步调研了 Github 上 Star 数比较多的两个方案 Preact 和 InfernoJS,在兼容至 IE8 的目标上,Preact 和 InfernoJS 两个方案都不能满足,具体如下:

不兼容特性/兼容库preactinfernojs
Getter/Setter需改造不需要
addEventListener不需要需改造
Dom 的 onload 事件不需要需改造
DOMNodeInserted不需要需改造
DOMNodeRemoved不需要需改造
DOMSubtreeModified不需要需改造
DOMCharacterDataModified不需要需改造
DOMAttributeNameChanged不需要需改造
DOMAttrModified不需要需改造
readystatechange不需要需改造
EventListener 原型绑定事件形式不需要需改造

由于 Preact 源码相对简单且不兼容特性较少,改造初步估计较少,所以我们首先研究 Preact 对 React 的兼容性如何。

初步尝试

我们首先在尝试在我们 React+Redux+ReactRouter 技术栈(请看这个 DEMO)和两三个依赖较少的内部项目上尝试用 Preact 替代 React,发现没有问题。但是在有些用了 antd 的库的项目的某些组件的兼容上有问题,交互不能正常完成。为了全面采用 Preact, 我们需要对 Preact 对 React 的兼容性进行全面的了解,因而我们想到了用 Preact 跑 React 15-stable 分支的 1347 个测试用例,看测试结果如何来判定 Preact 的兼容性

Preact 跑 React 的测试用例

首先感谢 nelsonlu 使这个成为可能,我们首先对测试源码中的依赖进行替换,以下是我们的替换对应关系:

目标
Reactpreact-compat
ReactDOMpreact-compat
ReactTestUtilspreact-test-utils
ReactDOMServerpreact-compat/server
ReactFragmentpreact-compat/lib/ReactFragment
ReactMountpreact-compat/lib/ReactMount
ReactPerfpreact-compat/lib/ReactPerf
updatepreact-compat/lib/update
create-react-class*preact-compat/lib/create-react-class*
renderSubtreeIntoContainerrequire(‘preact-compat’).unstable_renderSubtreeIntoContainer

经过替换之后,我们运行 React 的测试用例,结果如下:

Test Suites: 75 failed, 1 skipped, 30 passed, 105 of 106 total
Tests:       769 failed, 6 skipped, 572 passed, 1347 total

还是结果发现有很多测试用例运行失败的,我们尝试对失败的测试用例进行分类:

理由数量
没有按预期 warning 相关152
缺乏 create-react-class/factory 依赖26
在 dev 模式下,组件没有 _debugID78
没有 _rootNodeID,组件内部实现相关18
没有 _wrapperState,ReactDOMInput 的内部实现相关11
没有 _currentElement, ReactCompositeComponent 的内部实现相关39
没有 _renderedComponent, ReactCompositeComponent 的内部是想相关27
ReactUpdate 内部依赖相关19
内部事件实现相关6

以上这些可以说是内部实现相关的,对我们的兼容性分析无关,我们先忽略这部分测试用例,对剩下的 421 个测试用例进行分析,这些测试用例应该就反映了 Preact 的不兼容性。经过再一轮分析之后,我们发现了以下一些不通过的测试用例:

|理由|数量| |—–|——| |边界情况未处理|60| |Server Rendering 相关|17| |preact-test-util 没有 findRenderedComponentWithType 方法|11| |preact-test-util 没有 SimulateNative 对象|35| |ART渲染相关|7| |REACT NATIVE 相关|10| |preact的ReactPerf是空实现|15|  以上情况应该不构成致命性的兼容性问题,同时我们发现包括但不仅限于以下一些严重程度比较高的问题:

  1. refs 对象在重渲染的时候没有更行
  2. 用 createFactory 创建元素时 ref 丢失
  3. Element.getAttribute 返回 null
  4. 重渲染更改 defaultValue 时候不应该更改 input 的 value
  5. 当 container 重渲染的时候 textarea 的 value 为 null
  6. textarea 的 defaultValue 没有设置
  7. 组件未加载的时候 findDOMNode 应该是 Null 但却返回对应对象
  8. 返回的 element 不是 immutable 的
  9. 绑定 input 的值为 0.0 时,input 不受控
  10. preact 的 Element 在创建后 props 仍可写

发现包括但不仅限于以下未评估严重性的问题:

  1. 元素的 key 不强制为 String
  2. children 永远是数组
  3. React 不允许从 props 中获取 key 和 ref,而 preact 可以
  4. preact 的 ELement 的 prototype 为 VNode, React 的 ELement 的 prototype 为 Object
  5. preact 的 isValidElement 不能识别 Element 经过 JSON.stringify 后再 parse 的对象,preact 的isValidElement 使用 prototype 来识别是否是 Element。

发现缺少的 API:

  1. 缺少 ReactDOM.unstable_batchUpdate 方法
  2. 缺少 React.createMixin
  3. 缺少 __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED

由于分析用例的工作量巨大,截止发文章时刻,还未完成对所有用例的分析。

结论和建议

通过对结果分析,发现 Preact 对 React 还是有不少兼容性问题,初步估计大概至少 1/5 用例反映了 Preact 对 React 的兼容性问题。对于这个结果其实也在我们的预料之内,因为 Preact 的代码量,社区规模和应用范围和 React 都不在一个数量级上。尽管如此,用 Preact 替代 React 还是能够在我们尝试的项目中成功替换,跑起来没有问题(替换成功可能得益于 1. 我们的项目依赖库比较少。 2. 我们项目推荐使用 PureComponent 实现视图层,用 Redux 管理数据并驱动视图层展现,所以涉及 React 的功能相对比较集中和统一)。

所以这些失败的用例不一定跟你们的项目代码相关,我们推荐还是使用 Preact 尝试替换你们的项目看看是否有问题。

Back