前言
OK,这里是分享与爱的大鹅鹅鹅,今天我们瞬间移动,来到react源码解析栏目,我们来学习JSX与DOM的本质关系,以及虚拟dom的产生实现。消息就是这样,看看能学到点啥知识。
JSX代码如何转换成DOM?
JSX的本质是什么?它和js之间的关系?
JSX的本质是js的语法扩展,JSX充分具备js的能力。
为何要用JSX?
因为JSX的本质是React.createElement,JSX语法糖允许开发者使用我们最熟悉的类HTML标签来创建虚拟DOM,避免手动编写React.createElement
代码,降低学习成本的同时,也提升了研发效率与研发体验。
JSX背后的功能模块是什么?这个功能模块做了什么事?
JSX语法是通过Babel编译后,在js中生效的。
Babel将JSX编译成React.createElement()
,而React.createElement()将返回一个叫做“React Elememt”的js对象。
JSX本质是React.createElement这个js调用的语法糖,这个完美的呼应了React官方给出的“JSX充分具备js的能力”这句话。
虚拟dom如何产生的?
先说结论,调用React.createElement方法返回ReactElement对象实例,ReactElement对象实例是以js对象存在,表示为DOM的描述,也就是“虚拟DOM”。
React.createElement()源码解析
方法入参格式:
function createElement(type, config, children)
三个参数基本含义:
- type(节点类型) ;
- config(元素的属性);
- children(子元素可能有多个,从第三个入参开始往后,传入的参数都是children);
从源码可以看到,createElement
函数主要做了三件事:
- 处理 config;
- 构造 props(3步:提取config,处理子元素、处理默认值);
- 返回 ReactElement函数的调用;
具体来说:
- 首先声明 propName、props、key、ref、self、source 等变量;
- 依次对 ref、key、self 和 source 属性赋值,其中 key 会强制转换为字符串;
- 开始构造 props,首先遍历 config 对象,筛选出可以提取到 props 中的属性;
- 处理子元素,生成 props.children,这里分为一个和多个子元素两种情况;
- 处理 defaultProps,如果有默认属性值则覆盖;
- 传入处理好的参数,返回一个 ReactElement 函数的调用;
这里来看下createElement
函数的源码实现:
export function createElement(type, config, children) {
let propName;
// Reserved names are extracted
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;
// Remaining properties are added to a new props object
for (propName in config) {
// 筛选出可以提进 props 对象里的属性
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
const childrenLength = arguments.length - 2;
// 如果抛去type和config,就只剩一个参数,一般意味着文本节点出现了
if (childrenLength === 1) {
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 = childArray;
}
// Resolve default 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,
);
}
通过阅读源码,你就会发现createElement
函数的“工作量”并没有多大,没有十分复杂的逻辑和算法,每一个步骤几乎都是在格式化数据。
说直白点,createElement
就是开发者和ReactElement
调用之间的一个“转换器”。
接着再来说ReactElement函数。
ReactElement函数源码解析
ReactElement函数主要的工作就是“创建”,说白点就是ReactElement把传入的参数按照一定的规范,“组装”进了一个 ReactElement对象并将其返回给了React.createElement。
ReactElement 的源码如下:
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
if(__DEV__) {
// do something...
}
return element;
};
如上源码,ReactElement对象实例,本质是以js对象形式存在,表示为DOM的描述,也就是“虚拟DOM”。
这里我们可以验证,这里我们写入如下示例,打印出JSX的部分:
const APP = <div calssName='App'>
<h1 calssName='title'>I am the title</h1>
<p calssName='content'>I am the content</p>
</div>
console.log(App)
控制台输出如下:
以上就是“虚拟DOM”的描述。
虚拟 DOM到真实 DOM
而如何根据虚拟 DOM是,构建渲染到页面上的真实 DOM ,这个需要ReactDOM.render
方法来实现:
const virtualElement = React.createElement('div', null);
const rootElement = document.getElementById("root");
ReactDOM.render(virtualElement, rootElement);
结语
以上就是本篇的全部内容,如果你觉得此文对你有一点点帮助,不妨点个赞,一键三连,鼓励一下大鹅鹅鹅,O(∩_∩)O哈哈~