大家好,我是Kason。不知道大家有没有注意到,在用React开发的时候,react和react-dom这两个包里面有一个很奇怪的属性:__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:直译就是“内部神秘属性,不要乱用!否则你将被解雇。”为什么会有这样的唬人属性呢?让我们今天谈谈。React项目架构我们习惯在项目中使用如下语句引入Hook:import{useState}from'react';这是否意味着Hook的所有具体实现都在react包中?其实并不是。所有Hooks的具体实现都在ReactFiberHooks.new.js方法中,该方法来自于react-reconciler包。那为什么我们的项目中一直没有主动引入这个包呢?因为react-reconciler中用到的部分被封装到了react-dom中。简单来说,React为了实现跨平台渲染,采用了“一个主模块”+“一个渲染器”的模式。其中,“主模块”是react包,提供了所有常用的方法。“渲染器”根据宿主环境不同而不同,例如:浏览器环境使用ReactDOM/客户端渲染器。SSR使用ReactDOM/服务器渲染器。Native环境使用ReactNative渲染器。除了“宿主环境相关代码”之外,渲染器还有很多通用逻辑(比如Diff算法)。因此,可以认为react-dom是由以下多个包中的“用到的部分”封装的:shared,存放常用方法的包。react-reconciler提供了更新相关的功能,包括Hooks的实现、Diff算法、优先级调度等。react-dom提供宿主环境方法,例如“添加/移动/删除/修改DOM”。以此类推其他包。这就是为什么宿主环境差异很大,但它们都可以通过执行useState和触发视图更新来改变状态。原因是“Hooks的实现”和“在宿主环境中操作视图的方法”被打包到同一个包中。既然“Hooks的实现”被打包进了react-dom(或者宿主环境对应的其他包),那么最终使用的时候如何从react中导出呢?像这样://而不是从'react-dom'导入{useState}from'react';这使用开头提到的变量__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED。内部结构可以认为,当React团队要在react与“宿主环境对应的包”之间共享数据时,会保存在这个神秘的内部变量中。比如上面提到的“Hook的具体实现”。再举个例子,react和react-dom中都使用了object.assign方法的polyfill,但是如果分别导入两个包,然后分别打包,polyfill的代码会在react和react-dom中重复出现-dom在两个包中。为了减少重复代码,React会为object.assign方法引入一个polyfill,然后将其存储在一个神秘的内部变量中。react作为react-dom的peerDependencies,当项目中引入这两个包时,react-dom内部使用的object.assign实际上是来自包constreact=require('react');const{assign}=react.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;常见问题解答了解了神秘内部变量的作用之后,让我们来看看这种实现会导致的问题。假设我们有2个项目:组件库项目A,负责开发组件。业务项目B,依赖A,B安装依赖后,A会出现在B的node_modules中。为了调试方便,我们使用npmlink函数将B中的依赖A从“B的node_modules中的A”改为“组件库项目A”。npmlink后,B中业务代码使用的useState来自“B的reactinthenode_modules”。B中引入的组件库A的组件中使用的useState来自“A的node_modules中的react”。不同的react对应不同的__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,最终对应不同的react-dom。这将导致错误。解决方法是在项目中为react添加一个别名(alias),使得项目中所有使用react的地方都指向同一个react。总结这篇文章,我们了解了神秘的内部变量__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED在react和react-dom中的作用。他能够在这两个包之间传递共享数据。需要注意的一点是,如果你也想通过这种方式在两个包之间共享数据,你需要将一个包设置为另一个包的peerDependencies。否则,在打包时,“共享数据”在两个包中只会存在一份。
