跳至主要內容

深入理解JSX

Yihui大约 3 分钟

深入理解JSX

JSX在编译时会被Babel编译为React.createElement方法。这也是为什么在每个使用JSX的JS文件中,你必须显式的声明。但在React 17 中,已经不需要显式导入React了。

React 17 发布在即,尽管我们想对 JSX 的转换进行改进,但我们不想打破现有的配置。于是我们选择与 Babel 合作open in new window ,为想要升级的开发者提供了一个全新的,重构过的 JSX 转换的版本

  • 使用全新的转换,你可以单独使用 JSX 而无需引入 React。
  • 根据你的配置,JSX 的编译输出可能会略微改善 bundle 的大小。
  • 它将减少你需要学习 React 概念的数量,以备未来之需。 来源介绍全新的 JSX 转换open in new window

JSX并不是只能被编译为React.createElement方法,你可以通过@babel/plugin-transform-react-jsxopen in new window插件显式告诉Babel编译时需要将JSX编译为什么函数的调用(默认为React.createElement)。

React.createElement 做了什么

export function createElement(type, config, children) {
  let propName;

  const props = {};

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

  if (config != null) {
    // 将 config 处理后赋值给 props
    // ...省略
  }

  const childrenLength = arguments.length - 2;
  // 处理 children,会被赋值给props.children
  // ...省略

  // 处理 defaultProps
  // ...省略

  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // 标记这是个 React Element
    $$typeof: REACT_ELEMENT_TYPE,

    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };

  return element;
};

我们可以看到,React.createElement最终会调用ReactElement方法返回一个包含组件数据的对象,该对象有个参数$$typeof: REACT_ELEMENT_TYPE标记了该对象是个React Element

React提供了验证合法React Element的全局API React.isValidElement (opens new window)open in new window

export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}

可以看到,$$typeof === REACT_ELEMENT_TYPE的非null对象就是一个合法的React Element。换言之,在React中,所有JSX在运行时的返回结果(即React.createElement()的返回值)都是React Element

React Component 分类

ClassComponent对应的Elementtype字段为AppClass自身。

FunctionComponent对应的Elementtype字段为AppFunc自身。

同时,AppClass 和 AppFunc 都是函数类型

AppClass instanceof Function === true;
AppFunc instanceof Function === true;

所以无法通过引用类型区分ClassComponentFunctionComponentReact通过ClassComponent实例原型上的isReactComponent变量判断是否是ClassComponent

ClassComponent.prototype.isReactComponent = {};

由JSX生成Fiber节点

从上面的内容我们可以发现,JSX是一种描述当前组件内容的数据结构,他不包含组件schedulereconcilerender所需的相关信息。

比如如下信息就不包括在JSX中:

  • 组件在更新中的优先级
  • 组件的state
  • 组件被打上的用于Renderer标记

这些内容都包含在Fiber节点中。

所以,在组件mount时,Reconciler根据JSX描述的组件内容生成组件对应的Fiber节点

update时,ReconcilerJSXFiber节点保存的数据对比,生成组件对应的Fiber节点,并根据对比结果为Fiber节点打上标记