手动实现react.js
2019-11-09 本文已影响0人
key君
index.js
引入了自定义的React和ReactDOM,定义了jsx模板,里面有class组件(继承自定义的React.Component),原生标签和function组件,使用了自定义ReactDOM.render方法渲染模板
kReact/index.js
引入了自定义Component,实现createElement方法,根据传入的type判断是什么节点,并保存children到props上
react-dom.js
把传入的虚拟节点转化为真实节点 append到容器上
virtual-dom.js
根据传入的虚拟节点,取出他们各自的类型 用不同的方法生成真实dom
Component.js
定义了Component class保存传入的props 增加属性isReactComponent以区分是class组件还是function组件 实现了setState,setState内部执行forceUpdate方法
diff.js
把传入的新虚拟节点转化为真实节点 替换旧的节点
index.js
// import React, { Component } from "react";
// import ReactDOM from "react-dom";
// import React from "./kkreact/";
// import ReactDOM from "./kkreact/ReactDOM";
import React from "./kReact";
import ReactDOM from "./kReact/react-dom";
import "./index.css";
class ClassCpm extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
}
handle = () => {
this.setState({
counter: this.state.counter + 1,
});
console.log("handle", this.state.counter);
};
render() {
return (
<div className="border">
{this.props.name}
<button onClick={this.handle}>{this.state.counter}</button>
{[0, 1, 2].map(item => {
return <FuncCmp key={item} name={"function组件" + item} />;
})}
</div>
);
}
}
function FuncCmp(props) {
return <div className="border">{props.name}</div>;
}
let jsx = (
<div className="box border">
<p className="border">这是React</p>
<FuncCmp name="function组件" />
<ClassCpm name="class组件" />
</div>
);
ReactDOM.render(jsx, document.getElementById("root"));
index.css
body {
margin: 20px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.border {
margin: 10px;
padding: 10px;
border: solid 1px red;
}
src/kReact/index.js
import Component from "./Component";
function createElement(type, props, ...children) {
// console.log("arg", arguments);
props.children = children;
let vtype; //如果是文本节点, 就是undefined
if (typeof type === "string") {
//原生标签
vtype = 1;
} else if (typeof type === "function") {
//class: 2, function: 3
vtype = type.isReactComponent ? 2 : 3;
}
return {
vtype,
type,
props,
};
}
export default {
createElement,
Component,
};
src/kReact/react-dom.js
import { initVnode } from "./virtual-dom";
function render(vnode, container) {
//vnode->node
const node = initVnode(vnode, container);
container.appendChild(node);
}
export default {
render,
};
src/kReact/Component.js
import { diff } from "./diff";
class Component {
static isReactComponent = {};
constructor(props) {
this.props = props;
this.$cache = {};
this.state = {};
}
setState = (nextState, callback) => {
///这是个假的setState
this.state = {
...this.state,
...nextState,
};
this.forceUpdate();
};
forceUpdate = () => {
console.log("setState");
let newVnode = this.render();
const newNode = diff(this.$cache, newVnode);
//vnode newVnode ->node
this.$cache = {
...this.$cache,
vnode: newVnode,
node: newNode,
};
};
}
export default Component;
src/kReact/virtual-dom.js
export function initVnode(vnode, container) {
//vnode->node
const { vtype } = vnode;
let node;
if (!vtype) {
node = initTxtNode(vnode, container);
}
if (vtype === 1) {
//原生标签
node = initHtmlNode(vnode, container);
}
if (vtype === 2) {
//class组件
node = initClassNode(vnode, container);
}
if (vtype === 3) {
//function组件
node = initFunctionNode(vnode, container);
}
return node;
}
function initTxtNode(vnode, container) {
const node = document.createTextNode(vnode);
return node;
}
//原生vnode->node
function initHtmlNode(vnode, container) {
const { type, props } = vnode;
const node = document.createElement(type);
const { children, ...rest } = props;
children.map(item => {
if (Array.isArray(item)) {
item.map(c => {
node.appendChild(initVnode(c, node));
});
} else {
node.appendChild(initVnode(item, node));
}
});
for (let key in rest) {
if (key === "className") {
node.setAttribute("class", rest[key]);
} else if (key.slice(0, 2) === "on") {
node.addEventListener("click", rest[key]);
}
}
return node;
}
function initFunctionNode(vnode, container) {
const { type, props } = vnode;
const node = type(props); //vnode
return initVnode(node, container);
}
function initClassNode(vnode, container) {
const { type, props } = vnode;
const componet = new type(props);
const vvnode = componet.render(); //vnode
const node = initVnode(vvnode, container);
componet.$cache = {
vnode: vvnode,
node,
parentNode: container,
};
return node;
}
src/kReact/diff.js
import { initVnode } from "./virtual-dom";
//这不是diff
export function diff(cache, newVnode) {
console.log("new", newVnode);
const { vnode, node, parentNode } = cache;
const newNode = initVnode(newVnode, parentNode);
console.log("con", parentNode);
parentNode.replaceChild(newNode, node);
return newNode;
}