React系列: jsx转化为虚拟DOM
直接进入主题。
const element = <div>copyer</div>
上面的标签语法既不是字符串也是HTML,被称为JSX
,JavaScript的语法扩展。
React中推荐使用JSX
语法。(当然,其他框架也是可以使用的,比如现在的vue3中,也是支持JSX语法的)。
为什么要使用JSX语法?
React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。(官方原话)
那么使用JSX语法,react内部是怎么解析的呢?转化为真实的DOM。
React 16版本
jsx
语法通过babel
转化为React.createElement
函数调用,生成虚拟对象。
JSX
形式
function App() {
return <h1 className='ppt'>Hello World</h1>;
}
React.createElement
函数形式
"use strict";
function App() {
return /*#__PURE__*/React.createElement("h1", {
className: "ppt"
}, "Hello World");
}
React.createElement参数分析
/**
*
* @param {*} type 元素的类型
* @param {*} config 配置对象
* @param {*} children 第一个儿子,如果有多个,依次放在后面
*/
function createElement(type, config, children) {}
React 17版本
React 17 提供了一个全新的,重构过的 JSX 转换的版本。jsx语法不再化为 React.createElement
函数,而是内部通过 react/jsx-runtime
中jsx
函数生成虚拟对象。
jsx 新的转化,也是通过 babel完成的。两种方式
@babel/plugin-transform-react-jsx
// If you're using @babel/plugin-transform-react-jsx
{
"plugins": [
["@babel/plugin-transform-react-jsx", {
"runtime": "automatic"
}]
]
}
@babel/preset-react
// If you are using @babel/preset-react
{
"presets": [
["@babel/preset-react", {
"runtime": "automatic"
}]
]
示例:
jsx
形式
function App() {
return <h1 className='ppt'>Hello World</h1>;
}
new JSX transform
形式
// Inserted by a compiler (don't import it yourself!)
import {jsx as _jsx} from 'react/jsx-runtime';
function App() {
return _jsx('h1',{ className: 'ppt', children: 'Hello world' });
}
对比
createElement
函数与jsx
函数的区别
参数的不同
第一个参数,都是元素的类型。
createElement函数
第二个参数:元素的配置对象; 第三个参数,表示它的第一个子节点,如果有多个子节点,就依次的从四个参数往下方。(简单的来说,从第三个参数开始,都是元素的子节点)jsx函数
,第二个参数就是一个对象,里面包含着元素的配置对象({className: 'aa', children: []}
)。children属性就是表示该元素的子节点。如果只有一个,就是react元素;如果有多个,就是一个数组,里面存放着所有的子节点。
新的jsx transform
的好处
- 在React16版本,每个组件都必须导入React,不然就会报错。在新的jsx transform中不用导入。(因为新的 JSX 转换会自动引入必要的
react/jsx-runtime
函数,因此当你使用 JSX 时,将无需再引入 React。) - JSX 的编译输出可能会略微改善 bundle 的大小。
createElement
函数没有废弃
尽管新的 jsx 编译已经出来,但是并没有废弃 createElement函数。
如果想要使用js
创建元素,还是要使用 createElement。
// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';
源码分析
createElement函数源码
packages/react/src/ReactElement.js
// 保留的props
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};
// react元素的类型
const REACT_ELEMENT_TYPE = Symbol.for("react.element");
// 判断是不是有效的ref
function hasValidRef(config) {
return config.ref !== undefined;
}
// 判断是不是有效的key
function hasValidKey(config) {
return config.key !== undefined;
}
const ReactCurrentOwner = {
current: null, // (null: null | Fiber)
};
// react元素对象(虚拟节点)
const ReactElement = function (type, key, ref, self, source, owner, props) {
const element = {
// react元素的唯一标识
$$typeof: REACT_ELEMENT_TYPE,
// react元素的属性
type: type,
key: key,
ref: ref,
props: props,
// react元素的创建者
_owner: owner,
};
return element;
};
/**
*
* @param {*} type 元素的类型
* @param {*} config 配置对象
* @param {*} children 第一个儿子,如果有多个,依次放在后面
*/
function createElement(type, config, children) {
let propName;
// 定义props对象
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
if (config != null) {
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;
// 保留属性,添加到一个新的对象中
for (propName in config) {
// 判断 config的属性是不是保留属性,不是保留属性,就添加到对象中
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName) // reserved_props
) {
props[propName] = config[propName];
}
}
}
// 把children也添加到新的对象中
// 根据函数的参数个数,判断children是否存在
const childrenLength = arguments.length - 2;
// 如果为1,表明有一个children,就是直接赋值给新对象的children属性
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
// 如果有多个儿子,就放到一个数组中,children的value值就是该数组
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
// 去掉前面的两个参数
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}
// 判断该元素有不有type和默认的props(针对类组件)
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props
);
}
jsx函数源码
packages/react/src/ReactJSXElement.js
export function jsx(type, config, maybeKey) {
let propName;
const props = {};
let key = null;
let ref = null;
// maybeKey 就是处理一种key写法的现象
// <div {...props} key='hi'></div> 或则 <div key='hi' {...props}></div>
// props中可能包含key,按照第二种写法,props中的key就会原本节点上的key(同一个对象上)
// jsxDEV期望的是 <div {...props} key='hi'></div>,确定本节点上的key
if (maybeKey !== undefined) {
key = '' + maybeKey;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
if (hasValidRef(config)) {
ref = config.ref;
}
// 过滤掉: 预留的props属性
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
return ReactElement(
type,
key,
ref,
undefined,
undefined,
ReactCurrentOwner.current,
props,
);
}
发现没有,其实两个函数的源码基本上差不多,就是在处理子节点的有点不同。createElement是单独处理的,jsx函数根本就没有处理,直接赋值(因为 children并不预保留属性,所以会直接赋值给props)
总结
jsx被babel解析成虚拟对象
。
jsx如果包含子节点,子节点也会被解析,依次下去,就会形成一个深层次的对象,这个对象被称为虚拟DOM
。所以虚拟DOM就是一个对象。
如果上面写的有误,请指教~~~
转载自:https://segmentfault.com/a/1190000041789504