1.主要涉及的API
ReactDom.render()React.createElement()React.Component
2.JSX
Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。执行步骤:
ReactDom.render(vnode,container) =>
React.createElement(vnode) => // jsx转译
container.appendChild(node) // 转换为真实节点接下来会做一个简单的模拟过程。
3.项目准备
- 安装
create-react-app - 新建
kreact.js,kreact-dom.js文件放入k文件夹 - 模拟改写
index.js文件 - 在
index.js文件中引入我们自己的react,react-dom文件,并且设置三种类型的组件,函数组件,class组件,html标签;再准备一个子元素为数组类型的组件;调用ReactDom.render();
index.js如下:import React, { Component } from "./k/kreact";
import ReactDOM from "./k/kreact-dom";
import "./index.css";
const list = [
{ id: 1, name: "第一行" },
{ id: 2, name: "第二行" },
{ id: 3, name: "第三行" },
];
// 函数组件
function FunComp() {
return (
<div
name="函数组件"
className="red"
onClick={() => {
console.log("click");
}}
>
函数组件
<div>
{list.map((v) => (
<div key={v.id}>{v.name}</div>
))}
</div>
</div>
);
}
// jsx
const jsx = (
<div name="dom组件" className="blue">
dom组件
</div>
);
// class组件
class ClassComp extends Component {
render() {
return (
<div name="类组件" className="green">
类组件
</div>
);
}
}
ReactDOM.render(
<div>
{jsx}
<FunComp />
<ClassComp />
</div>,
document.getElementById("root")
);4.模拟React.createElement()4.模拟React.createElement()
kreact.js文件:
// react jsx通过createElement方法转换为节点树
function createElement(type, props, ...children) {
props.children = children;
delete props.__source;
delete props.__self;
// 类组件和函数组件的type都返回function,
// 因此可以在class组件继承的Component组件中设置静态属性以区分类组件和函数组件
let vtype = ""; // 虚拟dom类型,1-html标签,2-类组件,3-函数组件
if (typeof type === "string") {
vtype = 1;
} else if (type.isComponent) {
vtype = 2;
} else {
vtype = 3;
}
return {
vtype,
type,
props,
};
}
export class Component {
// class组件的标志,对象方便以后进行扩展,也可以是布尔值。
static isComponent = {};
constructor(props) {
this.props = props;
this.state = {};
}
}
export default { createElement };
5.模拟ReactDom.render()
kreact-dom.js文件:
function render(vnode, container) {
// 虚拟节点转为真实节点
const node = mount(vnode);
container.appendChild(node);
}
// 节点树转换为虚拟节点
function mount(vnode) {
switch (vnode.vtype) {
// html标签
case 1:
return createHtmlNode(vnode);
// class组件类型
case 2:
return createClassNode(vnode);
// 函数组件类型
case 3:
return createFunNode(vnode);
// 文本类型(vnode为文本)
default:
return createTextNode(vnode);
}
}
// 创建文本节点
function createTextNode(vnode) {
return document.createTextNode(vnode);
}
// 创建html dom节点
function createHtmlNode(vnode) {
const { type, props } = vnode;
let node = document.createElement(type);
// props把特殊的关键词单独拿出来,剩下的为props属性
const { className, children, ...rest } = props;
// className属性
if (props.className) {
node.setAttribute("class", props.className);
}
// 其余属性一一对应写上去就可以了
Object.keys(rest).forEach((item) => {
// 监听事件
if (item.startsWith("on")) {
node.addEventListener(
item.toLocaleLowerCase().replace("on", ""),
rest[item]
);
} else {
node.setAttribute(item, rest[item]);
}
});
children.forEach((item) => {
// 子内容为数组时
if (Array.isArray(item)) {
item.forEach((v) => node.appendChild(mount(v, node)));
} else {
node.appendChild(mount(item, node));
}
});
return node;
}
// class组件的vtype为class,需要new创建实例再调用实例的render方法获得返回的vnode,
// 再转换为html标签
function createClassNode(vnode) {
const { type } = vnode;
let instance = new type();
return mount(instance.render());
}
// 函数组件的vtype为函数,需要调用方法获得返回的vnode,再转换为html标签
function createFunNode(vnode) {
const { type } = vnode;
return mount(type());
}
export default { render };