大多数在前端工作的人以一种或另一种方式遇到了反应。 这是一个JavaScript库,可帮助创建很酷的界面;近年来,它已获得了极大的普及。 但是,很少有人知道它在内部如何工作。
在本系列文章中,我们阅读了代码,并试图找出反应堆背后的包装负责什么,它们的用途以及工作方式。 我们在浏览器中使用的最基本的是react
, react-dom
, events
和react-reconciler
。
我们将按顺序进行操作,今天我们有一篇有关react
软件包的文章。 谁在乎这个包装里的东西是什么?
首先,我们将举一个小例子,在此基础上,我们将考虑该软件包。 我们的小工具将如下所示:
function App() { const [text, changeText] = React.useState('Initial'); return ( <div className="app"> <span>{text}</span> <input type="text" value={text} onInput={(e) => changeText(e.target.value)} /> </div> ); } ReactDOM.render( <App />, document.getElementById('root') ) ;
让我们快速看一下这段代码。 在这里,我们看到了通过React.useState('Initial')
进行的钩子调用,一些JSX和render方法的调用,以在页面上获得所有这些信息。
实际上,正如许多人所知,这不是浏览器处理的最终代码。 在执行之前,例如使用通天塔对其进行转译。 在这种情况下,函数返回的内容将变为以下内容:
return React.createElement( "div", { className: "app" }, React.createElement("span", null, text), React.createElement("input", { type: "text", value: text, onInput: function onInput(e) { return changeText(e.target.value); } }) );
谁愿意做实验,看看您的babel代码变成了babel repl 。
React.createElement
因此,我们对React.createElement()
了很多调用,并有时间查看该函数的作用。 我们将用语言来描述它(或者您也可以查看文件-ReactElement.js )。
首先,它检查我们是否有道具(在代码中,我们传递的带有道具的对象称为config
)。
接下来,检查是否有undefined
key
和ref
undefined
,并保存它们(如果有)。
if (hasValidKey(config)) { key = '' + config.key; }
有趣的一点是config.key
被config.key
转换为字符串,这意味着您可以将任何数据类型作为键进行传递,主要是它实现了.toString()
或.valueOf()
方法并返回特定集唯一的值。
接下来是以下步骤:
- 复制传递给元素的道具;
- 如果不通过道具传递它们,而是将其作为嵌套元素,则在此处添加
children
字段; - 我们从
defaultProps
设置了我们之前未定义的属性的默认值。
准备好所有数据后,我们将调用一个内部函数,该函数创建一个描述组件的对象。 该对象如下所示:
{ // This tag allows us to uniquely identify this as a React Element $$typeof: REACT_ELEMENT_TYPE, // Symbol // Built-in properties that belong on the element type: type, key: key, ref: ref, props: props, // Record the component responsible for creating this element. _owner: owner, }
在这里,我们有$$typeof
属性,它是一个符号,因此无论如何滑倒哪个对象都会失败。
type
属性存储要创建的元素的类型。 在我们的示例中,这将是App()
函数以及行'div'
, 'span'
和'input'
。
key
属性将包含相同的键,因此警告会发送到控制台。
道具将包含我们传递的内容, children
defaultProps
以及defaultProps
中指定的内容。 _owner
属性对于正确使用ref
_owner
必需的。
转换为示例后, React.createElement(App, null)
如下所示:
{ $$typeof: REACT_ELEMENT_TYPE, type: App, key: null, ref: null, props: {}, _owner: null, }
另外,在开发模式下,我们将有一个附加字段,用于显示带有文件名和行的漂亮堆栈:
_source: { fileName: "/Users/appleseed/react-example/src/index.js", lineNumber: 7 }

总结一下我们在上面看到的内容。 react
包充当我们与其他可在我们的应用程序上进一步工作的包之间的翻译器,将我们的调用翻译成可以理解的单词,例如,对协调人。
React.useState
在16.8版中,出现了钩子。 它是什么以及如何使用它,您可以阅读链接 ,但是现在我们看一下react
包中的内容。
实际上,没什么可说的。 本质上,一揽子计划是我们应对内部实体面临的挑战的基础。
因此, useState
只是两行代码:
export function useState<S>(initialState: (() => S) | S) { const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState); }
其余的挂钩看上去几乎相同。 在这里,我们得到当前的调度程序,它是一个对象,包含字段,例如useState
。 此调度程序的更改取决于我们现在是拥有第一个渲染器,还是只想更新组件。
挂钩的实际实现存储在react-reconciler
包中,我们将在以下文章之一中讨论它。
接下来是什么
还有一件事。 阅读本文之后,您可以了解为什么我们总是导入react包,即使我们不直接使用它也是如此。 这是必要的,以便在通过气泡消化我们的jsx之后,我们有了一个React
变量。
react团队的人员(不仅是这个)已经处理了这个工作,现在正在着手替换createElement
。
简而言之,尝试进行解释:存在用两个jsx
和jsxs
替换当前创建元素的方法的jsxs
。 出于以下几个原因,这是必要的:
- 我们在上面讨论了
createElement
工作原理。 它会不断复制道具并将child字段添加到对象,在其中保存我们作为参数传递给函数的子元素(3个参数以及更多)。 现在建议在将jsx
转换为javascript
调用的阶段执行此操作,因为创建元素是一个经常被调用的函数,而且每次运行时都无法随意进行道具修改; - 您
import { jsx } from 'react'
导入React
对象,而仅导入特定的功能(例如, import { jsx } from 'react'
),因此,可以不向程序import { jsx } from 'react'
添加不使用的内容。 另外,您不必每次都解析React
对象的createElement
字段,因为它也不是免费的。 - 上面我们讨论过,当我们从道具中取出
key
并将其进一步向前移动时,我们有一个特殊情况。 现在建议在转换阶段从jsx
获取key
,并将其的第三个参数传递给元素创建函数。
在这里阅读更多。 react
软件包已经具有jsx
和jsxs
。 如果您想解决这个问题,可以克隆react库,在shared
包的ReactFeatureFlags.js
文件enableJSXTransformAPI
标志设置为true
,并在启用新API的情况下编译您的react版本( yarn build
)。
决赛
在此,我将结束有关react
包的今天的故事,而下一次我们将讨论react-dom
包如何使用react
创建的内容,什么方法以及如何实现。
感谢您阅读到底!