react 为什么推荐 jsx
前言
作为一个 react 开发者,那么在开发的过程中,不可避免的会接触到 jsx 来编写组件代码。虽然可以编写 jsx 的框架越来越多,但是联系最深的还是 react。那么 jsx 到底是什么呢?又是如何变成 dom 进行页面渲染的呢?接下来一探究竟!
jsx 是什么
jsx 是 javascript 的一种语法扩展,和模板语言很接近,但是它充分具备 javascript 的能力。
简单来说就是把 js 的语法做了一定的扩展,让其支持编写 html 的语法,但是对于浏览器解析来说,并不能识别这个 jsx,那么就需要通过工具进行转译,这个工具就是 babel
转译的 jsx 变成了什么
首先来看一个 babel 转译的例子:
从图片可以看出,jsx 通过 babel 转译之后会变成通过 createElement 方法创建 react 组件。那么由此可以简单理解为 jsx 是 createElement 方法的语法糖,通过 jsx 的方式可以更加方便的写出 react 组件代码。那么简而言之,你也可以不使用 jsx 而是选择直接使用 react 提供的 createElement 方法来创建组件等,很显然笔者断定你不会这个样子做的,因为我懂你(其实是因为太麻烦),看下这个复杂的例子:
如果业务开发中,你选择 crateElement 来编写组件,那我相信你将拥有无限加班的可能,这就是为什么选择 jsx 的原因
所以 jsx 的好处是代码层次分明、嵌套关系清晰,而不像 createElement 代码一样让人感觉浑乱,读写都不够友好。,其实它是最大的简化开发流程,让开发者们编码体验更好。
为什么老版本的 react 中需要引入 react,而新版本不用?
其实通过 babel 转译代码这个层面,我想大家也看出了一点问题所在,其实原因就是在于,在转译完成之后,jsx 会变成 React.createElement 的形式,那么毫无疑问你必须要引入 react。
但是作为新版本的 react 引入了 react/jsx-runtime,编译工具会自动到 React 模块中获取并将代码编译,而不是以前的 React.createElement
import {jsx as _jsx} from 'react/jsx-runtime'
function App() {
return _jsx('h1',{children:'hello water'})
}
所以在 react 新版本中不再需要显示引入 react 了
jsx 转译后的元素样子
通过 react.createElement 把 jsx 转换成 react element,对应的有自己的转换规则和类型
jsx 元素类型 | react.createElement 转换后 | type 属性 |
---|---|---|
element 元素类型 | react element 类型 | 标签字符串,例如 div |
fragment 类型 | react element 类型 | symbol react.fragment 类型 |
文本类型 | 直接字符串 | 无 |
数组类型 | 返回数组结构,里面元素被react.createElement 转换 | 无 |
组件类型 | react element 类型 | 组件类或者组件函数本身 |
三元运算 / 表达式 | 先执行三元运算,然后按照上述规则处理 | 看三元运算返回结果 |
函数执行 | 先执行函数,然后按照上述规则处理 | 看函数执行返回结果 |
在 react 的底层,react element 对象的每一个节点都会形成一个与之对应的 fiber 对象,然后 fiber 通过 sibling、return、child 等指针将每一个 fiber 对象联系起来。
- sibling: 一个 fiber 指向下一个兄弟 fiber 的指针
- return: 一个子级 fiber 指向父级 fiber 的指针
- child:一个子级 fiber 指向子级 fiber 的指针
通过源码初探 react dom 的转换
首先扒一扒 createElement 方法的源码,看都做了什么
/**
React的创建元素方法
*/
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
}
// 此处将 key 值字符串化
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 (
// 筛选出可以提进 props 对象里的属性
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName]
}
}
}
// childrenLength 指的是当前元素的子元素的个数,减去的 2 是 type 和 config 两个参数占用的长度
const childrenLength = arguments.length - 2
// 如果抛去type和config,就只剩下一个参数,一般意味着文本节点出现了
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]
}
}
}
// 最后返回一个调用ReactElement执行方法,并传入刚才处理过的参数
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props)
}
这个函数有 3 个参数分别是 type,config,children
- type: 用于标识节点的类型
- config:以对象的形式传入,组件所有的属性都会以键值对的形式存储在 config 对象中
- children:以对象形式传入,主要记录组件标签之间嵌套的内容(子节点元素)
找个例子你可能更好理解
通过 createElement 的源码阅读可以描绘出一个函数执行所做事情的流程图
从中可以看出,createElement 方法就像是一个 react element 的处理器,通过开发者传入的一些数据作为依据,构建出一个 react element。
构建 element 时会去调用 ReactElement 方法,然后返回一个根据入参配置生成的 element,源码如下
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__) {
// 这里是一些针对 __DEV__ 环境下的处理,对于大家理解主要逻辑意义不大,此处我直接省略掉,以免混淆视听
}
return element;
};
整体理解为调用 createElement 方法,然后再调用 reactElement 方法最后生成 element 元素,返回渲染
小结
总体而言 jsx 就是 js 的一个扩展,然后 react 通过 babel 工具将 jsx 转译成 react 的语法,通过调用 createElement 和 reactElement 方法创建并返回出一个 element 元素。