面试官:React组件为什么需要用标签包裹?

1,871 阅读4分钟

阅读时间大约5-10min~

前言

function App() {
  return (
    <h1>Hello CodeSandbox</h1>
    <h2>Start editing to see some magic happen!</h2>
  );
}

请问以上代码有什么问题,会报什么错? 答案:JSX元素必须被一个可闭合标签包裹⬇️

image.png 相信不少同学刚开始学习react的时候都编写过类似的代码,但大家有想过为什么会报这样的错吗?以及这样设计的目的是什么? 不了解也没有关系,接下来让我们一起逐步解密~

React.createElement与JSX

所有的JSX组件最终都通过React.createElement转化成对象交给react消费。

基本用法

React.createElement 接受至少三个参数:

  1. type: 字符串(代表 HTML 标签)或 React 组件(函数组件或类组件)。
  2. props: 对象,包含了该元素的属性。传递给元素的属性在组件中可通过 this.props 或 props 访问。
  3. ...children: 其余参数都会作为子节点(children)处理。这可以是更多的元素,字符串,数字等。

概念

当React在处理渲染一个组件的时候,它需要每个组件返回一个单一的根节点。JSX就是React.createElement的语法糖,每个JSX元素最终都会转化为React.createElement调用。例如⬇️

const element = (
  <div>
    <h1>Hello CodeSandbox</h1>
    <h2 className='title'>Start editing to see some magic happen!</h2>
  </div>
)

// 上述JSX最终会转化为⬇️
const element = React.createElement(
  'div',
  null,
  React.createElement('h1', null, 'Hello CodeSandbox'),
  React.createElement('h2', { className='title' }, 'Start editing to see some magic happen!')
)

综上所述,前言中的报错是因为React.createElement设计时是以单节点为基础的,所以同时传入多个节点会发生报错。 我们不妨再思考一下以下两个问题:

  1. 为什么设计React.createElement时是以单节点为基础的?
  2. 为什么传入<></>这样的空标签就不报错了呢?它最终其实也没有构建出真实的dom节点,这不是违背了React.createElement的设计初衷吗?。

React的单节点思想

接下来我想请大家跟我一起设想一个场景⬇️

首先,请大家抛弃react语法,用原生JS去思考这个问题。

现在有一个列表页,存在着3条数据,我点击了搜索,发现当前页面新增了4条数据,总共7条数据,这个时候你作为开发者,该怎么去更新dom呢?

这个时候你可能脱口而出,将这4条数据一条条塞进去,但大家应该都明白,每次操作dom的性能开销是很大的,需要进行dom插入(进行dom树的搜索),且会引起页面的回流重绘。如果进行多次操作,性能开销就会成倍增长。

现在你应该想到了,那我一次性把这4条数据的dom都塞进去,不就操作一次dom了吗?首先恭喜你回答正确,但可以再思考一下,我们怎么做到将4个dom一次性插入呢?原生js可没有类似的api支持你做到这一点,所以我们是不是需要将这多个dom合并成一个单一节点?这就是React的单节点思想

<></>的原理

image.png

大家请先看上面这张图,我用了空标签包裹住了两个子标签,实现了react的单节点思想,但空标签却没有构建出真实的dom出来,他的原理是什么呢?

<></>的前世今生

如果你使用过react16.2之前的版本,你一定对React.Fragment这个词不陌生,因为在16.2版本之前,<React.Fragment></React.Fragment>充当了<></>的作用。所以<></>只是之前的语法糖。 至于为什么<></>没有构建出真实dom,这是因为react内部对Fragment做了特殊处理,将此作为一个抽象组件。

为什么要出现<></>

我认为有以下原因:

  1. 性能消耗:避免影响dom结构,如果每一个标签都强制用真实标签包裹,我们的dom树会多出不少冗余dom,结构会更加复杂,对后续的dom操作消耗会更大。
  2. 结构统一:虽然在真实dom中,<></>没有作用。但在React内部,它会被作为一个真实节点,react会将每一个节点存储并定位,以便于后续做diff以及更新操作。

总结

在结束之际,我想留给大家一个文中并没有解答的问题。

如果让你实现一个<></>,你会怎么去设计与实现呢?