重学 React-JSX

什么是 JSX?

JSX 在 React 应用开发中承担了重要的角色,我们常用它来定义 React 组件的内容,官网这样描述 JSX:

JSX 是一个 JavaScript 的语法扩展。JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模版语言,但它具有 JavaScript 的全部功能。

JSX 即 JavaScript XML,JSX 语法允许我们这样定义变量:

const element = <h1>Hello, world!</h1>;
复制代码

看上去有点像 html 标签,但实际并不是,因为它被赋给了一个变量,它同样也不是字符串。那 React 是如何支持我们使用 JSX 呢?

如何支持 JSX?

作为 JS 的语法扩展,浏览器并不认识 JSX 语法,因此需要借助工具进行转换,这个工具通常是 Babel(也可以是 TypeScript)。我们知道 Babel 是下一代 JavaScript 语法编译器,它能将我们编写的 ES6、ES7 代码转换成低版本浏览器能够识别的 ES5 语法的代码。对于如下 JSX 代码:

<div>
  <h1 id="greet">hello</h1>
  <span>Jay</span>
</div>
复制代码

会被 Babel 转译成如下(这里在线尝试 JSX):

"use strict";

/*#__PURE__*/
React.createElement(
  "div",
  null,
  /*#__PURE__*/ React.createElement(
    "h1",
    {
      id: "greet",
    },
    "hello"
  ),
  /*#__PURE__*/ React.createElement("span", null, "Jay")
);
复制代码

原来我们编写的 JSX 会被 Babel (@babel/plugin-transform-react-jsx 插件)编译成 React.createElement 函数,源码中 React.createElement 会接收type、config、children三个参数,type代表当前的 HTML 标签或 React 组件名或 React.Fragment,config收集了标签内所有我们定义的属性,如例子中定义的<h1 id="greet">hello</h1> id 属性被纳入了config对象中,div 包裹的元素被作为 children 依次传入到 createElement 剩余参数中。

我们通过 Babel 拨开了 JSX 的外衣,转换后的代码只是普普通通的 JS 函数,这就让我们对JSX拥有完全的JavaScript能力不会感到疑惑。同时 JSX 转换后依赖 React 的 createElement 函数,我们可以继续看 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) {
    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;

    // 遍历config,加入到props对象中
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // 由于children数量不定,需要匹配children的数量,最后将children赋给props对象
  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;
  }

  // 对于设置了defaultProps的React组件,将对应的值赋给props,完成组件默认值设置
  if (type && 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 最终返回调用了ReactElement方法

// 整理了部分代码
const ReactElement = function (type, key, ref, self, source, owner, props) {
  const element = {
    // 作为ReactElement唯一标识
    $$typeof: REACT_ELEMENT_TYPE,

    type: type,
    key: key,
    ref: ref,
    props: props,

    _owner: owner,
  };

  return element;
};
复制代码

ReactElement将参数整合进了一个对象中并返回,这个对象就是 React 中的虚拟 DOM,记录了一个 DOM 元素或 React 组件要渲染的所有内容。

c815447f-1048-45c1-9d80-127fed059b1e-image.png

以上 JSX 转换的整个过程,但这隐式的转换常常让 React 初学者感到疑惑:自己的文件里并没有用到 React,但还是要引入 React,原因是开发者使用了 JSX,JSX 被编译后调用了 React.createElement 方法,间接依赖 React 包。在最新的React 17版本中新增了 JSX 的转换方式,开发者不需要再在自己的文件中引入 React 即可使用 JSX,主要还是通过 Babel 插件在运行时引入React 17新增的 JSX 入口,这个 JSX 入口也是一个函数,其职责实际上和 React.createElement 相似。

NOJSX.png

为什么使用 JSX?

用 JSX 开发 React 应用不是必须的,JSX 只是 React.createElement 的语法糖,两者都能达到描述 UI 的作用,但 React 团队建议我们使用 JSX 来开发 React 应用,主要有以下原因:

  • 历史原因:Facebook 早年使用 PHP 开发了 XHP,XHP 可以允许开发者在 PHP 中直接使用 HTML 标签,而不必使用字符串拼接 HTML。有了 XHP 的经验,Facebook 以相似的方式在 JS 中实现了 JSX。
  • 由于 JSX 完全拥有 JavaScript 的能力,学习 JSX 的成本比较低,对开发者来说这就是白盒,只需理解 JSX 部分命名规则即可。
  • 很少有人直接使用 React.createElement 的方式编写布局内容,React.createElement 表现的页面结构显得糅杂且不清晰,而 JSX 这种类 HTML 语法将页面结构表现的更加直观、更易读且容易理解。

JSX 与模板引擎的区别

如今三大前端框架里表达 UI 的方式主要有两种:JSX(React、Vue)与模板引擎(Angular、Vue)。JSX 允许开发者在 JS 中写 HTML,由于 View 和 JS 逻辑的上下文环境是统一的,这样 JSX 就显得特别灵活,这也是 React 选择 JSX 的原因。

React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。React 并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式,而是通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离。

同时JSX 打通了 HTML 模型与 JS 编程语言,几乎没有学习成本,模板引擎则需要开发者学习并遵循相应的模板语法。模板引擎将模板与逻辑相互分离,两者独立工作,整个开发模式分层明显,它能保持视图层的纯净,在某些场景(纯页面可视化搭建)拥有一定的优势。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享