JSX 渲染
是一种 JS 和 HTML 混合的语法,将组建的结构、数据甚至是样式都聚合在一起的写法
JSX 其实只是一种语法糖,最终会通过
babeljs转译成React.createElement语法js// 老版本需要手动引入React import React from "react"; let ele = <h1>ele</h1>; // 编译后的结果是 React.createElement(h1, null, 'ele')js// React17后新版本 let ele = <h1>ele</h1>; // 编译后会自动引入jsx // jsx('h1', null, 'ele')React.createElement会返回一个 React 元素React 元素事实上是普通的 JS 对象,用来描述你在屏幕上看到的内容
ReactDOM来确保浏览器中的真实 DOM 数据和 React 元素保持一致
JSX
text
<h1 className="title" style={{color:'red'}}>hello</h1>转译后的代码: 是由 babel 去处理的
text
React.createElement("h1", {
className: "title",
style: {
color: 'red'
}
}, "hello");返回的结果
React.createElement 的返回结果然后交由 ReactDOM 去处理
text
{
$$typeof: Symbol(react.element), // 元素类型
type:'h1', // 标签
props:{ // 属性
className: "title",
style: {
color: 'red'
}
},
children:"hello"
}JSX 的实现
src/index.js
js
import React from "./lib/react";
// import React from 'react';
import ReactDOM from "./lib/react-dom";
let element = (
<h1 id="test" className="h1" style={{ color: "red" }}>
hello<span> world</span>{" "}
</h1>
);
// babel会将其变成React.createElement('h1', null, 'hello')
ReactDOM.render(element, document.getElementById("root"));lib/react.js
js
import { REACT_ELEMENT } from "./element-types";
import { wrapToVdom } from "./utils";
// 把babel转换后的结果转成ReactDOM
function createElement(type, config, children) {
let key = config.key;
let ref = config.ref;
if (config) {
delete config.__source;
delete config._owner;
delete config.__self;
delete config.key;
delete config.ref;
}
let props = { ...config };
if (arguments.length > 3) {
// 如果参数大于3,说明子节点是数组
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);
}
return {
$$typeof: REACT_ELEMENT, // 表示是一个虚拟DOM,或者说是React元素
key,
ref,
type, // 虚拟DOM的类型
props, // 像事件,id,属性,class,style等都在这里面
};
}
const React = {
createElement,
};
export default React;lib/element-types.js
js
export const REACT_ELEMENT = Symbol("react.element"); // React元素
export const REACT_TEXT = Symbol("react.text"); // 纯文本lib/utils.js
js
import { REACT_TEXT } from "./element-types";
// 将纯本文包装成一个对象
export const wrapToVdom = (element) => {
const type = typeof element;
return ["string", "number"].includes(type)
? {
type: REACT_TEXT,
props: element,
}
: element;
};lib/react-dom.js
js
import { REACT_ELEMENT, REACT_TEXT } from "./element-types";
/**
* 要把虚拟DOM转换成真实DOM并且挂载到容器中
* @param {*} vdom 虚拟DOM
* @param {*} container 容器
*/
function render(vdom, container) {
mount(vdom, container);
}
function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
}
function createDOM(vdom) {
const { props, type } = vdom;
let dom; // 真实的DOM节点
if (type === REACT_TEXT) {
dom = document.createTextNode(props); // props是一个字符串
} else {
dom = document.createElement(type);
}
if (props) {
// 如果props存在,更新属性
updateProps(dom, {}, props);
if (typeof props.children === "object" && props.children.type) {
// 说明只有一个子节点
mount(props.children, dom); // 递归渲染子节点
} else if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
}
}
return dom;
}
function reconcileChildren(children, dom) {
children.forEach((child) => {
mount(child, dom);
});
}
function updateProps(dom, oldProps = {}, newProps) {
for (let key in newProps) {
// 先遍历新的属性
if (key === "children") continue;
if (key === "style") {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else {
// dom.className = 'title'
dom[key] = newProps[key];
}
}
// 再遍历老属性,如果新属性中没有直接删除掉
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
const ReactDOM = {
render,
};
export default ReactDOM;