Skip to content

react 为什么推荐 jsx

前言

作为一个 react 开发者,那么在开发的过程中,不可避免的会接触到 jsx 来编写组件代码。虽然可以编写 jsx 的框架越来越多,但是联系最深的还是 react。那么 jsx 到底是什么呢?又是如何变成 dom 进行页面渲染的呢?接下来一探究竟!

jsx 是什么

jsx 是 javascript 的一种语法扩展,和模板语言很接近,但是它充分具备 javascript 的能力。

简单来说就是把 js 的语法做了一定的扩展,让其支持编写 html 的语法,但是对于浏览器解析来说,并不能识别这个 jsx,那么就需要通过工具进行转译,这个工具就是 babel

转译的 jsx 变成了什么

首先来看一个 babel 转译的例子:

image.png 从图片可以看出,jsx 通过 babel 转译之后会变成通过 createElement 方法创建 react 组件。那么由此可以简单理解为 jsx 是 createElement 方法的语法糖,通过 jsx 的方式可以更加方便的写出 react 组件代码。那么简而言之,你也可以不使用 jsx 而是选择直接使用 react 提供的 createElement 方法来创建组件等,很显然笔者断定你不会这个样子做的,因为我懂你(其实是因为太麻烦),看下这个复杂的例子:

image.png

image.png 如果业务开发中,你选择 crateElement 来编写组件,那我相信你将拥有无限加班的可能,这就是为什么选择 jsx 的原因

所以 jsx 的好处是代码层次分明、嵌套关系清晰,而不像 createElement 代码一样让人感觉浑乱,读写都不够友好。,其实它是最大的简化开发流程,让开发者们编码体验更好。

为什么老版本的 react 中需要引入 react,而新版本不用?

其实通过 babel 转译代码这个层面,我想大家也看出了一点问题所在,其实原因就是在于,在转译完成之后,jsx 会变成 React.createElement 的形式,那么毫无疑问你必须要引入 react。

但是作为新版本的 react 引入了 react/jsx-runtime,编译工具会自动到 React 模块中获取并将代码编译,而不是以前的 React.createElement

import {jsx as _jsx} from 'react/jsx-runtime'

function App() {
    return _jsx('h1',{children:'hello water'})

}

所以在 react 新版本中不再需要显示引入 react 了

jsx 转译后的元素样子

通过 react.createElement 把 jsx 转换成 react element,对应的有自己的转换规则和类型

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

在 react 的底层,react element 对象的每一个节点都会形成一个与之对应的 fiber 对象,然后 fiber 通过 sibling、return、child 等指针将每一个 fiber 对象联系起来。

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

通过源码初探 react dom 的转换

首先扒一扒 createElement 方法的源码,看都做了什么

js
/**
 React的创建元素方法
 */
export function createElement(type, config, children) {
  // propName 变量用于储存后面需要用到的元素属性
  let propName
  // props 变量用于储存元素属性的键值对集合
  const props = {}
  // key、ref、self、source 均为 React 元素的属性,此处不必深究
  let key = null
  let ref = null
  let self = null
  let source = null

  // config 对象中存储的是元素的属性
  if (config != null) {
    // 进来之后做的第一件事,是依次对 ref、key、self 和 source 属性赋值
    if (hasValidRef(config)) {
      ref = config.ref
    }
    // 此处将 key 值字符串化
    if (hasValidKey(config)) {
      key = '' + config.key
    }
    self = config.__self === undefined ? null : config.__self
    source = config.__source === undefined ? null : config.__source
    // 接着就是要把 config 里面的属性都一个一个挪到 props 这个之前声明好的对象里面
    for (propName in config) {
      if (
        // 筛选出可以提进 props 对象里的属性
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName]
      }
    }
  }
  // childrenLength 指的是当前元素的子元素的个数,减去的 2 是 type 和 config 两个参数占用的长度
  const childrenLength = arguments.length - 2
  // 如果抛去type和config,就只剩下一个参数,一般意味着文本节点出现了
  if (childrenLength === 1) {
    // 直接把这个参数的值赋给props.children
    props.children = children
    // 处理嵌套多个子元素的情况
  } else if (childrenLength > 1) {
    // 声明一个子元素数组
    const childArray = Array(childrenLength)
    // 把子元素推进数组里
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2]
    }
    // 最后把这个数组赋值给props.children
    props.children = childArray
  }

  // 处理 defaultProps
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName]
      }
    }
  }

  // 最后返回一个调用ReactElement执行方法,并传入刚才处理过的参数
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props)
}

这个函数有 3 个参数分别是 type,config,children

  • type: 用于标识节点的类型
  • config:以对象的形式传入,组件所有的属性都会以键值对的形式存储在 config 对象中
  • children:以对象形式传入,主要记录组件标签之间嵌套的内容(子节点元素)

找个例子你可能更好理解

image.png

image.png

通过 createElement 的源码阅读可以描绘出一个函数执行所做事情的流程图

image.png

从中可以看出,createElement 方法就像是一个 react element 的处理器,通过开发者传入的一些数据作为依据,构建出一个 react element。

构建 element 时会去调用 ReactElement 方法,然后返回一个根据入参配置生成的 element,源码如下

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // REACT_ELEMENT_TYPE是一个常量,用来标识该对象是一个ReactElement
    $$typeof: REACT_ELEMENT_TYPE,

    // 内置属性赋值
    type: type,
    key: key,
    ref: ref,
    props: props,

    // 记录创造该元素的组件
    _owner: owner,
  };

  //
  if (__DEV__) {
    // 这里是一些针对 __DEV__ 环境下的处理,对于大家理解主要逻辑意义不大,此处我直接省略掉,以免混淆视听
  }

  return element;
};

整体理解为调用 createElement 方法,然后再调用 reactElement 方法最后生成 element 元素,返回渲染

小结

总体而言 jsx 就是 js 的一个扩展,然后 react 通过 babel 工具将 jsx 转译成 react 的语法,通过调用 createElement 和 reactElement 方法创建并返回出一个 element 元素。

如有转载或 CV 的请标注本站原文地址