语法糖JSX

通过配置Babel相应的插件,我们可以在React和Vue中使用JSX语法。那Babel会对JSX做什么呢?

Virtual DOM

Vue的Babel JSX插件React的Babel JSX插件

二者都是将JSX转换为函数调用。React是对React.createElement的调用。Vue是注入 var h = this.$createElement之后,转换为对h的调用。

React

var profile = <div>
  <img src="avatar.png" className="profile" />
  <h3>{[user.firstName, user.lastName].join(' ')}</h3>
</div>;
var profile = React.createElement("div", null,
  React.createElement("img", { src: "avatar.png", className: "profile" }),
  React.createElement("h3", null, [user.firstName, user.lastName].join(" "))
);

Vue:在SFC中,Vue的编译器将<template>部分解析并编译成vdom。

render (h) {
  return (
    <div
      // normal attributes or component props.
      id="foo"
      // DOM properties are prefixed with `domProps`
      domPropsInnerHTML="bar"
      // event listeners are prefixed with `on` or `nativeOn`
      onClick={this.clickHandler}
      nativeOnClick={this.nativeClickHandler}
      // other special top-level properties
      class={{ foo: true, bar: false }}
      style={{ color: 'red', fontSize: '14px' }}
      key="key"
      ref="ref"
      // assign the `ref` is used on elements/components with v-for
      refInFor
      slot="slot">
    </div>
  )
}
render (h) {
  return h('div', {
    // Component props
    props: {
      msg: 'hi'
    },
    // normal HTML attributes
    attrs: {
      id: 'foo'
    },
    // DOM props
    domProps: {
      innerHTML: 'bar'
    },
    // Event handlers are nested under "on", though
    // modifiers such as in v-on:keyup.enter are not
    // supported. You'll have to manually check the
    // keyCode in the handler instead.
    on: {
      click: this.clickHandler
    },
    // For components only. Allows you to listen to
    // native events, rather than events emitted from
    // the component using vm.$emit.
    nativeOn: {
      click: this.nativeClickHandler
    },
    // class is a special module, same API as `v-bind:class`
    class: {
      foo: true,
      bar: false
    },
    // style is also same as `v-bind:style`
    style: {
      color: 'red',
      fontSize: '14px'
    },
    // other special top-level properties
    key: 'key',
    ref: 'ref',
    // assign the `ref` is used on elements/components with v-for
    refInFor: true,
    slot: 'slot'
  })
}

你可以看到React和Vue都有自己的“createElement”函数。这背后是他们各自的VirtualDOM实现。

实现VirtualDOM的还有

通过配置Babel,可以将React.createElement换为其他内容:

{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "pragma": "deku.dom", // 默认是React.createElement,这里为deku修改为dom
      "pragmaFrag": "deku.DomFrag"
    }]
  ]
}

从babel-plugin-transform-react-jsx源码中可以知道,它有3个可配置项:

  • throwIfNamespace
  • pragma
  • pragmaFrag
const THROW_IF_NAMESPACE =
    options.throwIfNamespace === undefined ? true : !!options.throwIfNamespace;

const PRAGMA_DEFAULT = options.pragma || "React.createElement";
const PRAGMA_FRAG_DEFAULT = options.pragmaFrag || "React.Fragment";

对比一下,感受一下documenet.createElement与各个vdom的createElement。

{
  'real-DOM': document.createElement,
  'react-vdom': React.createElement,
  'vue-vdom': Vue.prototype.$createElement,
  'virtual-dom': VirtualDOM.createElement,
  'deku-vdom': deku.element,
  'mithril-vdom': m
}

其实这个createElement函数的核心功能就是构建一个树型数据结构来表示DOM:你可以试一下下面的代码

function createElement(tag, props, ...children) {
  return { tag, props, children }
}
createElement('ul', { 'class': 'user-list' },
  createElement('li', {}, 'Mike'),
  createElement('li', {}, 'Jack'),
);

所以我们的核心目的是构建vdom。而JSX只是一种通过引入新的语法糖,满足我们使用熟悉的声明式方式创建HTML的需要。

有些人认为JSX让代码充满’噪音‘,并且扰乱了编辑器的语法高亮(这一点与styled-component类似)。所有就有了HyperScript。有兴趣可以看一下。他相当于virtual-dom的h.js

这些vdom库都可以将构建的vdom渲染到某个真实DOM节点上:

function App() {
  return (
    <div className="App">
      <h1>Hello JSX!</h1>
    </div>
  );
}
ReactDOM.render(<App />, document.getElementById("root"));
//转换为
function App() {
  return React.createElement(
    "div",
    { className: "App" },
    React.createElement(
      "h1",
      null,
      "Hello JSX!"
    )
  );
}
ReactDOM.render(React.createElement(App, null), document.getElementById("root"));

如何学习JSX语法

除了官方文档:Introducing JSXJSX in DepthJSX规范

你可以还在Babel官网提供的REPL中测试JSX。将不明白的JSX粘上去,查看翻译后的JS代码就可以了。

比如:为什么JSX中只能写“表达式”?

从转换结果来看。JSX中的「代码」都被放在了createElement的参数中。自然只能使用表达式了:

var box =
  <Box>
    {
      shouldShowAnswer(user) ?
      <Answer value={false}>no</Answer> :
      <Box.Comment>
         Text Content
      </Box.Comment>
    }
  </Box>;

//转换为

var box = React.createElement(
  Box,
  null,
  shouldShowAnswer(user) ? React.createElement(
    Answer,
    { value: false },
    "no"
  ) : React.createElement(
    Box.Comment,
    null,
    "Text Content"
  )
);

我们import入react,React.createElement变为_react2.default.createElement了。

import React from 'react'
function f () {
  return <div>Hello JSX!</div>
}
//转换为
'use strict';
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function f() {
  return _react2.default.createElement(
    'div',
    null,
    'Hello JSX!'
  );
}

不仅仅是JSX

JSX是模拟HTML的写法。是不是可以模拟其他模板语言的写法,然后将至编译成对createElement的调用呢?当然!

babel-plugin-transform-react-pug 就是将pug转为JSX的Babel插件。

https://github.com/pugjs/babel-plugin-transform-react-pug/blob/master/src/index.js中可以看出:

这个插件处理TaggedTemplateExpression,然后查看是否以pug开头,即pug`div`。如果是,则将字符串内容用pug引擎处理得到html,然后再讲html转换成JSX。

React JSX vs Vue JSX

这里有一个国人写的将React组件定义转换为Vue组件的插件,从中可以学到另个框架在生命周期上的对应关系与不同。还有JSX写法的差异。

除了源码还需要关注仓库的Issue和测试代码。

https://github.com/vueact/babel-plugin-transform-react-to-vue

更多可能

通过编写Babel transform插件,可以为JSX添加新的功能,比如

https://github.com/adobe/babel-plugin-transform-react-twist

为JSX增加了<if>、<elseif>、<repeat>、<using>等功能

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top