JSX 不是魔法,是语法糖
很多同学天天写 JSX,却很少想过:这玩意儿到底是怎么变成 React 认识的对象的?这一篇我们从编译到运行时,把 JSX → ReactElement 的完整链路拆开看一遍。
浏览器不认识 JSX。Babel 的 @babel/plugin-transform-react-jsx 会在编译期把它转成函数调用:
- React 17 之前:
React.createElement(type, props, ...children) - React 17+ 新 JSX transform:
jsx(type, props, key),不再强制引入 React
JSX// 源码
<div className="box">Hello</div>
// 编译后(React 17+)
jsx('div', { className: 'box', children: 'Hello' })
ReactElement 长什么样
jsx / createElement 最终返回一个普通的 JS 对象:
JAVASCRIPT{
$$typeof: Symbol.for('react.element'),
type: 'div',
key: null,
ref: null,
props: { className: 'box', children: 'Hello' },
_owner: null
}
注意 $$typeof,它是一个 Symbol。为什么要它?防 XSS:服务端如果返回伪造的 JSON,JSON 里不可能有 Symbol,React 就能识别出「这不是我生成的元素」,直接拒绝渲染。
递归构建整棵树
JSX 的嵌套在编译后体现为 children 的层层传递:
JSX// 源码
<div><span>a</span><span>b</span></div>
// 编译后
jsx('div', {
children: [
jsx('span', { children: 'a' }),
jsx('span', { children: 'b' }),
],
})
一次渲染调用,就会递归创建出整棵 ReactElement 树——这就是常说的「虚拟 DOM」。不过它还只是静态描述,真正的调度与渲染,要等 Fiber 接手。
小结
JSX → ReactElement 是 React 的第一公里:编译期负责「翻译」,运行期负责「建树」。理解了这棵树,下一期我们来看它如何进入 Fiber 的世界,成为可中断、可复用的调度单元。