前言
JSX(JavaScript XML)是学习 React 框架必须要理解的一个概念,尽管当初越来越多的人认可与反对 JSX 语法的框架(比方 Vue.js),然而当 React 首次带着 JSX 公布之时,还是引起了很多争议,甚至我的一个敌人大呼这是开历史倒车。随着 React 框架的不断完善,JSX 也被越来越多的开发者所认可,变得越来越风行,当得了一句“真香”!
本次分享会从上面三个问题登程:
- JSX 是什么?
- JSX 作用是什么?
- JSX 背地做了哪些事件?
在分享的过程中,我会通过一些 Demo 代码解说以及解读源码的形式尝试着去寻找答案,心愿通过这样的形式帮忙你意识 JSX,并从根本上了解 JSX,从而帮忙咱们写出更好的 React 代码。
JSX 的实质:JavaScript 的语法扩大
咱们先看一下 React 官网是如何定义 JSX 的:
■ JSX 是一个 JavaScript 的语法扩大,它看起来像是一种模板语言,但它具备 JavaScript 的全副性能。
刚开始接触 JSX 的同学个别会认为这是一种新的模板语言,因为它与个别的模板语言十分相像。上面咱们通过三段作用雷同的代码来直观的感受一下:
// 定义一些数据
const title = ‘hello’
const list = [‘a’, ‘b’, c]
const visible = true
const footCssName = ‘footer’
Vue 模板:
<div className=”app”>
<h1 className=”title”>{{title}}</h1>
<section className=”content”>
<p v-for=”item in list”>{{item}}</p>
</section>
<footer v-if=”visible” :class=”footCssName”>foot</footer>
</div>
EJS 模板:
<div class=”app”>
<h1 class=”title”><%= title %></h1>
<section class=”content”>
<% list.forEach(function(item) {%>
<p><%= item %></p>
<% }) %>
</section>
<% if (visible) {%>
<footer class=”<%= footCssName %>”>foot</footer>
<% } %>
</div>
JSX 语法:
<div className=”app”>
<h1 className=”title”>{title}</h1>
<section className=”content”>
{list.map((item) => <p key={item}>{item}</p>)}
</section>
{visible && (
<footer className={footCssName}>foot</footer>
)}
</div>
实际上 JSX 并不是模板语言,而是对 JavaScript 语言的扩大。JSX 基于 JavaScript 语言,所以它具备 JS 的全副能力(JS 能做的 JSX 都能够做,比方表达式计算),但又新增了某些能力(JS 不能做的 JSX 也能够做,比方自定义组件)。
既然说 JSX 是 JavaScript 的扩大,那么咱们的浏览器就没方法解析原始的 JSX 代码,因为不意识,那么 JSX 是如何失效的呢?其实 React 官网曾经给出了答案(证实官网文档是值得重复研读的)
■ JSX 会被编译为 React.createElement(),React.createElement() 将返回一个叫作“React Element”的 JS 对象。
JSX 须要被编译,而编译这个动作就是由 Babel 来实现的。
Babel
■ Babel 是一个工具链,次要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便可能运行在以后和旧版本的浏览器或其余环境中。
// 编译前
const firstName = ‘Dan’
const lastName = ‘Abramov’
conso.log(hello ${firstName} ${lastName} !
)
// 编译后
var firstName = “Dan”;
var lastName = “Abramov”;
conso.log(“hello “.concat(firstName, ” “).concat(lastName, ” !”));
更多的内容能够通过拜访 Babel 官网进行学习理解。咱们只须要晓得,Babel 就是一个转译工具,它具备把 JSX 语法转换为 JavaScript 代码的能力。
// https://babeljs.io/repl/
<div className=”app”>
<h1 className=”title”>{title}</h1>
<p>content</p>
</div>
通过 Babel REPL 咱们能够看到,JSX 中标签都被转换成了 React.createElement 函数调用,也就是说其实写 JSX 就是在写 React.createElement 函数,换句话说就是,JSX 就是 React.createElement 函数调用的语法糖。
JSX 的作用:更好的 React 应用体验
既然 JSX 只是 React.createElement 函数的语法糖,那么为什么不间接应用 React.createElement 来编写代码呢,这样还能省一个 Babel 编译的步骤。为了直观的了解应用 JSX 的益处,咱们还是看上面的代码:
<div className=”app”>
<div className=”app__one”>
One text
<div className=”app__two”>
Second text
<div className=”app__three”>
<h2>Three text</h2>
</div>
<div className=”app__four”>
<p>Four text</p>
</div>
</div>
</div>
<Hello name=”woqutech” />
</div>
// ===>
// /#__PURE__/ 标记是 Babel 工具加上的
// 次要用途就是通知前面的代码压缩插件,此处代码是无副作用的代码,能够释怀优化(删除)
// 具体能够看:https://laysent.com/til/2019-…
/#__PURE__/
React.createElement(
‘div’,
{
className: ‘app’,
},
/#__PURE__/ React.createElement(
‘div’,
{
className: ‘app__one’,
},
‘One text’,
/#__PURE__/ React.createElement(
‘div’,
{
className: ‘app__two’,
},
‘Second text’,
/#__PURE__/ React.createElement(
‘div’,
{
className: ‘app__three’,
},
/#__PURE__/ React.createElement(‘h2’, null, ‘Three text’)
),
/#__PURE__/ React.createElement(
‘div’,
{
className: ‘app__four’,
},
/#__PURE__/ React.createElement(‘p’, null, ‘Four text’)
)
)
),
/#__PURE__/ React.createElement(Hello, { name: “woqutech”})
)
React 框架有一个驰名的公式:UI=render(data),这个公式也是 React 理念的体现。用户看到的界面(UI),应该是一个函数(render)的执行后果,这个函数只承受数据(data)作为参数,且这个函数是一个纯函数(无副作用,输入齐全依赖于输出,雷同的输出肯定失去雷同的输入)。
React 认为渲染逻辑实质上与其余 UI 逻辑是外在耦合的,比如说在 UI 中绑定解决工夫,触发回调更新数据,以及在 UI 中展现更新后的数据等。如果仅仅通过纯 JavaScript 代码来形容 UI,那么在简单的 UI 界面下,写进去的代码可读性十分差,只有应用 JSX 这种基于 JavaScript 语言并具备标记语言(Markup Language)个性的非凡语法,让咱们应用最为相熟的 HTML(HyperText Markup Language,超文本标记语言)标签语法来创立虚构 DOM,能力在升高学习老本的同时,进步代码可读性(层次分明,嵌套关系清晰)以及编码效率(借助工具主动补全及谬误提醒)之间获得均衡,这也是 JSX 越来越风行的一个重要起因。
JSX 背地的解决:创立虚构 DOM 节点
前文提到 JSX 是 React.createElement 函数的语法糖,那么 React.createElement 函数又是做什么的呢?让咱们从 React 源码中一探到底。
留神:本文摘取的 React 源码都来自于 16.8.6 版本。
·小技巧
在 github repo 页面,通过单击键盘上的 . 键,能够启动在线 IDE 模式,实现更好的代码浏览体验。
// packages/react/src/ReactElement.js
// createElement 有 3 个入参,这 3 个入参蕴含了 React 创立一个元素所须要晓得的全副信息
// type:用于标识节点的类型。它能够是相似“h1”“div”这样的规范 HTML 标签字符串,也能够是 React 组件类型或 React fragment 类型
// config:以对象模式传入,组件所有的属性都会以键值对的模式存储在 config 对象中
// children:以对象模式传入,它记录的是组件标签之间嵌套的内容,也就是所谓的“子节点”“子元素”
export function createElement(type, config, children) {
// propName 变量用于贮存前面须要用到的元素属性
let propName
// props 变量用于贮存元素属性的键值对汇合
const props = {}
// key、ref、self、source 均为 React 元素的属性
let key = null
let ref = null
let self = null
let source = null
// config 对象中存储的是元素的属性
if (config != null) {
// 进来之后做的第一件事,是顺次对 ref、key、self 和 source 属性赋值
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]
}
}
}
// childrenLength 指的是以后元素的子元素的个数
// 减去的 2 是 type 和 config 两个参数占用的长度
const childrenLength = arguments.length – 2
// 如果只剩下一个参数,那么个别就是 文本节点
if (childrenLength === 1) {
// 间接把这个参数的值赋给 props.children
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
props.children = childArray
}
// 解决 defaultProps
if (type && type.defaultProps) {
const defaultProps = type.defaultProps
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName]
}
}
}
if (__DEV__) {
// …
}
// 最初返回一个调用 ReactElement 执行办法,并传入方才解决过的参数
// ReactCurrentOwner 是虚构 DOM 中辨认自定义组件的要害
// 具体能够看:https://que01.top/2019/06/28/…
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props)
}
createElement 函数做的事件其实不简单,简略来说就是格式化数据,给 ReactElement 筹备参数,算是一个数据处理层,真正“干活”的还是 ReactElement。
// packages/react/src/ReactElement.js
const ReactElement = function (type, key, ref, self, source, owner, props) {
const element = {
// REACT_ELEMENT_TYPE 是一个常量,用来标识该对象是一个 ReactElement
$$typeof: REACT_ELEMENT_TYPE,
// 内置属性赋值
type: type,
key: key,
ref: ref,
props: props,
// 记录发明该元素的组件
_owner: owner,
}
if (__DEV__) {
// ...
}
return element
}
ReactElement 函数的代码比拟简短,除去 __DEV__ 解决的逻辑代码根本就没有别的内容,从理论代码来看,这里做的事件就是依照肯定的标准组装 element 对象,并且返回这个组装好的对象。
这里的 ReactElement 对象又是什么呢?其实它实质上是以 JavaScript 对象模式存在的对 DOM 的形容,也就是咱们常常说的虚构 DOM(精确说应该是虚构 DOM 中的一个节点)。虚构 DOM 和实在的 DOM 还是有肯定的差异,而这个差异就是靠 ReactDOM.render 办法来填补。
// ReactDOM.render(<App />, document.getElementById(‘root’))
ReactDOM.render(
// 须要渲染的元素(ReactElement)
element,
// 元素挂载的指标容器(一个实在 DOM 节点)
container,
// 回调函数,可选参数,能够用来解决渲染完结后的逻辑
[callback]
)
至此,整个 JSX 到实在 DOM 的流程咱们就算走完了,而通过解读源码咱们也能看到这块内容其实比较简单,对于咱们了解 JSX 没有阻碍。
总结
通过浏览本文,我置信大家应该能轻松容易答复开篇提出的 3 个问题了,也欢送大家把本人的心得或疑难写在评论区,不便咱们进一步探讨。
参考资料
https://babeljs.io/docs/en/ba…
https://laysent.com/til/2019-…
https://que01.top/2019/06/28/…