通过配置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的还有
- https://github.com/developit/preact/
- https://github.com/Matt-Esch/virtual-dom
- https://github.com/anthonyshort/deku
- https://github.com/MithrilJS/mithril.js
通过配置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 JSX、JSX in Depth,JSX规范
你可以还在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>等功能