不同react版本对jsx的转换方式:
React17以前
React.createElement("h1", {
id: "title",
key: "title",
ref: "title"
}, "hello");
复制代码
React17之后
import { jsx as _jsx } from "react/jsx-runtime";
let element = _jsx("h1", {id: "title",key:"title",ref:"title",children: "hello"}, "title");
复制代码
这么做的原因:
-
如果在文件里没有引入 React
-
但经过babel会转换为
-
React.createElement();
-
如果你引入了React
-
在编写代码的阶段,因为代码没有用到React变量,有些工具eslint会报
通常,根组件中都通过render来渲染所有元素
ReactDOM.render(
element, document.getElementById('root')
)
复制代码
createElement的实现:
./utils:
export function wrapToVdom(element) {
return typeof element === 'string' || typeof element === 'number' ?
{ type: REACT_TEXT, props: { content: element } } : element;
}
复制代码
react.js:
import { wrapToVdom } from './utils'
/**
* createElement('h1',null,'a','b');
* 创建一个虚拟DOM,也就是一个React元素
* @param {*} type 元素的类型span div p
* @param {*} config 配置对象 className style
* @param {*} children 儿子,有可能独生子(对象),也可能是多个(数组)
*/
function createElement(type, config, children) {
let ref;//可以通过 ref引用此元素
let key;//可以唯一标识一个子元素
if (config) {
delete config.__source;
delete config.__self;
ref = config.ref;
key = config.key;
delete config.ref;//props里没有ref属性的
delete config.key;
}
let props = { ...config };//props里没有key的
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);//children可能是React元素对象,也可能是一个字符串 数字 null undefined
}
return { $$typeof: REACT_ELEMENT, type, ref, key, props };//React元素
}
const React = {
createElement
}
export default React;
复制代码
react-dom.js:
import { REACT_TEXT } from "./constants";
/**
* 把虚拟DOM变成真实DOM插入到容器内部
* @param {*} vdom 虚拟DOM
* @param {*} container 容器
*/
function render(vdom, container) {
mount(vdom, container);
}
function mount(vdom, parentDOM) {
let newDOM = createDOM(vdom)
if (newDOM) {
parentDOM.appendChild(newDOM)
}
}
/**
* 把虚拟DOM转成真实DOM
*/
function createDOM(vdom) {
if (!vdom) return null;
let { type, props } = vdom;
let dom;//真实DOM
if (type === REACT_TEXT) {//如果这个元素是一个文本的话
dom = document.createTextNode(props.content);
} else {
dom = document.createElement(type);// div span p
}
//处理属性
if (props) {
updateProps(dom, {}, props);
if (props.children) {
let children = props.children;
if (typeof children === 'object' && children.type) {//说明这是一个React元素
mount(children, dom);
} else if (Array.isArray(children)) {
reconcileChildren(props.children, dom);
}
}
}
vdom.dom = dom;//让虚拟DOM的dom属性指向这个虚拟DOM对应的真实DOM
return dom;
}
function reconcileChildren(childrenVdom, parentDOM) {
childrenVdom.forEach(childVdom => mount(childVdom, parentDOM));
}
/**
* 把新的属性更新到真实DOM上
* @param {*} dom 真实DOM
* @param {*} oldProps 旧的属性对象
* @param {*} newProps 新的属性对象
*/
function updateProps(dom, oldProps, newProps) {
for (let key in newProps) {
if (key === 'children') {//children
continue;//此处忽略子节点的处理
} else if (key === 'style') {//style
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (key.startsWith('on')) {
dom[key.toLocaleLowerCase()] = newProps[key];
} else {
dom[key] = newProps[key];//className
}
}
}
const ReactDOM = {
render
}
export default ReactDOM;
复制代码
createElement是经过babel转换调用的方法,通常以React.createElement的形式出现
而createDOM通常是将createElement的结果作为入参,生成最终的DOM
createDOM的调用链通常是render -> mount -> createDOM
每个节点的处理都分为
1、创建这个节点(createDOM)
2、将这个节点append到父组件中
这两个过程被抽象为mount方法
mount的调用分为两种场合,我们需要关注一下每种场合下,元素创建好以后append到哪个父组件这个:
1、对于根组件来说,append到哪个父组件是个初始化的参数
2、对于递归创建的子组件来说,是在父组件创建完后才知道append到谁里面
所以对于递归创建的子组件,它们的mount过程是在父组件create好了以后调用的,即是在父组件的createDOM种调用的
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END