JSX作为描述组件内容的数据结构,为JS赋予了更多视觉表现力。在React中我们大量使用他。在深入源码之前,有些疑问我们需要先解决:

  • JSXFiber节点是同一个东西么?
  • React ComponentReact Element是同一个东西么,他们和JSX有什么关系?

带着这些疑问,让我们开始这一节的学习。

JSX简介

相信作为React的使用者,你已经接触过JSX。如果你还不了解他,可以看下官网对其的描述深入理解JSX - 图1 (opens new window)

JSX在编译时会被Babel编译为React.createElement方法。

JSX编译

关注公众号,后台回复712获得在线Demo地址

这也是为什么在每个使用JSX的JS文件中,你必须显式的声明

  1. import React from 'react';

否则在运行时该模块内就会报未定义变量 React的错误。

注意

在React17中,已经不需要显式导入React了。详见介绍全新的 JSX 转换深入理解JSX - 图2 (opens new window)

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

比如在preact深入理解JSX - 图4 (opens new window)这个类React库中,JSX会被编译为一个名为h的函数调用。

  1. // 编译前
  2. <p>KaSong</p>
  3. // 编译后
  4. h("p", null, "KaSong");

React.createElement深入理解JSX - 图5 (opens new window)

既然JSX会被编译为React.createElement,让我们看看他做了什么:

  1. export function createElement(type, config, children) {
  2. let propName;
  3. const props = {};
  4. let key = null;
  5. let ref = null;
  6. let self = null;
  7. let source = null;
  8. if (config != null) {
  9. // 将 config 处理后赋值给 props
  10. // ...省略
  11. }
  12. const childrenLength = arguments.length - 2;
  13. // 处理 children,会被赋值给props.children
  14. // ...省略
  15. // 处理 defaultProps
  16. // ...省略
  17. return ReactElement(
  18. type,
  19. key,
  20. ref,
  21. self,
  22. source,
  23. ReactCurrentOwner.current,
  24. props,
  25. );
  26. }
  27. const ReactElement = function(type, key, ref, self, source, owner, props) {
  28. const element = {
  29. // 标记这是个 React Element
  30. $$typeof: REACT_ELEMENT_TYPE,
  31. type: type,
  32. key: key,
  33. ref: ref,
  34. props: props,
  35. _owner: owner,
  36. };
  37. return element;
  38. };

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

所以调用React.createElement返回的对象就是React Element么?

React提供了验证合法React Element的全局API React.isValidElement深入理解JSX - 图6 (opens new window),我们看下他的实现:

  1. export function isValidElement(object) {
  2. return (
  3. typeof object === 'object' &&
  4. object !== null &&
  5. object.$$typeof === REACT_ELEMENT_TYPE
  6. );
  7. }

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

那么JSXReact Component的关系呢?

React Component

React中,我们常使用ClassComponentFunctionComponent构建组件。

  1. class AppClass extends React.Component {
  2. render() {
  3. return <p>KaSong</p>
  4. }
  5. }
  6. console.log('这是ClassComponent:', AppClass);
  7. console.log('这是Element:', <AppClass/>);
  8. function AppFunc() {
  9. return <p>KaSong</p>;
  10. }
  11. console.log('这是FunctionComponent:', AppFunc);
  12. console.log('这是Element:', <AppFunc/>);

React Component 分类 Demo

关注公众号,后台回复901获得在线Demo地址

我们可以从Demo控制台打印的对象看出,ClassComponent对应的Elementtype字段为AppClass自身。

FunctionComponent对应的Elementtype字段为AppFunc自身,如下所示:

  1. {
  2. $$typeof: Symbol(react.element),
  3. key: null,
  4. props: {},
  5. ref: null,
  6. type: ƒ AppFunc(),
  7. _owner: null,
  8. _store: {validated: false},
  9. _self: null,
  10. _source: null
  11. }

值得注意的一点,由于

  1. AppClass instanceof Function === true;
  2. AppFunc instanceof Function === true;

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

  1. ClassComponent.prototype.isReactComponent = {};

JSX与Fiber节点

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

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

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

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

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

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

参考资料