如果用 react创建一个App,在页面中输出app,那么大概的代码是这样的
现在我们尝试自己实现上述的api达到同样的效果,我们分成几个步骤来实现这个需求
1、vdom和渲染全部写死
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.js"></script>
</body>
</html>
main.js
javascript
//v1
const dom = document.createElement("div");
dom.id = "app";
document.querySelector("#root").append(dom);
const textNode = document.createTextNode("");
textNode.nodeValue = "app";
dom.append(textNode);
这样页面上就会渲染出app
2、vdom 动态生成,dom 渲染写死
其实虚拟dom就是一个js对象,type,props还有后代children 可以如下表示
javascript
//文本
const TxtEl = {
type: "TEXT_ELEMENT",
props: {
nodeValue: "app",
children: [],
},
};
//代表dom元素
const el = {
type: "div",
props: {
id:'app',
children: [TxtEl],
},
};
//渲染过程
const dom = document.createElement(el.type);
dom.id = el.props.id;
document.querySelector("#root").append(dom);
const textNode = document.createTextNode("");
textNode.nodeValue = TxtEl.props.nodeValue;
dom.append(textNode);
再进一步可以将vdom的生成写成一个函数
javascript
unction createTextNode(text){
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
}
}
//这里。。。形成了剩余参数,children就是一个数组
function createElement(type,props,...children){
return {
type,
props:{
...props,
children
}
}
}
//相应的上述代码就演变成
const App=createElement("div",{id:'App'},createTextNode("app"))
//创建节点
const dom = document.createElement(App.type);
//设置属性
dom.id = App.props.id;
//添加节点
document.querySelector("#root").append(dom);
//创建
const textNode = document.createTextNode("");
//设置
textNode.nodeValue = TxtEl.props.nodeValue;
//添加
dom.append(textNode);
至此vdom的创建已经可以动态生成,下面来改造渲染过程
3、vdom 动态生成,dom动态生成
仔细观察上述代码,不难发现整个渲染过程就是 1)创建dom 2)设置dom属性 3)添加dom到容器中 这三步,那么我们可以根据这个流程创建 render 函数
javascript
const render=(el,container)=>{
//createEl
const dom=el.type==="TEXT_ELEMENT"?document.createTextNode(""):document.createElement(el.type)
//设置pros
Object.keys(el.props).forEach((key)=>{
if(key!=='children'){
dom[key]=el.props[key]
}
})
//递归处理后代元素
const children=el.props.children
children.forEach(child=>{
render(child,dom)
})
//添加
container.append(dom)
}
const App=createElement("div",{id:'App'},createTextNode("app"))
//创建节点
const container = document.querySelector("#root");
render(App,container)
这样我们就完成了动态创建vdom和动态渲染的过程
#和React api 保持一致
首先我们对上述代码做一些小优化,目标是可以这样创建根组件App const App=createElement("div",{id:'App'},"app")
javascript
function createElement(type,props,...children){
return {
type,
props:{
...props,
children:children.map(child=>{
//对child作处理如果是 string 直接返回文本节点
return typeof child==='string'?createTextNode(child):child
})
}
}
}
接下去就是对api的一些改造,主要是要构造一个ReactDom对象,接受一个根容器,返回的对象中暴露render方法,render方法执行时接受根组件,然后再调用我们自己写的render方法将根容器和组件传入即可
javascript
const ReactDom={
createRoot(container){
return {
render(App){
render(App,container)
}
}
}
}
const App=createElement("div",{id:'App'},"app")
ReactDom.createRoot(document.querySelector("#root")).render(App)
大功告成!
最终代码
为了后续的开发,我们对代码做一些整理,结构如下
main.js
javascript
import ReactDom from "./core/reactDom.js";
import App from "./App.js";
ReactDom.createRoot(document.querySelector("#root")).render(App)
App.js
javascript
import React from "./core/React.js"
const App=React.createElement("div",{id:'App'},"app",' hi-',' minireact')
export default App
core/React.js
javascript
const render=(el,container)=>{
//createEl
const dom=el.type==="TEXT_ELEMENT"?document.createTextNode(""):document.createElement(el.type)
//设置pros
Object.keys(el.props).forEach((key)=>{
if(key!=='children'){
dom[key]=el.props[key]
}
})
const children=el.props.children
children.forEach(child=>{
render(child,dom)
})
//添加
container.append(dom)
}
function createElement(type,props,...children){
return {
type,
props:{
...props,
children:children.map(child=>{
return typeof child==='string'?createTextNode(child):child
})
}
}
}
function createTextNode(text){
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
}
}
const React={
render,
createElement
}
export default React
core/ReactDom.js
javascript
import React from "./React.js"
const ReactDom={
createRoot(container){
return {
render(App){
React.render(App,container)
}
}
}
}
export default ReactDom