React进阶系列之JSX

793 阅读4分钟

最近在读修言的《深入浅出搞定 React》,笔者的文笔和文风都非常有趣,又不乏干货,重读几遍后仍收获满满,整理了下笔记分享给大家。本系列大概有 15 篇,如果觉得有帮助可以给个 star,如果发现问题请不吝在评论区指正。

React 作为一个优秀的前端框架,在架构上融合了数据驱动视图、组件化、函数式编程、面向对象、Fiber 等经典设计哲学,在底层技术选型上涉及了 JSX、虚拟 DOM 等经典解决方案,在周边生态上至少涵盖了状态管理和前端路由两大领域的最佳实践,此外,它还自建了状态管理机制与事件系统,创造性地在前端框架中引入了 Hooks 思想...React 十年如一日的稳定输出背后,有太多值得我们去吸收和借鉴的东西。

题外:关于学习,我认为,学习的本质是重复。

JSX 三问

如果你不能很好地回答下面的问题,那你可能把 JSX 想的过于简单了。

1. JSX 的本质是什么?它和 JS 之间到底是什么关系?

JSX 是 Javascript 的语法扩展,它和模板语言很接近,但是充分具备 Javascript 的能力,通过 Babel 等编译工具编译后 JSX 就变成了 React.createElement,所以 JSX 的本质就是 React.createElement 这个 Javascript 调用的语法糖。

2. 为什么要用 JSX?不用会有什么后果?

React.createElement 调用代码过于繁琐,而作为语法糖的 JSX 则层次分明,嵌套关系清晰,这就是为什么官方推荐使用 JSX 的原因,这种类 HTML 标签的语法糖能够帮助我们快速创建虚拟 DOM。

3. JSX 背后的功能模块是什么?这些功能模块都做了哪些事情?

JSX 背后主要对应 React.createElement 和 ReactElement 两个函数,这两个函数帮助我们将 JSX 映射为虚拟 DOM,最后通过 ReactDOM.render 我们可以将虚拟 DOM 渲染为真实 DOM。React.createElement 主要做了三件事,处理 config,构造 props,然后返回一个 ReactElement 的调用,ReactElement 接受处理后的参数,简单组装后返回一个 element,也就是我们说的虚拟 DOM 的一个节点。

接下来我们重点看 JSX 相关的两个函数源码。

React.createElement

React.createElement 源码如下,

/**
 * Create and return a new ReactElement of the given type.
 * See https://reactjs.org/docs/react-api.html#createelement
 */
export function createElement(type, config, children) {
  let propName;

  // Reserved names are extracted
  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    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 = childArray;
  }

  // Resolve default props
  if (type &amp;&amp; type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

从源码可以看到,createElement 函数主要做了三件事:

  1. 处理 config
  2. 构造 props(3步:提取config,处理子元素、处理默认值)
  3. 调用 ReactElement

具体来说,

  1. 首先声明 propName、props、key、ref、self、source 等变量
  2. 依次对 ref、key、self 和 source 属性赋值,其中 key 会强制转换为字符串
  3. 开始构造 props,首先遍历 config 对象,筛选出可以提取到 props 中的属性
  4. 处理子元素,生成 props.children,这里分为一个和多个子元素两种情况
  5. 处理 defaultProps,如果有默认属性值则覆盖
  6. 传入处理好的参数,返回一个 ReactElement 函数的调用

有哪些需要留意的点?

  1. 方法的入参,type(节点类型) | config(元素的属性)| children(子元素可能有多个)
  2. 属性 key 如果不是字符串会被强制转换为字符串
  3. 默认属性是通过 type 传入的而不是 config
  4. 本质上 createElement 就是一个 “参数中介” 没有太多魔法

ReactElement

ReactElement 的源码如下,

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // 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,
  };

  return element;
};

ReactElement 只做了一件事,就是创建并返回一个 element 对象,这个实例对象就是我们说的虚拟 DOM 中的一个节点。既然是虚拟 DOM,那就意味着和渲染到页面上的真实 DOM 之间还有一些距离,而这个距离就是由大家喜闻乐见的 ReactDOM.render 方法来填补的,

const virtualElement = React.createElement('div', null);
const rootElement = document.getElementById("root");
ReactDOM.render(virtualElement, rootElement);

新增的 $$typeof 属性用于判断一个元素是否为 ReactElement,

function isValidElement(object) {
  // 对象检测和 $$typeof 属性检测
  return (
    typeof object === 'object' &amp;&amp;
    object !== null &amp;&amp;
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}

至此,相信你已经可以很好地回答上面的关于 JSX 的三个问题,接下来我们从老生常谈的 React 生命周期开始,一起体会下 React 背后的设计思想:组件化和虚拟 DOM,以及 React 几个版本生命周期的不同,其废旧立新的背后的考量。

相关文章

写在最后

本文首发于我的 博客,才疏学浅,难免有错误,文章有误之处还望不吝指正!

如果有疑问或者发现错误,可以在评论区进行提问和勘误,

如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

(完)