# 目标

  • 了解常用的元素会被 React 处理成什么,有利于后续理解 react fiber 类型
  • 理解 jsx 的编译过程,方便操纵 children、控制 React 渲染,有利于便捷使用 React 插槽组件

# babel编译结果

<div>
   <TextComponent />
   <div>hello,world</div>
   let us learn React!
</div>

==babel==> 
React.createElement("div", null,
    React.createElement(TextComponent, null),
    React.createElement("div", null, "hello,world"),
    "let us learn React!"
)

# babel编译结果归纳

React.createElement(
  type,
  [props],
  [...children]
)
  • type 如果是组件类型,会传入组件对应的类或函数;如果是 dom 元素类型,传入 div 或者 span 之类的字符串

  • [props] 一个对象,在 dom 类型中为标签属性,在组件类型中为 props

  • [...children] children,根据顺序排列。

# JSX转换规则

jsx元素类型 react.createElement 转换后 type 属性
element元素类型 react element类型 标签字符串,例如 div
fragment类型 react element类型 symbol react.fragment类型
文本类型 直接字符串
数组类型 返回数组结构,里面元素被react.createElement转换
组件类型 react element类型 组件类或者组件函数本身
三元运算 / 表达式 先执行三元运算,然后按照上述规则处理 看三元运算返回结果
函数执行 先执行函数,然后按照上述规则处理 看函数执行返回结果

# react 调和处理

在调和阶段,上述 React element 对象的每一个子节点都会形成一个与之对应的 fiber 对象,然后通过 sibling、return、child 将每一个 fiber 对象联系起来。

# 不同种类的 fiber Tag

export const FunctionComponent = 0;       // 函数组件
export const ClassComponent = 1;          // 类组件
export const IndeterminateComponent = 2;  // 初始化的时候不知道是函数组件还是类组件 
export const HostRoot = 3;                // Root Fiber 可以理解为根元素 , 通过reactDom.render()产生的根元素
export const HostPortal = 4;              // 对应  ReactDOM.createPortal 产生的 Portal 
export const HostComponent = 5;           // dom 元素 比如 <div>
export const HostText = 6;                // 文本节点
export const Fragment = 7;                // 对应 <React.Fragment> 
export const Mode = 8;                    // 对应 <React.StrictMode>   
export const ContextConsumer = 9;         // 对应 <Context.Consumer>
export const ContextProvider = 10;        // 对应 <Context.Provider>
export const ForwardRef = 11;             // 对应 React.ForwardRef
export const Profiler = 12;               // 对应 <Profiler/ >
export const SuspenseComponent = 13;      // 对应 <Suspense>
export const MemoComponent = 14;          // 对应 React.memo 返回的组件

# fiber 对应关系

  • child: 一个由父级 fiber 指向子级 fiber 的指针
  • return:一个子级 fiber 指向父级 fiber 的指针
  • sibling: 一个 fiber 指向下一个兄弟 fiber 的指针

# 面试题

1. 问: 老版本的 React 中,为什么写 jsx 的文件要默认引入 React? 如下

import React from 'react'
function Index(){
    return <div>hello,world</div>
}

答: 因为 jsx 在被 babel 编译后,写的 jsx 会变成上述 React.createElement 形式,所以需要引入 React,防止找不到 React 引起报错。

2. react17 jsx 为什么不需要引入React 答: 介绍全新的 JSX 转换 (opens new window)介绍全新的 JSX 转换 (opens new window)

3. 问: React.createElement 和 React.cloneElement 到底有什么区别呢? 答: 可以完全理解为,一个是用来创建 element 。另一个是用来修改 element,并返回一个新的 React.element 对象。

4. 问: 为什么需要cloneElement,不能在原来的element上修改更新 答: 原来的 element 的属性是notextensible 不可拓展的, React 不能轻易让开发者修改 Element

5. 问: jsx更新流程 答: [1] babel 编译成 React Element 形式,jsx 内容被 react.createElement 转换成 react element 对象 [2] 调和处理,上述 React element 对象的每一个子节点都会形成一个与之对应的 fiber 对象,然后通过 sibling、return、child 将每一个 fiber 对象联系起来 [3]

[总结] JSX --- babel编译 ---> ReactElement --- reconciler ---> fiber

6. 问: 如何手动控制jsx来生成React.element来实现render的一个可控性 答: [1] 将children扁平化处理,将数组类型的子节点打开 ; [2] 去除children中文本类型节点; [3] 向children最后插入say goodbye元素; [4] 克隆新的元素节点并渲染。

7. 问: jsx通过Babel来转化成js识别的对象的流程 答: [1] createElement方式(需要因引入React) [2] runtime方式(需要 .babelrc 设置 runtime: automatic), @babel/plugin-syntax-jsx ,在编译的过程中注入 _jsxRuntime api ,使得新版本 React 已经不需要引入 createElement