Skip to content

理解 React 中的 JSX #32

Open
Open
@myLightLin

Description

@myLightLin

JSX 是什么

引用 React 官网对 JSX 的定义:

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

在 React 里,JSX 大概长这样:

function App(props) {
  return (
    <div>
      <div className="title">我是标题</div>
      <div className="content">我是内容</div>
    </div>
  )
}

上面这段代码,会经过 Babel 编译,然后转换为 React.createElement() 函数调用。所以接下来我们要探究 createElement 这个函数。

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 (__DEV__) {
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    if (hasValidKey(config)) {
      if (__DEV__) {
        checkKeyStringCoercion(config.key);
      }
      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];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // Resolve default props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  if (__DEV__) {
    if (key || ref) {
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      if (key) {
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

这个方法接收三个入参:

export function createElement(type, config, children)
  • type: 传入的节点类型,比如 div, p 等,也可以是 React 组件
  • config: 配置参数,比如组件的一些 props 等信息
  • children: 嵌套的子组件内容

这个 createElement 函数主要做了如下工作:

  1. 处理 key,ref,source,self 内部参数
  2. 从 config 中提取可以放进 props 的参数
  3. 通过 arguments.length - 2 判断是传入了一个子数组项还是多个,计算得到 props.children
  4. 如果 type 传了 defaultProps,从中提取属性放入到 props 中
  5. 发起 ReactElement 函数调用

ReactElement

接下来要接着追一下 ReactElement 函数,源码如下:

/**
 * Factory method to create a new React element. This no longer adheres to
 * the class pattern, so do not use new to call it. Also, instanceof check
 * will not work. Instead test $$typeof field against Symbol.for('react.element') to check
 * if something is a React Element.
 *
 * @param {*} type
 * @param {*} props
 * @param {*} key
 * @param {string|object} ref
 * @param {*} owner
 * @param {*} self A *temporary* helper to detect places where `this` is
 * different from the `owner` when React.createElement is called, so that we
 * can warn. We want to get rid of owner and replace string `ref`s with arrow
 * functions, and as long as `this` and owner are the same, there will be no
 * change in behavior.
 * @param {*} source An annotation object (added by a transpiler or otherwise)
 * indicating filename, line number, and/or other information.
 * @internal
 */
function ReactElement(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,
  };

  if (__DEV__) {
    // The validation flag is currently mutative. We put it on
    // an external backing store so that we can freeze the whole object.
    // This can be replaced with a WeakMap once they are implemented in
    // commonly used development environments.
    element._store = {};

    // To make comparing ReactElements easier for testing purposes, we make
    // the validation flag non-enumerable (where possible, which should
    // include every environment we run tests in), so the test framework
    // ignores it.
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false,
    });
    // self and source are DEV only properties.
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self,
    });
    // Two elements created in two different places should be considered
    // equal for testing purposes and therefore we hide it from enumeration.
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source,
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }

  return element;
}

这个 ReactElement 函数看起来非常简短,除去那些 DEV 环境的处理代码,它其实只做了一件事,就是返回下面这个 element 对象:

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

这就是一个 React 元素节点,到这里来,我们就可以知道 JSX 的真面目了:Babel 把我们写的 JSX 编译为 React.createElement 调用,然后这个函数调用后又返回了一个 ReactElement 元素实例,也就是虚拟 DOM 节点,那这样的虚拟节点是怎么转变为真实 DOM 的呢?这就是 ReactDOM.render 干的活了。

总结

  • JSX 是 JavaScript 的扩展语法,它允许我们使用类 HTML 的语法来编写 UI 模板
  • JSX 会经由 babel 编译为 React.createElement 调用,这个函数二次处理参数,然后返回一个 ReactElement 对象实例
  • ReactElement 对象实例也就是虚拟 DOM 节点,它还要经过 ReactDOM.render 才能变成页面上的 DOM

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions