React 实际上是如何工作的?

Posted by Bibooo on 07-27,2022
const App = () =>{
 return (
  <div>
  App component
  </div>
 );
};

它是一个 React 组件,它使用 JSX 返回一些 React 元素,但是对于 React ,返回值是一个看起来像这样的对象。

console.log(App())

{$$typeof: Symbol(react.element), type: 'div', key: null, ref: null, props: {…}, …}
$$typeof: Symbol(react.element)
key: null
props: {children: 'App component'}
ref: null
type: "div"
_owner: null
_store: {validated: false}
_self: null
_source: null
[[Prototype]]: Object

首先 JSX被转换许多 React.createElement 函数调用,然后,它们中的每一个都返回适当的对象。

在使用 JSX时总是必须导入 React 的原因

什么是 React 组件

React 组件是一个输出元素树的类或函数,如果是函数,则输出为函数的返回值,如果是类,则输出为 render 方法的返回值。

React 组件不仅有一个输出,它们还有一个输入,我们都熟悉 props

console.log()

$$typeof: Symbol(react.element), key: null, ref: null, props: {…}, type: ƒ, …}
$$typeof: Symbol(react.element)
key: null
props: {}
ref: null
type: ƒ App()
_owner: null
_store: {validated: false}
_self: null
_source: null
[[Prototype]]: Object
这是打印日志

当我们在 JSX中渲染我们的组件时,React 实际上是在幕后调用它。如果它是一个函数, React 直接使用分配的 props 调用它,如果它是一个类 React 创建一个新的类实例并调用它的渲染方法

所以 React 元素不仅可以描述一个 DOM 节点,它们还可以描述一个组件更准确地说是一个组件实例。

当一个 React 元素描述一个组件时,React 会跟踪它并创建一个组件实例

React 将这个元素树保存在内存中,它被称为虚拟 DOM 接下来要做的是将虚拟DOM 与真实 DOM 同步

每个实例都有状态和生命周期

我们知道 React 所做的只是创建一个元素树,这个过程非常快,因为正如我们所见,元素只是普通的 JavaScript对象,我们基本上可以在瞬间创建数千个对象,当我们调用 render 方法时,所有这些都在发生 React 通过从最顶部开始并递归地移动到最底部来生成这个元素树,如果它遇到一个组件,它会调用它并继续下降到返回的React 元素

总结

React 创建一个元素树,过程非常快,元素都只是普通的 js 对象,当调用 render 方法时 React 会从上而下并递归来生成这个元素树,如果遇到组件会继续下降到返回的 React 元素


**在初始渲染期间,除了插入完整的树之外,没有其他方法 **

如果状态变化导致不同的返回值和不同的元素怎么办?

React 再次非常快速地生成了一个新的元素树,我们现在有两棵树-旧的和新的。现在,它必须再次将包含新树的虚拟 DOM 与真实DON 同步。

重新渲染整个树会非常低效,React 采用了一种称为 Diffing 算法来做到这一点

Diffing目标是区分树

1.首先,它假设两种不同类型的元素会产生两颗完全不同的树。(true)

2.React 假设当我们有一个子元素列表并对其进行更改时,我们将提供密钥并且我们将以正确的方式提供它(key)

<div>                     <div>
 <h1>title1</h1>    ==>      <h2>title2</h2>
<div>                     <div>

<div>                     <div>
 <Component1/>      ==>      <Component2/>
</div>                    </div>

假设我们在同一个位置有两个不同类型的元素。在这种情况下,React 将只是从头开始构建一棵新树,这意味着旧树中的所有组件实例连同它们的当前状态一起被销毁 组件卸载

<div className='first'>              <div>
                          ==>           <h2>title2</h2>
<div>                                <div>

<Component someProp="first"          <Component
                          ==>          someProp="second"
/>                                  />

如果我们在同一位置有两个具有相同类型但具有不同属性的元素怎么办?

React 只是更新这些属性实例保持不变并保存状态

在 ul li 列表,我们使用键,告诉 React 哪些元素是相同的,哪些是新的,再一次,这就是我们不能使用索引作为键的原因。

React DOM 和 React Native

我们将这些包称为渲染器,React 本身只是一个库,它允许我们定义组件和元素,它也完成了差异部分。它不是特定与平台,也不负责渲染。大多数实际实现都存在与渲染器中。它们必须生成元素树并将其插入到必须插入的任何位置,这就是为什么它们是不同的包装。当我们使用 React 进行 web开发时,React 基本上甚至不知道元素最终会出现在 Web 浏览器中,因此 React 与任何渲染器兼容。

在 web开发方面,我们通常只导入一次 React DOM 并调用它的 render方法。

那么大多数实现如何才能存在与渲染器包中呢?理解这一点的关键是要知道 React 与渲染器通信。

React 有很多方法可以做到这一点,我们先探索其中一种: setState函数

setState 讲究是如何工作的呢?

setState 是类组件的一部分,答案是每个渲染器在创建的类上设置一个特殊字段该字段称为更新程序。当我们在 web 上使用 React 时,是 React DOM 在创建类的实例后立即设置此字段.

const instance = new Component();
instance.props = props;
instance.updater = ReactDOMupdater;

首先创建一个类组件的一个实例,然后,我们可以看到 updater 字段确实被设置为 React DOM的一部分

然后我们每当调用 setState 时,都会调用一个于此类似的函数。

setState(partialstate,callback){
 this.updater.enqueueSetState(this,partialstate,callback);
}

React 钩子

react 钩子基本上做同样的事情,但是它们使用调度程序对象而不是更新器字段

const React = {
    __currentDispatcher:null,
    useState(initialstate){
       return React.__cirrentDispatcher.useState(initalstate);
    }
}

当我们调用 useState时,调用被转发到了一个叫做 currentDispatcher的东西。而这个调度器再次由 React DOM设置。每当我们渲染一个组件时,React DOM 都会像这样设置调度程序。

const previouDispatcher = React.__currentDispatcher;
Reacr.__currentDispathch = ReactDOMDispatcher;
let result;
try{
resule = Component(props);
} finally{
 React.__currentDispatcher = previousDispatcher;
}

为什么 key 和 ref 不是 props 的一部分?

它是 React 的核心-- Diffing 算法的重要组成部分。

$$typeof 属性到底是什么?