create-react-app zhufengreact
cd zhufengreact
npm install cross-env
React.createElement
语法React.createElement
会返回一个React元素ReactDOM
来确保浏览器中的真实DOM数据和React元素保持一致JSX
<h1 className="title" style={{color:'red'}}>hello</h1>
老转译后的代码
React.createElement("h1", {
className: "title",
style: {
color: 'red'
}
}, "hello");
新转译后的代码
import { jsx } from "react/jsx-runtime";
jsx("h1", {
className: "title",
style: {
color: 'red'
},
children: "hello"
});
返回的React元素也就是虚拟DOM
{
type:'h1',
props:{
className: "title",
style: {
color: 'red'
}
},
children:"hello"
}
{
"name": "zhufengreact",
"version": "0.1.0",
"scripts": {
+ "start": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts start",
+ "build": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts build",
+ "test": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts test",
+ "eject": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts eject"
},
}
src\index.js
import React from "./react";
import ReactDOM from './react-dom/client';
let element = (
<div className="title" style={{ color: "red" }}>
<span>hello</span>world
</div>
);
console.log(JSON.stringify(element, null, 2));
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
src\constants.js
export const REACT_TEXT = Symbol('REACT_TEXT');
export const REACT_ELEMENT = Symbol('react.element');
src\utils.js
import { REACT_TEXT } from "./constants";
export function wrapToVdom(element) {
return typeof element === "string" || typeof element === "number"
? { type: REACT_TEXT, props: element }
: element;
}
src\react.js
import { wrapToVdom } from "./utils";
import { REACT_ELEMENT } from "./constants";
function createElement(type, config, children) {
let ref;
let key;
if (config) {
delete config.__source;
delete config.__self;
ref = config.ref;
delete config.ref;
key = config.key;
delete config.key;
}
let props = { ...config };
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);
}
return {
$$typeof: REACT_ELEMENT,
type,
ref,
key,
props,
};
}
const React = {
createElement,
};
export default React;
src\react-dom\client.js
import { REACT_TEXT } from "../constants";
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
}
export function createDOM(vdom) {
let { type, props } = vdom;
let dom;
if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else {
dom = document.createElement(type);
}
if (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);
}
}
vdom.dom = dom;
return dom;
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
mount(childrenVdom[i], parentDOM);
}
}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot
}
export default ReactDOM
JavaScript
函数。它接受任意的入参(props属性),并返回用于描述页面展示内容的 React 元素props
import React from "./react";
import ReactDOM from './react-dom/client';
+function FunctionComponent(props) {
+ return <div className="title" style={{ color: 'red' }}><span>{props.name}</span>{props.children}</div>;
+}
+let element = <FunctionComponent name="hello">world</FunctionComponent>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
src\react-dom.js
import { REACT_TEXT } from "../constants";
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
}
export function createDOM(vdom) {
let { type, props } = vdom;
let dom;
if (type === REACT_TEXT) {
dom = document.createTextNode(props);
+ } else if (typeof type === "function") {
+ return mountFunctionComponent(vdom);
+ } else {
dom = document.createElement(type);
}
if (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);
}
}
vdom.dom = dom;
return dom;
}
+function mountFunctionComponent(vdom) {
+ let { type, props } = vdom;
+ let renderVdom = type(props);
+ return createDOM(renderVdom);
+}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
mount(childrenVdom[i], parentDOM);
}
}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot
}
export default ReactDOM
src\index.js
import React from "./react";
import ReactDOM from './react-dom/client';
+class ClassComponent extends React.Component {
+ render() {
+ return <div className="title" style={{ color: 'red' }}><span>{this.props.name}</span>{this.props.children}</div>;
+ }
+}
+let element = <ClassComponent name="hello">world</ClassComponent>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
src\Component.js
export class Component {
static isReactComponent = true
constructor(props) {
this.props = props;
}
}
src\react.js
import { wrapToVdom } from "./utils";
import { REACT_ELEMENT } from "./constants";
+import { Component } from './Component';
function createElement(type, config, children) {
let ref;
let key;
if (config) {
delete config.__source;
delete config.__self;
ref = config.ref;
delete config.ref;
key = config.key;
delete config.key;
}
let props = { ...config };
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);
}
return {
$$typeof: REACT_ELEMENT,
type,
ref,
key,
props,
};
}
const React = {
createElement,
+ Component
};
export default React;
src\react-dom.js
import { REACT_TEXT } from "../constants";
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
}
export function createDOM(vdom) {
let { type, props } = vdom;
let dom;
if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else if (typeof type === "function") {
+ if (type.isReactComponent) {
+ return mountClassComponent(vdom);
+ } else {
+ return mountFunctionComponent(vdom);
+ }
} else {
dom = document.createElement(type);
}
if (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);
}
}
vdom.dom = dom;
return dom;
}
+function mountClassComponent(vdom) {
+ let { type, props } = vdom;
+ let classInstance = new type(props);
+ let renderVdom = classInstance.render();
+ let dom = createDOM(renderVdom);
+ return dom;
+}
function mountFunctionComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type(props);
return createDOM(renderVdom);
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
mount(childrenVdom[i], parentDOM);
}
}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot
}
export default ReactDOM
setState
import React from "./react";
import ReactDOM from './react-dom/client';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { number: 0 };
}
handleClick = () => {
this.setState({ number: this.state.number + 1 });
console.log(this.state);
}
render() {
return (
<div>
<p>{this.props.title}</p>
<p>number:{this.state.number}</p>
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
let element = <Counter title="计数器" />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
src\Component.js
+import { findDOM, compareTwoVdom } from './react-dom/client';
+class Updater {
+ constructor(classInstance) {
+ this.classInstance = classInstance;
+ this.pendingStates = [];
+ this.callbacks = [];
+ }
+ flushCallbacks() {
+ if (this.callbacks.length > 0) {
+ //如果没有使用箭头函数,那这里的
+ this.callbacks.forEach((callback) => callback());
+ this.callbacks.length = 0;
+ }
+ }
+ addState(partialState, callback) {
+ this.pendingStates.push(partialState);///等待更新的或者说等待生效的状态
+ if (typeof callback === 'function')
+ this.callbacks.push(callback);//状态更新后的回调
+ this.emitUpdate();
+ }
+ emitUpdate() {
+ this.updateComponent();
+ }
+ updateComponent() {
+ let { classInstance, pendingStates } = this;
+ if (pendingStates.length > 0) {
+ shouldUpdate(classInstance, this.getState());
+ }
+ }
+ getState() {
+ let { classInstance, pendingStates } = this;
+ let { state } = classInstance;
+ pendingStates.forEach((nextState) => {
+ if (typeof nextState === 'function') {
+ nextState = nextState(state);
+ }
+ state = { ...state, ...nextState };
+ });
+ pendingStates.length = 0;
+ return state;
+ }
+}
+function shouldUpdate(classInstance, nextState) {
+ classInstance.state = nextState;
+ classInstance.forceUpdate();
+}
export class Component {
static isReactComponent = true
constructor(props) {
this.props = props;
+ this.state = {};
+ this.updater = new Updater(this);
}
+ setState(partialState, callback) {
+ this.updater.addState(partialState, callback);
+ }
+ forceUpdate() {
+ let oldRenderVdom = this.oldRenderVdom;
+ let oldDOM = findDOM(oldRenderVdom);
+ let newRenderVdom = this.render();
+ compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
+ this.oldRenderVdom = newRenderVdom;
+ this.updater.flushCallbacks();
+ }
}
src\react-dom/client.js
import { REACT_TEXT } from "../constants";
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
}
export function createDOM(vdom) {
let { type, props } = vdom;
let dom;
if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else if (typeof type === "function") {
if (type.isReactComponent) {
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else {
dom = document.createElement(type);
}
if (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);
}
}
vdom.dom = dom;
return dom;
}
function mountClassComponent(vdom) {
let { type, props } = vdom;
let classInstance = new type(props);
+ vdom.classInstance = classInstance;
let renderVdom = classInstance.render();
+ classInstance.oldRenderVdom = renderVdom;
let dom = createDOM(renderVdom);
return dom;
}
function mountFunctionComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
+ } else if (/^on[A-Z].*/.test(key)) {
+ dom[key.toLowerCase()] = newProps[key];
+ } else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
+export function findDOM(vdom) {
+ if (!vdom) return null;
+ if (vdom.dom) {
+ return vdom.dom;
+ } else {
+ const renderVdom = vdom.classInstance ? vdom.classInstance.oldRenderVdom : vdom.oldRenderVdom;
+ return findDOM(renderVdom);
+ }
+}
+export function compareTwoVdom(parentDOM, oldVdom, newVdom) {
+ let oldDOM = findDOM(oldVdom);
+ let newDOM = createDOM(newVdom);
+ parentDOM.replaceChild(newDOM, oldDOM);
+}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
mount(childrenVdom[i], parentDOM);
}
}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot
}
export default ReactDOM
false
的方式阻止默认行为。你必须显式的使用preventDefault
src\index.js
import React from "./react";
import ReactDOM from './react-dom/client';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { number: 0 };
}
handleClick = () => {
this.setState({ number: this.state.number + 1 });
console.log(this.state);
this.setState({ number: this.state.number + 1 });
console.log(this.state);
setTimeout(() => {
this.setState({ number: this.state.number + 1 });
console.log(this.state);
this.setState({ number: this.state.number + 1 });
console.log(this.state);
});
}
handleClick2 = () => {
this.setState({ number: this.state.number + 1 });
}
clickParentCapture = (event) => {
console.log('clickParentCapture');
}
clickParentBubble = (event) => {
console.log('clickParentBubble');
}
clickChildCapture = (event) => {
console.log('clickChildCapture');
}
clickChildBubble = (event) => {
console.log('clickChildBubble');
//event.stopPropagation();
}
render() {
return (
<div onClick={this.clickParentBubble} onClickCapture={this.clickParentCapture}>
<div onClick={this.clickChildBubble} onClickCapture={this.clickChildCapture}>
<p>{this.props.title}</p>
<p>number:{this.state.number}</p>
<button onClick={this.handleClick2}>+</button>
</div>
</div>
)
}
}
let element = <Counter title="计数器" />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
src\Component.js
import { findDOM, compareTwoVdom } from './react-dom/client';
+export let updateQueue = {
+ isBatchingUpdate: false,
+ updaters: new Set(),
+ batchUpdate() {
+ updateQueue.isBatchingUpdate = false;
+ for (var updater of updateQueue.updaters) {
+ updater.updateComponent();
+ }
+ updateQueue.updaters.clear();
+ }
+}
class Updater {
constructor(classInstance) {
this.classInstance = classInstance;
this.pendingStates = [];
this.callbacks = [];
}
flushCallbacks() {
if (this.callbacks.length > 0) {
//如果没有使用箭头函数,那这里的
this.callbacks.forEach((callback) => callback());
this.callbacks.length = 0;
}
}
addState(partialState, callback) {
this.pendingStates.push(partialState);///等待更新的或者说等待生效的状态
if (typeof callback === 'function')
this.callbacks.push(callback);//状态更新后的回调
this.emitUpdate();
}
emitUpdate() {
+ if (updateQueue.isBatchingUpdate) {
+ updateQueue.updaters.add(this);
+ } else {
this.updateComponent();
+ }
}
updateComponent() {
let { classInstance, pendingStates } = this;
if (pendingStates.length > 0) {
shouldUpdate(classInstance, this.getState());
}
}
getState() {
let { classInstance, pendingStates } = this;
let { state } = classInstance;
pendingStates.forEach((nextState) => {
if (typeof nextState === 'function') {
nextState = nextState(state);
}
state = { ...state, ...nextState };
});
pendingStates.length = 0;
return state;
}
}
function shouldUpdate(classInstance, nextState) {
classInstance.state = nextState;
classInstance.forceUpdate();
}
export class Component {
static isReactComponent = true
constructor(props) {
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState, callback) {
this.updater.addState(partialState, callback);
}
forceUpdate() {
let oldRenderVdom = this.oldRenderVdom;
let oldDOM = findDOM(oldRenderVdom);
let newRenderVdom = this.render();
compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
this.oldRenderVdom = newRenderVdom;
this.updater.flushCallbacks();
}
}
src\event.js
import { updateQueue } from './Component';
const eventNames = ['click'];
eventNames.forEach(eventName => {
document.addEventListener(eventName, event => {
dispatchEvent(event, true);
}, true);
document.addEventListener(eventName, event => {
dispatchEvent(event, false);
}, false);
});
export function addEvent(dom, eventType, handler) {
let store = dom.store || (dom.store = {});
store[eventType.toLowerCase()] = handler;
}
function dispatchEvent(event, isCapture) {
const { target, type } = event;
let eventType = `on${type}`;
let eventTypeCapture = `on${type}capture`;
let syntheticEvent = createSyntheticEvent(event);
updateQueue.isBatchingUpdate = true;
let targetStack = [];
let currentTarget = target;
while (currentTarget) {
targetStack.push(currentTarget);
currentTarget = currentTarget.parentNode;
}
if (isCapture) {
for (let i = targetStack.length - 1; i >= 0; i--) {
const currentTarget = targetStack[i];
let { store } = currentTarget;
let handler = store && store[eventTypeCapture];
handler && handler(syntheticEvent);
}
} else {
for (let i = 0; i < targetStack.length; i++) {
const currentTarget = targetStack[i];
let { store } = currentTarget;
let handler = store && store[eventType];
handler && handler(syntheticEvent);
if (syntheticEvent.isPropagationStopped) {
break;
}
}
}
updateQueue.batchUpdate();
}
function createSyntheticEvent(nativeEvent) {
let syntheticEvent = {};
for (let key in nativeEvent) {
let value = nativeEvent[key];
if (typeof value === 'function') value = value.bind(nativeEvent);
syntheticEvent[key] = value;
}
syntheticEvent.nativeEvent = nativeEvent;
syntheticEvent.isDefaultPrevented = false;
syntheticEvent.preventDefault = preventDefault;
syntheticEvent.isPropagationStopped = false;
syntheticEvent.stopPropagation = stopPropagation;
return syntheticEvent;
}
function preventDefault() {
this.isDefaultPrevented = true;
const nativeEvent = this.nativeEvent;
if (nativeEvent.preventDefault) {
nativeEvent.preventDefault();
} else {
nativeEvent.returnValue = false;
}
}
function stopPropagation() {
this.isPropagationStopped = true;
const nativeEvent = this.nativeEvent;
if (nativeEvent.stopPropagation) {
nativeEvent.stopPropagation();
} else {
nativeEvent.cancelBubble = true;
}
}
src\react-dom.js
import { REACT_TEXT } from "../constants";
+import { addEvent } from '../event';
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
}
export function createDOM(vdom) {
let { type, props } = vdom;
let dom;
if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else if (typeof type === "function") {
if (type.isReactComponent) {
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else {
dom = document.createElement(type);
}
if (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);
}
}
vdom.dom = dom;
return dom;
}
function mountClassComponent(vdom) {
let { type, props } = vdom;
let classInstance = new type(props);
vdom.classInstance = classInstance;
let renderVdom = classInstance.render();
classInstance.oldRenderVdom = renderVdom;
let dom = createDOM(renderVdom);
return dom;
}
function mountFunctionComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
+ addEvent(dom, key.toLocaleLowerCase(), newProps[key]);
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {
return vdom.dom;
} else {
const renderVdom = vdom.classInstance ? vdom.classInstance.oldRenderVdom : vdom.oldRenderVdom;
return findDOM(renderVdom);
}
}
export function compareTwoVdom(parentDOM, oldVdom, newVdom) {
let oldDOM = findDOM(oldVdom);
let newDOM = createDOM(newVdom);
parentDOM.replaceChild(newDOM, oldDOM);
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
mount(childrenVdom[i], parentDOM);
}
}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot
}
export default ReactDOM
src\index.js
import React from "./react";
import ReactDOM from './react-dom/client';
class Sum extends React.Component {
a
b
result
constructor(props) {
super(props);
this.a = React.createRef();
this.b = React.createRef();
this.result = React.createRef();
}
handleAdd = () => {
let a = this.a.current.value;
let b = this.b.current.value;
this.result.current.value = a + b;
}
render() {
return (
<>
<input ref={this.a} />+<input ref={this.b} /><button onClick={this.handleAdd}>=</button><input ref={this.result} />
</>
);
}
}
let element = <Sum />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
src\index.js
import React from "./react";
import ReactDOM from './react-dom/client';
class Form extends React.Component {
input
constructor(props) {
super(props);
this.input = React.createRef();
}
getFocus = () => {
this.input.current.getFocus();
}
render() {
return (
<>
<TextInput ref={this.input} />
<button onClick={this.getFocus}>获得焦点</button>
</>
);
}
}
class TextInput extends React.Component {
input
constructor(props) {
super(props);
this.input = React.createRef();
}
getFocus = () => {
this.input.current.focus();
}
render() {
return <input ref={this.input} />
}
}
let element = <Form />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
src/index.js
import React from "./react";
import ReactDOM from './react-dom/client';
const TextInput = React.forwardRef((props, ref) => (
<input ref={ref} />
));
class Form extends React.Component {
input
constructor(props) {
super(props);
this.input = React.createRef();
}
getFocus = () => {
console.log(this.input.current);
this.input.current.focus();
}
render() {
return (
<>
<TextInput ref={this.input} />
<button onClick={this.getFocus}>获得焦点</button>
</>
);
}
}
let element = <Form />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
export const REACT_TEXT = Symbol('REACT_TEXT');
export const REACT_ELEMENT = Symbol('react.element');
+export const REACT_FORWARD_REF_TYPE = Symbol('react.forward_ref');
src\react.js
import { wrapToVdom } from "./utils";
+import { REACT_ELEMENT, REACT_FORWARD_REF_TYPE } from "./constants";
import { Component } from './Component';
function createElement(type, config, children) {
let ref;
let key;
if (config) {
delete config.__source;
delete config.__self;
ref = config.ref;
delete config.ref;
key = config.key;
delete config.key;
}
let props = { ...config };
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);
}
return {
$$typeof: REACT_ELEMENT,
type,
ref,
key,
props,
};
}
+function createRef() {
+ return { current: null };
+}
+function forwardRef(render) {
+ var elementType = {
+ $$typeof: REACT_FORWARD_REF_TYPE,
+ render: render
+ };
+ return elementType;
+}
const React = {
createElement,
Component,
+ createRef,
+ forwardRef
};
export default React;
src\react-dom.js
+import { REACT_TEXT, REACT_FORWARD_REF_TYPE } from "../constants";
import { addEvent } from '../event';
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
}
export function createDOM(vdom) {
+ let { type, props, ref } = vdom;
let dom;
+ if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) {
+ return mountForwardComponent(vdom);
+ } if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else if (typeof type === "function") {
if (type.isReactComponent) {
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else {
dom = document.createElement(type);
}
if (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);
}
}
vdom.dom = dom;
+ if (ref) ref.current = dom;
return dom;
}
+function mountForwardComponent(vdom) {
+ let { type, props, ref } = vdom;
+ let renderVdom = type.render(props, ref);
+ vdom.oldRenderVdom = renderVdom;
+ return createDOM(renderVdom);
+}
function mountClassComponent(vdom) {
+ let { type, props, ref } = vdom;
let classInstance = new type(props);
+ if (ref) ref.current = classInstance;
vdom.classInstance = classInstance;
let renderVdom = classInstance.render();
classInstance.oldRenderVdom = renderVdom;
let dom = createDOM(renderVdom);
return dom;
}
function mountFunctionComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
addEvent(dom, key.toLocaleLowerCase(), newProps[key]);
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {
return vdom.dom;
} else {
const renderVdom = vdom.classInstance ? vdom.classInstance.oldRenderVdom : vdom.oldRenderVdom;
return findDOM(renderVdom);
}
}
export function compareTwoVdom(parentDOM, oldVdom, newVdom) {
let oldDOM = findDOM(oldVdom);
let newDOM = createDOM(newVdom);
parentDOM.replaceChild(newDOM, oldDOM);
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
mount(childrenVdom[i], parentDOM);
}
}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot
}
export default ReactDOM
src\index.js
import React from "./react";
import ReactDOM from './react-dom/client';
class Counter extends React.Component {
static defaultProps = {
name: '珠峰架构'
};
constructor(props) {
super(props);
this.state = { number: 0 }
console.log('Counter 1.constructor')
}
UNSAFE_componentWillMount() {
console.log('Counter 2.componentWillMount');
}
componentDidMount() {
console.log('Counter 4.componentDidMount');
}
handleClick = () => {
this.setState({ number: this.state.number + 1 });
};
shouldComponentUpdate(nextProps, nextState) {
console.log('Counter 5.shouldComponentUpdate');
return nextState.number % 2 === 0;
}
UNSAFE_componentWillUpdate() {
console.log('Counter 6.componentWillUpdate');
}
componentDidUpdate() {
console.log('Counter 7.componentDidUpdate');
}
render() {
console.log('Counter 3.render');
return (
<div>
<p>{this.state.number}</p>
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
let element = <Counter />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
/**
Counter 1.constructor
Counter 2.componentWillMount
Counter 3.render
Counter 4.componentDidMount
Counter 5.shouldComponentUpdate
Counter 5.shouldComponentUpdate
Counter 6.componentWillUpdate
Counter 3.render
Counter 7.componentDidUpdate
Counter 5.shouldComponentUpdate
Counter 5.shouldComponentUpdate
Counter 6.componentWillUpdate
Counter 3.render
Counter 7.componentDidUpdate
*/
src\Component.js
import { findDOM, compareTwoVdom } from './react-dom/client';
export let updateQueue = {
isBatchingUpdate: false,
updaters: new Set(),
batchUpdate() {
updateQueue.isBatchingUpdate = false;
for (var updater of updateQueue.updaters) {
updater.updateComponent();
}
updateQueue.updaters.clear();
}
}
class Updater {
constructor(classInstance) {
this.classInstance = classInstance;
this.pendingStates = [];
this.callbacks = [];
}
flushCallbacks() {
if (this.callbacks.length > 0) {
//如果没有使用箭头函数,那这里的
this.callbacks.forEach((callback) => callback());
this.callbacks.length = 0;
}
}
addState(partialState, callback) {
this.pendingStates.push(partialState);///等待更新的或者说等待生效的状态
if (typeof callback === 'function')
this.callbacks.push(callback);//状态更新后的回调
this.emitUpdate();
}
emitUpdate() {
if (updateQueue.isBatchingUpdate) {
updateQueue.updaters.add(this);
} else {
this.updateComponent();
}
}
updateComponent() {
let { classInstance, pendingStates } = this;
if (pendingStates.length > 0) {
shouldUpdate(classInstance, this.getState());
}
}
getState() {
let { classInstance, pendingStates } = this;
let { state } = classInstance;
pendingStates.forEach((nextState) => {
if (typeof nextState === 'function') {
nextState = nextState(state);
}
state = { ...state, ...nextState };
});
pendingStates.length = 0;
return state;
}
}
function shouldUpdate(classInstance, nextState) {
+ let willUpdate = true;
+ if (classInstance.shouldComponentUpdate && (
+ !classInstance.shouldComponentUpdate(
+ classInstance.props,
+ nextState
+ ))) {
+ willUpdate = false
+ }
+ if (willUpdate && classInstance.UNSAFE_componentWillUpdate) {
+ classInstance.UNSAFE_componentWillUpdate();
+ }
+ classInstance.state = nextState;
if (willUpdate)
+ classInstance.forceUpdate();
}
export class Component {
static isReactComponent = true
constructor(props) {
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState, callback) {
this.updater.addState(partialState, callback);
}
forceUpdate() {
let oldRenderVdom = this.oldRenderVdom;
let oldDOM = findDOM(oldRenderVdom);
let newRenderVdom = this.render();
compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
this.oldRenderVdom = newRenderVdom;
this.updater.flushCallbacks();
+ if (this.componentDidUpdate) {
+ this.componentDidUpdate(this.props, this.state);
+ }
}
}
src\react-dom.js
import { REACT_TEXT, REACT_FORWARD_REF_TYPE } from "../constants";
import { addEvent } from '../event';
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
+ if (newDOM.componentDidMount) {
+ newDOM.componentDidMount()
+ }
}
export function createDOM(vdom) {
let { type, props, ref } = vdom;
let dom;
if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) {
return mountForwardComponent(vdom);
} if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else if (typeof type === "function") {
if (type.isReactComponent) {
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else {
dom = document.createElement(type);
}
if (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);
}
}
vdom.dom = dom;
if (ref) ref.current = dom;
return dom;
}
function mountForwardComponent(vdom) {
let { type, props, ref } = vdom;
let renderVdom = type.render(props, ref);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountClassComponent(vdom) {
let { type, props, ref } = vdom;
let classInstance = new type(props);
if (ref) ref.current = classInstance;
vdom.classInstance = classInstance;
+ if (classInstance.UNSAFE_componentWillMount) {
+ classInstance.UNSAFE_componentWillMount();
+ }
let renderVdom = classInstance.render();
classInstance.oldRenderVdom = renderVdom;
let dom = createDOM(renderVdom);
+ if (classInstance.componentDidMount) {
+ dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
+ }
return dom;
}
function mountFunctionComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
addEvent(dom, key.toLocaleLowerCase(), newProps[key]);
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {
return vdom.dom;
} else {
const renderVdom = vdom.classInstance ? vdom.classInstance.oldRenderVdom : vdom.oldRenderVdom;
return findDOM(renderVdom);
}
}
export function compareTwoVdom(parentDOM, oldVdom, newVdom) {
let oldDOM = findDOM(oldVdom);
let newDOM = createDOM(newVdom);
parentDOM.replaceChild(newDOM, oldDOM);
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
mount(childrenVdom[i], parentDOM);
}
}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot
}
export default ReactDOM
src\index.js
import React from "./react";
import ReactDOM from './react-dom/client';
class Counter extends React.Component {
static defaultProps = {
name: '珠峰架构'
};
constructor(props) {
super(props);
this.state = { number: 0 }
console.log('Counter 1.constructor')
}
componentWillMount() {
console.log('Counter 2.componentWillMount');
}
componentDidMount() {
console.log('Counter 4.componentDidMount');
}
handleClick = () => {
this.setState({ number: this.state.number + 1 });
};
shouldComponentUpdate(nextProps, nextState) {
console.log('Counter 5.shouldComponentUpdate');
return nextState.number % 2 === 0;
}
componentWillUpdate() {
console.log('Counter 6.componentWillUpdate');
}
componentDidUpdate() {
console.log('Counter 7.componentDidUpdate');
}
render() {
console.log('Counter 3.render');
return (
<div>
<p>{this.state.number}</p>
+ {this.state.number === 4 ? null : <ChildCounter count={this.state.number} />}
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
+class ChildCounter extends React.Component {
+ componentWillUnmount() {
+ console.log(' ChildCounter 6.componentWillUnmount')
+ }
+ componentWillMount() {
+ console.log('ChildCounter 1.componentWillMount')
+ }
+ render() {
+ console.log('ChildCounter 2.render')
+ return (<div>
+ {this.props.count}
+ </div>)
+ }
+ componentDidMount() {
+ console.log('ChildCounter 3.componentDidMount')
+ }
+ componentWillReceiveProps(newProps) {
+ console.log('ChildCounter 4.componentWillReceiveProps')
+ }
+ shouldComponentUpdate(nextProps, nextState) {
+ console.log('ChildCounter 5.shouldComponentUpdate')
+ return nextProps.count % 3 === 0;
+ }
+}
let element = <Counter />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
src\react-dom.js
+import { REACT_TEXT, REACT_FORWARD_REF_TYPE} from "../constants";
import { addEvent } from '../event';
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount()
}
}
export function createDOM(vdom) {
let { type, props, ref } = vdom;
let dom;
if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) {
return mountForwardComponent(vdom);
} if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else if (typeof type === "function") {
if (type.isReactComponent) {
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else {
dom = document.createElement(type);
}
if (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);
}
}
vdom.dom = dom;
if (ref) ref.current = dom;
return dom;
}
function mountForwardComponent(vdom) {
let { type, props, ref } = vdom;
let renderVdom = type.render(props, ref);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountClassComponent(vdom) {
let { type, props, ref } = vdom;
let classInstance = new type(props);
if (ref) ref.current = classInstance;
vdom.classInstance = classInstance;
if (classInstance.UNSAFE_componentWillMount) {
classInstance.UNSAFE_componentWillMount();
}
let renderVdom = classInstance.render();
classInstance.oldRenderVdom = renderVdom;
let dom = createDOM(renderVdom);
if (classInstance.componentDidMount) {
dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
}
return dom;
}
function mountFunctionComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
addEvent(dom, key.toLocaleLowerCase(), newProps[key]);
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {
return vdom.dom;
} else {
const renderVdom = vdom.classInstance ? vdom.classInstance.oldRenderVdom : vdom.oldRenderVdom;
return findDOM(renderVdom);
}
}
+export function compareTwoVdom(parentDOM, oldVdom, newVdom, nextDOM) {
+ if (!oldVdom && !newVdom) {
+ return;
+ } else if (oldVdom && !newVdom) {
+ unMountVdom(oldVdom);
+ } else if (!oldVdom && newVdom) {
+ let newDOM = createDOM(newVdom);
+ if (nextDOM) {
+ parentDOM.insertBefore(newDOM, nextDOM);
+ } else {
+ parentDOM.appendChild(newDOM);
+ }
+ if (newDOM.componentDidMount) {
+ newDOM.componentDidMount();
+ }
+ } else if (oldVdom && newVdom && (oldVdom.type !== newVdom.type)) {
+ unMountVdom(oldVdom)
+ let newDOM = createDOM(newVdom)
+ if (nextDOM) {
+ parentDOM.insertBefore(newDOM, nextDOM);
+ } else {
+ parentDOM.appendChild(newDOM);
+ }
+ if (newDOM.componentDidMount) {
+ newDOM.componentDidMount();
+ }
+ } else {
+ updateElement(oldVdom, newVdom);
+ }
+}
+function updateElement(oldVdom, newVdom) {
+ if (oldVdom.type === REACT_TEXT) {
+ let currentDOM = newVdom.dom = findDOM(oldVdom);
+ if (oldVdom.props !== newVdom.props) {
+ currentDOM.textContent = newVdom.props;
+ }
+ return;
+ } else if (typeof oldVdom.type === 'string') {
+ let currentDOM = newVdom.dom = findDOM(oldVdom);
+ updateProps(currentDOM, oldVdom.props, newVdom.props);
+ updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);
+ } else if (typeof oldVdom.type === 'function') {
+ if (oldVdom.type.isReactComponent) {
+ updateClassComponent(oldVdom, newVdom);
+ } else {
+ updateFunctionComponent(oldVdom, newVdom);
+ }
+ }
+}
+function updateFunctionComponent(oldVdom, newVdom) {
+ let currentDOM = findDOM(oldVdom);
+ if (!currentDOM) return;
+ let parentDOM = currentDOM.parentNode;
+ const { type, props } = newVdom;
+ const newRenderVdom = type(props);
+ compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom);
+ newVdom.oldRenderVdom = newRenderVdom;
+}
+function updateClassComponent(oldVdom, newVdom) {
+ let classInstance = newVdom.classInstance = oldVdom.classInstance;
+ if (classInstance.UNSAFE_componentWillReceiveProps) {
+ classInstance.UNSAFE_componentWillReceiveProps(newVdom.props);
+ }
+ classInstance.updater.emitUpdate(newVdom.props);
+}
+function updateChildren(parentDOM, oldVChildren, newVChildren) {
+ oldVChildren = (Array.isArray(oldVChildren) ? oldVChildren : oldVChildren ? [oldVChildren] : []);
+ newVChildren = (Array.isArray(newVChildren) ? newVChildren : newVChildren ? [newVChildren] : []);
+ let maxLength = Math.max(oldVChildren.length, newVChildren.length);
+ for (let i = 0; i < maxLength; i++) {
+ let nextVdom = oldVChildren.find((item, index) => index > i && item && findDOM(item));
+ compareTwoVdom(parentDOM, oldVChildren[i], newVChildren[i], nextVdom && findDOM(nextVdom));
+ }
+}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
mount(childrenVdom[i], parentDOM);
}
}
+function unMountVdom(vdom) {
+ const { props, ref } = vdom;
+ let currentDOM = findDOM(vdom);
+ if (vdom.classInstance && vdom.classInstance.componentWillUnmount) {
+ vdom.classInstance.componentWillUnmount();
+ }
+ if (ref) {
+ ref.current = null;
+ }
+ if (props.children) {
+ const children = Array.isArray(props.children) ? props.children : [props.children];
+ children.forEach(unMountVdom);
+ }
+ if (currentDOM) currentDOM.remove();
+}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot
}
export default ReactDOM
src\Component.js
import { findDOM, compareTwoVdom } from './react-dom/client';
export let updateQueue = {
isBatchingUpdate: false,
updaters: new Set(),
batchUpdate() {
updateQueue.isBatchingUpdate = false;
for (var updater of updateQueue.updaters) {
updater.updateComponent();
}
updateQueue.updaters.clear();
}
}
class Updater {
constructor(classInstance) {
this.classInstance = classInstance;
this.pendingStates = [];
this.callbacks = [];
}
flushCallbacks() {
if (this.callbacks.length > 0) {
//如果没有使用箭头函数,那这里的
this.callbacks.forEach((callback) => callback());
this.callbacks.length = 0;
}
}
addState(partialState, callback) {
this.pendingStates.push(partialState);///等待更新的或者说等待生效的状态
if (typeof callback === 'function')
this.callbacks.push(callback);//状态更新后的回调
this.emitUpdate();
}
+ emitUpdate(nextProps) {
+ this.nextProps = nextProps;
if (updateQueue.isBatchingUpdate) {
updateQueue.updaters.add(this);
} else {
this.updateComponent();
}
}
updateComponent() {
+ let { classInstance,nextProps, pendingStates } = this;
+ if (nextProps || pendingStates.length > 0) {
+ shouldUpdate(classInstance,nextProps, this.getState());
}
}
getState() {
let { classInstance, pendingStates } = this;
let { state } = classInstance;
pendingStates.forEach((nextState) => {
if (typeof nextState === 'function') {
nextState = nextState(state);
}
state = { ...state, ...nextState };
});
pendingStates.length = 0;
return state;
}
}
+function shouldUpdate(classInstance, nextProps, nextState) {
let willUpdate = true;
if (classInstance.shouldComponentUpdate && (
!classInstance.shouldComponentUpdate(
+ nextProps,
nextState
))) {
willUpdate = false
}
if (willUpdate && classInstance.UNSAFE_componentWillUpdate) {
classInstance.UNSAFE_componentWillUpdate();
}
classInstance.state = nextState;
+ if (nextProps) {
+ classInstance.props = nextProps;
+ }
if (willUpdate)
classInstance.forceUpdate();
}
export class Component {
static isReactComponent = true
constructor(props) {
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState, callback) {
this.updater.addState(partialState, callback);
}
forceUpdate() {
let oldRenderVdom = this.oldRenderVdom;
let oldDOM = findDOM(oldRenderVdom);
let newRenderVdom = this.render();
compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
this.oldRenderVdom = newRenderVdom;
this.updater.flushCallbacks();
if (this.componentDidUpdate) {
this.componentDidUpdate(this.props, this.state);
}
}
}
key
标识移动的元素src\index.js
import React from "./react";
import ReactDOM from './react-dom/client';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
list: ['A', 'B', 'C', 'E', 'F']
}
}
handleClick = () => {
this.setState({
list: ['A', 'C', 'E', 'B', 'G', 'F']
});
};
render() {
return (
<div>
<ul>
{
this.state.list.map(item => <li key={item}>{item}</li>)
}
</ul>
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
let element = <Counter />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
src\constants.js
export const REACT_TEXT = Symbol('REACT_TEXT');
export const REACT_ELEMENT = Symbol('react.element');
export const REACT_FORWARD_REF_TYPE = Symbol('react.forward_ref');
+export const PLACEMENT = 'PLACEMENT';
+export const MOVE = 'MOVE';
src\react-dom\client.js
+import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT } from "../constants";
import { addEvent } from '../event';
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount()
}
}
export function createDOM(vdom) {
let { type, props, ref } = vdom;
let dom;
if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) {
return mountForwardComponent(vdom);
} if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else if (typeof type === "function") {
if (type.isReactComponent) {
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else {
dom = document.createElement(type);
}
if (props) {
updateProps(dom, {}, props);
if (typeof props.children == "object" && props.children.type) {
+ props.children.mountIndex = 0;
mount(props.children, dom);
} else if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
}
}
vdom.dom = dom;
if (ref) ref.current = dom;
return dom;
}
function mountForwardComponent(vdom) {
let { type, props, ref } = vdom;
let renderVdom = type.render(props, ref);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountClassComponent(vdom) {
let { type, props, ref } = vdom;
let classInstance = new type(props);
if (ref) ref.current = classInstance;
vdom.classInstance = classInstance;
if (classInstance.UNSAFE_componentWillMount) {
classInstance.UNSAFE_componentWillMount();
}
let renderVdom = classInstance.render();
classInstance.oldRenderVdom = renderVdom;
let dom = createDOM(renderVdom);
if (classInstance.componentDidMount) {
dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
}
return dom;
}
function mountFunctionComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
addEvent(dom, key.toLocaleLowerCase(), newProps[key]);
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {
return vdom.dom;
} else {
const renderVdom = vdom.classInstance ? vdom.classInstance.oldRenderVdom : vdom.oldRenderVdom;
return findDOM(renderVdom);
}
}
export function compareTwoVdom(parentDOM, oldVdom, newVdom, nextDOM) {
if (!oldVdom && !newVdom) {
return;
} else if (oldVdom && !newVdom) {
unMountVdom(oldVdom);
} else if (!oldVdom && newVdom) {
let newDOM = createDOM(newVdom);
if (nextDOM) {
parentDOM.insertBefore(newDOM, nextDOM);
} else {
parentDOM.appendChild(newDOM);
}
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
} else if (oldVdom && newVdom && (oldVdom.type !== newVdom.type)) {
unMountVdom(oldVdom)
let newDOM = createDOM(newVdom)
if (nextDOM) {
parentDOM.insertBefore(newDOM, nextDOM);
} else {
parentDOM.appendChild(newDOM);
}
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
} else {
updateElement(oldVdom, newVdom);
}
}
function updateElement(oldVdom, newVdom) {
if (oldVdom.type === REACT_TEXT) {
let currentDOM = newVdom.dom = findDOM(oldVdom);
if (oldVdom.props !== newVdom.props) {
currentDOM.textContent = newVdom.props;
}
return;
} else if (typeof oldVdom.type === 'string') {
let currentDOM = newVdom.dom = findDOM(oldVdom);
updateProps(currentDOM, oldVdom.props, newVdom.props);
updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);
} else if (typeof oldVdom.type === 'function') {
if (oldVdom.type.isReactComponent) {
updateClassComponent(oldVdom, newVdom);
} else {
updateFunctionComponent(oldVdom, newVdom);
}
}
}
function updateFunctionComponent(oldVdom, newVdom) {
let currentDOM = findDOM(oldVdom);
if (!currentDOM) return;
let parentDOM = currentDOM.parentNode;
const { type, props } = newVdom;
const newRenderVdom = type(props);
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom);
newVdom.oldRenderVdom = newRenderVdom;
}
function updateClassComponent(oldVdom, newVdom) {
let classInstance = newVdom.classInstance = oldVdom.classInstance;
if (classInstance.UNSAFE_componentWillReceiveProps) {
classInstance.UNSAFE_componentWillReceiveProps(newVdom.props);
}
classInstance.updater.emitUpdate(newVdom.props);
}
function updateChildren(parentDOM, oldVChildren, newVChildren) {
oldVChildren = (Array.isArray(oldVChildren) ? oldVChildren : oldVChildren ? [oldVChildren] : []);
newVChildren = (Array.isArray(newVChildren) ? newVChildren : newVChildren ? [newVChildren] : []);
+ const keyedOldMap = {}
+ let lastPlacedIndex = -1;
+ oldVChildren.forEach((oldVChild, index) => {
+ let oldKey = oldVChild.key ? oldVChild.key : index;
+ keyedOldMap[oldKey] = oldVChild;
+ })
+ let patch = [];
+ newVChildren.forEach((newVChild, index) => {
+ newVChild.mountIndex = index;
+ let newKey = newVChild.key ? newVChild.key : index;
+ let oldVChild = keyedOldMap[newKey];
+ if (oldVChild) {
+ updateElement(oldVChild, newVChild);
+ if (oldVChild.mountIndex < lastPlacedIndex) {
+ patch.push({
+ type: MOVE,
+ oldVChild,
+ newVChild,
+ mountIndex: index
+ });
+ }
+ delete keyedOldMap[newKey];
+ lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex);
+ } else {
+ patch.push({
+ type: PLACEMENT,
+ newVChild,
+ mountIndex: index
+ });
+ }
+ });
+ const moveVChildren = patch.filter(action => action.type === MOVE).map(action => action.oldVChild);
+ Object.values(keyedOldMap).concat(moveVChildren).forEach(oldVChild => {
+ let currentDOM = findDOM(oldVChild);
+ parentDOM.removeChild(currentDOM);
+ });
+ patch.forEach(action => {
+ const { type, oldVChild, newVChild, mountIndex } = action;
+ let oldTrueDOMs = parentDOM.childNodes;
+ if (type === PLACEMENT) {
+ let newDOM = createDOM(newVChild);
+ const oldTrueDOM = oldTrueDOMs[mountIndex];
+ if (oldTrueDOM) {
+ parentDOM.insertBefore(newDOM, oldTrueDOM);
+ } else {
+ parentDOM.appendChild(newDOM);
+ }
+ } else if (type === MOVE) {
+ let oldDOM = findDOM(oldVChild);
+ let oldTrueDOM = oldTrueDOMs[mountIndex];
+ if (oldTrueDOM) {
+ parentDOM.insertBefore(oldDOM, oldTrueDOM);
+ } else {
+ parentDOM.appendChild(oldDOM);
+ }
+ }
+ });
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
+ childrenVdom[i].mountIndex = i;
mount(childrenVdom[i], parentDOM);
}
}
function unMountVdom(vdom) {
const { props, ref } = vdom;
let currentDOM = findDOM(vdom);
if (vdom.classInstance && vdom.classInstance.componentWillUnmount) {
vdom.classInstance.componentWillUnmount();
}
if (ref) {
ref.current = null;
}
if (props.children) {
const children = Array.isArray(props.children) ? props.children : [props.children];
children.forEach(unMountVdom);
}
if (currentDOM) currentDOM.remove();
}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot
}
export default ReactDOM
static getDerivedStateFromProps(props, state)
这个生命周期的功能实际上就是将传入的props映射到state上面import React from "./react";
import ReactDOM from './react-dom/client';
class Counter extends React.Component {
static defaultProps = {
name: 'zhufeng'
}
constructor(props) {
super(props);
this.state = { number: 0 };
console.log('Counter 1.constructor');
}
UNSAFE_componentWillMount() {
console.log('Counter 2.componentWillMount');
}
handleClick = () => {
this.setState({ number: this.state.number + 1 });
}
shouldComponentUpdate(nextProps, nextState) {
return true;
}
UNSAFE_componentWillUpdate() {
//组件将要更新
console.log(`Counter 6.componentWillUpdate`);
}
componentDidUpdate() {
console.log(`Counter 7.componentDidUpdate`);
}
render() {
console.log('Counter 3.render');
return (
<div id={`counter${this.state.number}`}>
<p>{this.state.number}</p>
<ChildCounter count={this.state.number} />
<button onClick={this.handleClick}>+</button>
</div>
)
}
componentDidMount() {
console.log('Counter 4.componentDidMount');
}
}
class ChildCounter extends React.Component {
constructor(props) {
super(props);
this.state = { number: 0 };
}
UNSAFE_componentWillReceiveProps(newProps) {
console.log('ChildCounter 4.componentWillReceiveProps');
}
UNSAFE_componentWillMount() {
console.log('ChildCounter 1.componentWillMount');
}
shouldComponentUpdate(nextProps, nextState) {
return true;
}
static getDerivedStateFromProps(nextProps, prevState) {
const { count } = nextProps;
if (count % 2 === 0) {
return { number: count * 2 };
} else {
return { number: count * 3 }
}
}
render() {
console.log('ChildCounter 2.render');
return (
<div>{this.state.number}</div>
)
}
componentDidMount() {
console.log('ChildCounter 3.componentDidMount');
}
componentWillUnmount() {
console.log('ChildCounter 6.componentWillUnmount');
}
}
let element = <Counter />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
import React from "./react";
import ReactDOM from './react-dom/client';
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.state = { messages: [] }
this.wrapper = React.createRef();
}
addMessage() {
this.setState(state => ({
messages: [`${state.messages.length}`, ...state.messages],
}))
}
componentDidMount() {
this.timeID = window.setInterval(() => {
this.addMessage();
}, 1000)
}
componentWillUnmount() {
window.clearInterval(this.timeID);
}
getSnapshotBeforeUpdate() {
return { prevScrollTop: this.wrapper.current.scrollTop, prevScrollHeight: this.wrapper.current.scrollHeight };
}
componentDidUpdate(pervProps, pervState, { prevScrollHeight, prevScrollTop }) {
this.wrapper.current.scrollTop = prevScrollTop + (this.wrapper.current.scrollHeight - prevScrollHeight);
}
render() {
let styleObj = {
height: '100px',
width: '200px',
border: '1px solid red',
overflow: 'auto'
}
return (
<div ref={this.wrapper} style={styleObj}>
{
this.state.messages.map((message, index) => (
<div key={index}>{message}</div>
))
}
</div>
);
}
}
let element = <ScrollingList />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
import { findDOM, compareTwoVdom } from './react-dom/client';
export let updateQueue = {
isBatchingUpdate: false,
updaters: new Set(),
batchUpdate() {
updateQueue.isBatchingUpdate = false;
for (var updater of updateQueue.updaters) {
updater.updateComponent();
}
updateQueue.updaters.clear();
}
}
class Updater {
constructor(classInstance) {
this.classInstance = classInstance;
this.pendingStates = [];
this.callbacks = [];
}
flushCallbacks() {
if (this.callbacks.length > 0) {
//如果没有使用箭头函数,那这里的
this.callbacks.forEach((callback) => callback());
this.callbacks.length = 0;
}
}
addState(partialState, callback) {
this.pendingStates.push(partialState);///等待更新的或者说等待生效的状态
if (typeof callback === 'function')
this.callbacks.push(callback);//状态更新后的回调
this.emitUpdate();
}
emitUpdate(nextProps) {
this.nextProps = nextProps;
if (updateQueue.isBatchingUpdate) {
updateQueue.updaters.add(this);
} else {
this.updateComponent();
}
}
updateComponent() {
let { classInstance, nextProps, pendingStates } = this;
if (nextProps || pendingStates.length > 0) {
shouldUpdate(classInstance, nextProps, this.getState());
}
}
getState() {
let { classInstance, pendingStates } = this;
let { state } = classInstance;
pendingStates.forEach((nextState) => {
if (typeof nextState === 'function') {
nextState = nextState(state);
}
state = { ...state, ...nextState };
});
pendingStates.length = 0;
return state;
}
}
function shouldUpdate(classInstance, nextProps, nextState) {
let willUpdate = true;
if (classInstance.shouldComponentUpdate && (
!classInstance.shouldComponentUpdate(
nextProps,
nextState
))) {
willUpdate = false
}
if (willUpdate && classInstance.UNSAFE_componentWillUpdate) {
classInstance.UNSAFE_componentWillUpdate();
}
classInstance.state = nextState;
if (nextProps) {
classInstance.props = nextProps;
}
if (willUpdate)
classInstance.forceUpdate();
}
export class Component {
static isReactComponent = true
constructor(props) {
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState, callback) {
this.updater.addState(partialState, callback);
}
forceUpdate() {
let oldRenderVdom = this.oldRenderVdom;
let oldDOM = findDOM(oldRenderVdom);
+ if (this.constructor.getDerivedStateFromProps) {
+ let newState = this.constructor.getDerivedStateFromProps(this.props, this.state);
+ if (newState)
+ this.state = { ...this.state, ...newState };
+ }
+ let snapshot = this.getSnapshotBeforeUpdate && this.getSnapshotBeforeUpdate();
let newRenderVdom = this.render();
compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
this.oldRenderVdom = newRenderVdom;
this.updater.flushCallbacks();
if (this.componentDidUpdate) {
+ this.componentDidUpdate(this.props, this.state, snapshot);
}
}
}
import React from "./react";
import ReactDOM from './react-dom/client';
let ThemeContext = React.createContext();
console.log(ThemeContext);
const { Provider, Consumer } = ThemeContext;
let style = { margin: '5px', padding: '5px' };
function Title() {
return (
<Consumer>
{
(contextValue) => (
<div style={{ ...style, border: `5px solid ${contextValue.color}` }}>
Title
</div>
)
}
</Consumer>
)
}
class Header extends React.Component {
static contextType = ThemeContext
render() {
console.log('Header');
return (
<div style={{ ...style, border: `5px solid ${this.context.color}` }}>
Header
<Title />
</div>
)
}
}
function Content() {
console.log('Content');
return (
<Consumer>
{
(contextValue) => (
<div style={{ ...style, border: `5px solid ${contextValue.color}` }}>
Content
<button style={{ color: 'red' }} onClick={() => contextValue.changeColor('red')}>变红</button>
<button style={{ color: 'green' }} onClick={() => contextValue.changeColor('green')}>变绿</button>
</div>
)
}
</Consumer>
)
}
class Main extends React.Component {
static contextType = ThemeContext
render() {
console.log('Main');
return (
<div style={{ ...style, border: `5px solid ${this.context.color}` }}>
Main
<Content />
</div>
)
}
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = { color: 'black' };
}
changeColor = (color) => {
this.setState({ color });
}
render() {
let contextValue = { color: this.state.color, changeColor: this.changeColor };
return (
<Provider value={contextValue}>
<div style={{ ...style, width: '250px', border: `5px solid ${this.state.color}` }}>
Page
<Header />
<Main />
</div>
</Provider >
)
}
}
let element = <Page />;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
src\constants.js
export const REACT_TEXT = Symbol('REACT_TEXT');
export const REACT_ELEMENT = Symbol('react.element');
export const REACT_FORWARD_REF_TYPE = Symbol('react.forward_ref');
+export const REACT_PROVIDER = Symbol('react.provider');
+export const REACT_CONTEXT = Symbol('react.context');
export const PLACEMENT = 'PLACEMENT';
export const MOVE = 'MOVE';
src\Component.js
import { findDOM, compareTwoVdom } from './react-dom/client';
export let updateQueue = {
isBatchingUpdate: false,
updaters: new Set(),
batchUpdate() {
updateQueue.isBatchingUpdate = false;
for (var updater of updateQueue.updaters) {
updater.updateComponent();
}
updateQueue.updaters.clear();
}
}
class Updater {
constructor(classInstance) {
this.classInstance = classInstance;
this.pendingStates = [];
this.callbacks = [];
}
flushCallbacks() {
if (this.callbacks.length > 0) {
//如果没有使用箭头函数,那这里的
this.callbacks.forEach((callback) => callback());
this.callbacks.length = 0;
}
}
addState(partialState, callback) {
this.pendingStates.push(partialState);///等待更新的或者说等待生效的状态
if (typeof callback === 'function')
this.callbacks.push(callback);//状态更新后的回调
this.emitUpdate();
}
emitUpdate(nextProps) {
this.nextProps = nextProps;
if (updateQueue.isBatchingUpdate) {
updateQueue.updaters.add(this);
} else {
this.updateComponent();
}
}
updateComponent() {
let { classInstance, nextProps, pendingStates } = this;
if (nextProps || pendingStates.length > 0) {
shouldUpdate(classInstance, nextProps, this.getState());
}
}
getState() {
let { classInstance, pendingStates } = this;
let { state } = classInstance;
pendingStates.forEach((nextState) => {
if (typeof nextState === 'function') {
nextState = nextState(state);
}
state = { ...state, ...nextState };
});
pendingStates.length = 0;
return state;
}
}
function shouldUpdate(classInstance, nextProps, nextState) {
let willUpdate = true;
if (classInstance.shouldComponentUpdate && (
!classInstance.shouldComponentUpdate(
nextProps,
nextState
))) {
willUpdate = false
}
if (willUpdate && classInstance.UNSAFE_componentWillUpdate) {
classInstance.UNSAFE_componentWillUpdate();
}
classInstance.state = nextState;
if (nextProps) {
classInstance.props = nextProps;
}
if (willUpdate)
classInstance.forceUpdate();
}
export class Component {
static isReactComponent = true
constructor(props) {
this.props = props;
this.state = {};
this.updater = new Updater(this);
}
setState(partialState, callback) {
this.updater.addState(partialState, callback);
}
forceUpdate() {
let oldRenderVdom = this.oldRenderVdom;
let oldDOM = findDOM(oldRenderVdom);
+ if (this.constructor.contextType) {
+ this.context = this.constructor.contextType._currentValue;
+ }
if (this.constructor.getDerivedStateFromProps) {
let newState = this.constructor.getDerivedStateFromProps(this.props, this.state);
if (newState)
this.state = { ...this.state, ...newState };
}
let snapshot = this.getSnapshotBeforeUpdate && this.getSnapshotBeforeUpdate();
let newRenderVdom = this.render();
compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
this.oldRenderVdom = newRenderVdom;
this.updater.flushCallbacks();
if (this.componentDidUpdate) {
this.componentDidUpdate(this.props, this.state, snapshot);
}
}
}
src\react.js
import { wrapToVdom } from "./utils";
+import { REACT_ELEMENT, REACT_FORWARD_REF_TYPE, REACT_PROVIDER, REACT_CONTEXT } from "./constants";
import { Component } from './Component';
function createElement(type, config, children) {
let ref;
let key;
if (config) {
delete config.__source;
delete config.__self;
ref = config.ref;
delete config.ref;
key = config.key;
delete config.key;
}
let props = { ...config };
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);
}
return {
$$typeof: REACT_ELEMENT,
type,
ref,
key,
props,
};
}
function createRef() {
return { current: null };
}
function forwardRef(render) {
var elementType = {
$$typeof: REACT_FORWARD_REF_TYPE,
render: render
};
return elementType;
}
+function createContext() {
+ let context = { _currentValue: undefined };
+ context.Provider = {
+ $$typeof: REACT_PROVIDER,
+ _context: context
+ }
+ context.Consumer = {
+ $$typeof: REACT_CONTEXT,
+ _context: context
+ }
+ return context;
+}
const React = {
createElement,
Component,
createRef,
forwardRef,
+ createContext
};
export default React;
src\react-dom.js
+import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT } from "../constants";
import { addEvent } from '../event';
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount()
}
}
export function createDOM(vdom) {
let { type, props, ref } = vdom;
let dom;
+ if (type && type.$$typeof === REACT_PROVIDER) {
+ return mountProviderComponent(vdom)
+ } else if (type && type.$$typeof === REACT_CONTEXT) {
+ return mountContextComponent(vdom)
} if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) {
return mountForwardComponent(vdom);
} if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else if (typeof type === "function") {
if (type.isReactComponent) {
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else {
dom = document.createElement(type);
}
if (props) {
updateProps(dom, {}, props);
if (typeof props.children == "object" && props.children.type) {
props.children.mountIndex = 0;
mount(props.children, dom);
} else if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
}
}
vdom.dom = dom;
if (ref) ref.current = dom;
return dom;
}
+function mountProviderComponent(vdom) {
+ let { type, props } = vdom;
+ let context = type._context;
+ context._currentValue = props.value;
+ let renderVdom = props.children;
+ vdom.oldRenderVdom = renderVdom;
+ return createDOM(renderVdom);
+}
+function mountContextComponent(vdom) {
+ let { type, props } = vdom;
+ let context = type._context;
+ let renderVdom = props.children(context._currentValue);
+ vdom.oldRenderVdom = renderVdom;
+ return createDOM(renderVdom);
+}
function mountForwardComponent(vdom) {
let { type, props, ref } = vdom;
let renderVdom = type.render(props, ref);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountClassComponent(vdom) {
let { type, props, ref } = vdom;
let classInstance = new type(props);
+ if (type.contextType) {
+ classInstance.context = type.contextType._currentValue;
+ }
if (ref) ref.current = classInstance;
vdom.classInstance = classInstance;
if (classInstance.UNSAFE_componentWillMount) {
classInstance.UNSAFE_componentWillMount();
}
let renderVdom = classInstance.render();
classInstance.oldRenderVdom = renderVdom;
let dom = createDOM(renderVdom);
if (classInstance.componentDidMount) {
dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
}
return dom;
}
function mountFunctionComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
addEvent(dom, key.toLocaleLowerCase(), newProps[key]);
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {
return vdom.dom;
} else {
const renderVdom = vdom.classInstance ? vdom.classInstance.oldRenderVdom : vdom.oldRenderVdom;
return findDOM(renderVdom);
}
}
export function compareTwoVdom(parentDOM, oldVdom, newVdom, nextDOM) {
if (!oldVdom && !newVdom) {
return;
} else if (oldVdom && !newVdom) {
unMountVdom(oldVdom);
} else if (!oldVdom && newVdom) {
let newDOM = createDOM(newVdom);
if (nextDOM) {
parentDOM.insertBefore(newDOM, nextDOM);
} else {
parentDOM.appendChild(newDOM);
}
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
} else if (oldVdom && newVdom && (oldVdom.type !== newVdom.type)) {
unMountVdom(oldVdom)
let newDOM = createDOM(newVdom)
if (nextDOM) {
parentDOM.insertBefore(newDOM, nextDOM);
} else {
parentDOM.appendChild(newDOM);
}
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
} else {
updateElement(oldVdom, newVdom);
}
}
function updateElement(oldVdom, newVdom) {
+ if (oldVdom.type.$$typeof === REACT_CONTEXT) {
+ updateContextComponent(oldVdom, newVdom);
+ } else if (oldVdom.type.$$typeof === REACT_PROVIDER) {
+ updateProviderComponent(oldVdom, newVdom);
} else if (oldVdom.type === REACT_TEXT) {
let currentDOM = newVdom.dom = findDOM(oldVdom);
if (oldVdom.props !== newVdom.props) {
currentDOM.textContent = newVdom.props;
}
return;
} else if (typeof oldVdom.type === 'string') {
let currentDOM = newVdom.dom = findDOM(oldVdom);
updateProps(currentDOM, oldVdom.props, newVdom.props);
updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);
} else if (typeof oldVdom.type === 'function') {
if (oldVdom.type.isReactComponent) {
updateClassComponent(oldVdom, newVdom);
} else {
updateFunctionComponent(oldVdom, newVdom);
}
}
}
+function updateProviderComponent(oldVdom, newVdom) {
+ let parentDOM = findDOM(oldVdom).parentNode;
+ let { type, props } = newVdom;
+ let context = type._context;
+ context._currentValue = props.value;
+ let renderVdom = props.children;
+ compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
+ newVdom.oldRenderVdom = renderVdom;
+}
+function updateContextComponent(oldVdom, newVdom) {
+ let parentDOM = findDOM(oldVdom).parentNode;
+ let { type, props } = newVdom;
+ let context = type._context;
+ let renderVdom = props.children(context._currentValue);
+ compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
+ newVdom.oldRenderVdom = renderVdom;
+}
function updateFunctionComponent(oldVdom, newVdom) {
let currentDOM = findDOM(oldVdom);
if (!currentDOM) return;
let parentDOM = currentDOM.parentNode;
const { type, props } = newVdom;
const newRenderVdom = type(props);
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom);
newVdom.oldRenderVdom = newRenderVdom;
}
function updateClassComponent(oldVdom, newVdom) {
let classInstance = newVdom.classInstance = oldVdom.classInstance;
if (classInstance.UNSAFE_componentWillReceiveProps) {
classInstance.UNSAFE_componentWillReceiveProps(newVdom.props);
}
classInstance.updater.emitUpdate(newVdom.props);
}
function updateChildren(parentDOM, oldVChildren, newVChildren) {
oldVChildren = (Array.isArray(oldVChildren) ? oldVChildren : oldVChildren ? [oldVChildren] : []);
newVChildren = (Array.isArray(newVChildren) ? newVChildren : newVChildren ? [newVChildren] : []);
const keyedOldMap = {}
let lastPlacedIndex = -1;
oldVChildren.forEach((oldVChild, index) => {
let oldKey = oldVChild.key ? oldVChild.key : index;
keyedOldMap[oldKey] = oldVChild;
})
let patch = [];
newVChildren.forEach((newVChild, index) => {
newVChild.mountIndex = index;
let newKey = newVChild.key ? newVChild.key : index;
let oldVChild = keyedOldMap[newKey];
if (oldVChild) {
updateElement(oldVChild, newVChild);
if (oldVChild.mountIndex < lastPlacedIndex) {
patch.push({
type: MOVE,
oldVChild,
newVChild,
mountIndex: index
});
}
delete keyedOldMap[newKey];
lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex);
} else {
patch.push({
type: PLACEMENT,
newVChild,
mountIndex: index
});
}
});
const moveVChildren = patch.filter(action => action.type === MOVE).map(action => action.oldVChild);
Object.values(keyedOldMap).concat(moveVChildren).forEach(oldVChild => {
let currentDOM = findDOM(oldVChild);
parentDOM.removeChild(currentDOM);
});
patch.forEach(action => {
const { type, oldVChild, newVChild, mountIndex } = action;
let oldTrueDOMs = parentDOM.childNodes;
if (type === PLACEMENT) {
let newDOM = createDOM(newVChild);
const oldTrueDOM = oldTrueDOMs[mountIndex];
if (oldTrueDOM) {
parentDOM.insertBefore(newDOM, oldTrueDOM);
} else {
parentDOM.appendChild(newDOM);
}
} else if (type === MOVE) {
let oldDOM = findDOM(oldVChild);
let oldTrueDOM = oldTrueDOMs[mountIndex];
if (oldTrueDOM) {
parentDOM.insertBefore(oldDOM, oldTrueDOM);
} else {
parentDOM.appendChild(oldDOM);
}
}
});
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
childrenVdom[i].mountIndex = i;
mount(childrenVdom[i], parentDOM);
}
}
function unMountVdom(vdom) {
const { props, ref } = vdom;
let currentDOM = findDOM(vdom);
if (vdom.classInstance && vdom.classInstance.componentWillUnmount) {
vdom.classInstance.componentWillUnmount();
}
if (ref) {
ref.current = null;
}
if (props.children) {
const children = Array.isArray(props.children) ? props.children : [props.children];
children.forEach(unMountVdom);
}
if (currentDOM) currentDOM.remove();
}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot
}
export default ReactDOM
const NewComponent = higherOrderComponent(OldComponent)
import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
class Button extends React.Component{
constructor(props){
super(props);
this.state = {name:'button'};
}
render(){
return <button name={this.state.name}>{this.props.title}</button>
}
}
let element = <Button/>
DOMRoot.render(element);
import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
class Button extends React.Component{
constructor(props){
super(props);
this.state = {name:'button'};
}
componentDidMount(){
console.log(`Button componentDidMount`);
}
render(){
return <button name={this.state.name}>{this.props.title}</button>
}
}
function wrapper(OldComponent){
return class NewComponent extends OldComponent{
constructor(props){
super(props);
this.state = {number:0}
}
componentDidMount(){
console.log(`NewComponent componentDidMount`);
super.componentDidMount();
}
handleClick = ()=>{
this.setState({number:this.state.number+1});
}
render(){
let vdom = super.render();
let newProps = {
...vdom.props,
...this.state,
onClick:this.handleClick
}
return React.cloneElement(
vdom,
newProps,
this.state.number
);
}
}
}
const NewButton = wrapper(Button);
let element = <NewButton title="按钮"/>
DOMRoot.render(element);
src\react.js
import { wrapToVdom } from "./utils";
import { REACT_ELEMENT, REACT_FORWARD_REF_TYPE, REACT_PROVIDER, REACT_CONTEXT } from "./constants";
import { Component } from './Component';
function createElement(type, config, children) {
let ref;
let key;
if (config) {
delete config.__source;
delete config.__self;
ref = config.ref;
delete config.ref;
key = config.key;
delete config.key;
}
let props = { ...config };
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);
}
return {
$$typeof: REACT_ELEMENT,
type,
ref,
key,
props,
};
}
function createRef() {
return { current: null };
}
function forwardRef(render) {
var elementType = {
$$typeof: REACT_FORWARD_REF_TYPE,
render: render
};
return elementType;
}
function createContext() {
let context = { _currentValue: undefined };
context.Provider = {
$$typeof: REACT_PROVIDER,
_context: context
}
context.Consumer = {
$$typeof: REACT_CONTEXT,
_context: context
}
return context;
}
+function cloneElement(element, newProps, children) {
+ let props = { ...element.props, ...newProps };
+ if (arguments.length > 3) {
+ props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom)
+ } else if (arguments.length === 3) {
+ props.children = wrapToVdom(children);
+ }
+ return {
+ ...element,
+ props
+ }
+}
const React = {
createElement,
Component,
createRef,
forwardRef,
createContext,
+ cloneElement
};
export default React;
render prop
是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术import React from 'react';
import ReactDOM from 'react-dom';
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div onMouseMove={this.handleMouseMove}>
<h1>移动鼠标!</h1>
<p>当前的鼠标位置是 ({this.state.x}, {this.state.y})</p>
</div>
);
}
}
ReactDOM.render(<MouseTracker />, document.getElementById('root'));
import React from './react';
import ReactDOM from './react-dom';
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.children(this.state)}
</div>
);
}
}
ReactDOM.render(<MouseTracker >
{
(props) => (
<div>
<h1>移动鼠标!</h1>
<p>当前的鼠标位置是 ({props.x}, {props.y})</p>
</div>
)
}
</MouseTracker >, document.getElementById('root'));
import React from 'react';
import ReactDOM from 'react-dom';
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
ReactDOM.render(< MouseTracker render={params => (
<>
<h1>移动鼠标!</h1>
<p>当前的鼠标位置是 ({params.x}, {params.y})</p>
</>
)} />, document.getElementById('root'));
import React from 'react';
import ReactDOM from 'react-dom';
function withTracker(OldComponent){
return class MouseTracker extends React.Component{
constructor(props){
super(props);
this.state = {x:0,y:0};
}
handleMouseMove = (event)=>{
this.setState({
x:event.clientX,
y:event.clientY
});
}
render(){
return (
<div onMouseMove = {this.handleMouseMove}>
<OldComponent {...this.state}/>
</div>
)
}
}
}
//render
function Show(props){
return (
<div>
<h1>请移动鼠标</h1>
<p>当前鼠标的位置是: x:{props.x} y:{props.y}</p>
</div>
)
}
let HighShow = withTracker(Show);
ReactDOM.render(
<HighShow/>, document.getElementById('root'));
import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
class ClassCounter extends React.PureComponent {
render() {
console.log('ClassCounter render');
return <div>ClassCounter:{this.props.count}</div>
}
}
function FunctionCounter(props) {
console.log('FunctionCounter render');
return <div>FunctionCounter:{props.count}</div>
}
const MemoFunctionCounter = React.memo(FunctionCounter);
class App extends React.Component {
state = { number: 0 }
amountRef = React.createRef()
handleClick = () => {
let nextNumber = this.state.number + parseInt(this.amountRef.current.value);
this.setState({ number: nextNumber });
}
render() {
return (
<div>
<ClassCounter count={this.state.number} />
<MemoFunctionCounter count={this.state.number} />
<input ref={this.amountRef} />
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
let element = <App title="按钮" />
DOMRoot.render(element);
src\constants.js
export const REACT_TEXT = Symbol('REACT_TEXT');
export const REACT_ELEMENT = Symbol('react.element');
export const REACT_FORWARD_REF_TYPE = Symbol('react.forward_ref');
export const REACT_PROVIDER = Symbol('react.provider');
export const REACT_CONTEXT = Symbol('react.context');
export const PLACEMENT = 'PLACEMENT';
export const MOVE = 'MOVE';
+export const REACT_MEMO = Symbol('react.memo')
src\utils.js
import { REACT_TEXT } from "./constants";
export function wrapToVdom(element) {
return typeof element === "string" || typeof element === "number"
? { type: REACT_TEXT, props: element }
: element;
}
+export function shallowEqual(obj1, obj2) {
+ if (obj1 === obj2) {
+ return true;
+ }
+ if (typeof obj1 != "object" || obj1 === null || typeof obj2 != "object" || obj2 === null) {
+ return false;
+ }
+ let keys1 = Object.keys(obj1);
+ let keys2 = Object.keys(obj2);
+ if (keys1.length !== keys2.length) {
+ return false;
+ }
+ for (let key of keys1) {
+ if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
+ return false;
+ }
+ }
+ return true;
+}
src\react.js
+import { wrapToVdom, shallowEqual } from "./utils";
+import { REACT_ELEMENT, REACT_FORWARD_REF_TYPE, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "./constants";
import { Component } from './Component';
function createElement(type, config, children) {
let ref;
let key;
if (config) {
delete config.__source;
delete config.__self;
ref = config.ref;
delete config.ref;
key = config.key;
delete config.key;
}
let props = { ...config };
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
} else {
props.children = wrapToVdom(children);
}
return {
$$typeof: REACT_ELEMENT,
type,
ref,
key,
props,
};
}
function createRef() {
return { current: null };
}
function forwardRef(render) {
var elementType = {
$$typeof: REACT_FORWARD_REF_TYPE,
render: render
};
return elementType;
}
function createContext() {
let context = { _currentValue: undefined };
context.Provider = {
$$typeof: REACT_PROVIDER,
_context: context
}
context.Consumer = {
$$typeof: REACT_CONTEXT,
_context: context
}
return context;
}
function cloneElement(element, newProps, children) {
let props = { ...element.props, ...newProps };
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom)
} else if (arguments.length === 3) {
props.children = wrapToVdom(children);
}
return {
...element,
props
}
}
+class PureComponent extends Component {
+ shouldComponentUpdate(newProps, nextState) {
+ return !shallowEqual(this.props, newProps) || !shallowEqual(this.state, nextState);
+ }
+}
+function memo(type, compare = shallowEqual) {
+ return {
+ $$typeof: REACT_MEMO,
+ type,
+ compare
+ }
+}
const React = {
createElement,
Component,
createRef,
forwardRef,
createContext,
cloneElement,
+ PureComponent,
+ memo
};
export default React;
src\react-dom\client.js
+import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount()
}
}
export function createDOM(vdom) {
let { type, props, ref } = vdom;
let dom;
+ if (type && type.$$typeof === REACT_MEMO) {
+ return mountMemoComponent(vdom);
} else if (type && type.$$typeof === REACT_PROVIDER) {
return mountProviderComponent(vdom)
} else if (type && type.$$typeof === REACT_CONTEXT) {
return mountContextComponent(vdom)
} if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) {
return mountForwardComponent(vdom);
} if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else if (typeof type === "function") {
if (type.isReactComponent) {
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else {
dom = document.createElement(type);
}
if (props) {
updateProps(dom, {}, props);
if (typeof props.children == "object" && props.children.type) {
props.children.mountIndex = 0;
mount(props.children, dom);
} else if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
}
}
vdom.dom = dom;
if (ref) ref.current = dom;
return dom;
}
+function mountMemoComponent(vdom) {
+ let { type, props } = vdom;
+ let renderVdom = type.type(props);
+ vdom.oldRenderVdom = renderVdom;
+ return createDOM(renderVdom);
+}
function mountProviderComponent(vdom) {
let { type, props } = vdom;
let context = type._context;
context._currentValue = props.value;
let renderVdom = props.children;
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountContextComponent(vdom) {
let { type, props } = vdom;
let context = type._context;
let renderVdom = props.children(context._currentValue);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountForwardComponent(vdom) {
let { type, props, ref } = vdom;
let renderVdom = type.render(props, ref);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountClassComponent(vdom) {
let { type, props, ref } = vdom;
let classInstance = new type(props);
if (type.contextType) {
classInstance.context = type.contextType._currentValue;
}
if (ref) ref.current = classInstance;
vdom.classInstance = classInstance;
if (classInstance.UNSAFE_componentWillMount) {
classInstance.UNSAFE_componentWillMount();
}
let renderVdom = classInstance.render();
classInstance.oldRenderVdom = renderVdom;
let dom = createDOM(renderVdom);
if (classInstance.componentDidMount) {
dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
}
return dom;
}
function mountFunctionComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type(props);
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
addEvent(dom, key.toLocaleLowerCase(), newProps[key]);
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {
return vdom.dom;
} else {
const renderVdom = vdom.classInstance ? vdom.classInstance.oldRenderVdom : vdom.oldRenderVdom;
return findDOM(renderVdom);
}
}
export function compareTwoVdom(parentDOM, oldVdom, newVdom, nextDOM) {
if (!oldVdom && !newVdom) {
return;
} else if (oldVdom && !newVdom) {
unMountVdom(oldVdom);
} else if (!oldVdom && newVdom) {
let newDOM = createDOM(newVdom);
if (nextDOM) {
parentDOM.insertBefore(newDOM, nextDOM);
} else {
parentDOM.appendChild(newDOM);
}
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
} else if (oldVdom && newVdom && (oldVdom.type !== newVdom.type)) {
unMountVdom(oldVdom)
let newDOM = createDOM(newVdom)
if (nextDOM) {
parentDOM.insertBefore(newDOM, nextDOM);
} else {
parentDOM.appendChild(newDOM);
}
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
} else {
updateElement(oldVdom, newVdom);
}
}
function updateElement(oldVdom, newVdom) {
+ if (oldVdom.type && oldVdom.type.$$typeof === REACT_MEMO) {
+ updateMemoComponent(oldVdom, newVdom);
+ } if (oldVdom.type.$$typeof === REACT_CONTEXT) {
updateContextComponent(oldVdom, newVdom);
} else if (oldVdom.type.$$typeof === REACT_PROVIDER) {
updateProviderComponent(oldVdom, newVdom);
} else if (oldVdom.type === REACT_TEXT) {
let currentDOM = newVdom.dom = findDOM(oldVdom);
if (oldVdom.props !== newVdom.props) {
currentDOM.textContent = newVdom.props;
}
return;
} else if (typeof oldVdom.type === 'string') {
let currentDOM = newVdom.dom = findDOM(oldVdom);
updateProps(currentDOM, oldVdom.props, newVdom.props);
updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);
} else if (typeof oldVdom.type === 'function') {
if (oldVdom.type.isReactComponent) {
updateClassComponent(oldVdom, newVdom);
} else {
updateFunctionComponent(oldVdom, newVdom);
}
}
}
+function updateMemoComponent(oldVdom, newVdom) {
+ let { type } = oldVdom;
+ if (!type.compare(oldVdom.props, newVdom.props)) {
+ const oldDOM = findDOM(oldVdom);
+ const parentDOM = oldDOM.parentNode;
+ const { type } = newVdom;
+ let renderVdom = type.type(newVdom.props);
+ compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
+ newVdom.oldRenderVdom = renderVdom;
+ } else {
+ newVdom.oldRenderVdom = oldVdom.oldRenderVdom;
+ }
+}
function updateProviderComponent(oldVdom, newVdom) {
let parentDOM = findDOM(oldVdom).parentNode;
let { type, props } = newVdom;
let context = type._context;
context._currentValue = props.value;
let renderVdom = props.children;
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
newVdom.oldRenderVdom = renderVdom;
}
function updateContextComponent(oldVdom, newVdom) {
let parentDOM = findDOM(oldVdom).parentNode;
let { type, props } = newVdom;
let context = type._context;
let renderVdom = props.children(context._currentValue);
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
newVdom.oldRenderVdom = renderVdom;
}
function updateFunctionComponent(oldVdom, newVdom) {
let currentDOM = findDOM(oldVdom);
if (!currentDOM) return;
let parentDOM = currentDOM.parentNode;
const { type, props } = newVdom;
const newRenderVdom = type(props);
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom);
newVdom.oldRenderVdom = newRenderVdom;
}
function updateClassComponent(oldVdom, newVdom) {
let classInstance = newVdom.classInstance = oldVdom.classInstance;
if (classInstance.UNSAFE_componentWillReceiveProps) {
classInstance.UNSAFE_componentWillReceiveProps(newVdom.props);
}
classInstance.updater.emitUpdate(newVdom.props);
}
function updateChildren(parentDOM, oldVChildren, newVChildren) {
oldVChildren = (Array.isArray(oldVChildren) ? oldVChildren : oldVChildren ? [oldVChildren] : []);
newVChildren = (Array.isArray(newVChildren) ? newVChildren : newVChildren ? [newVChildren] : []);
const keyedOldMap = {}
let lastPlacedIndex = -1;
oldVChildren.forEach((oldVChild, index) => {
let oldKey = oldVChild.key ? oldVChild.key : index;
keyedOldMap[oldKey] = oldVChild;
})
let patch = [];
newVChildren.forEach((newVChild, index) => {
newVChild.mountIndex = index;
let newKey = newVChild.key ? newVChild.key : index;
let oldVChild = keyedOldMap[newKey];
if (oldVChild) {
updateElement(oldVChild, newVChild);
if (oldVChild.mountIndex < lastPlacedIndex) {
patch.push({
type: MOVE,
oldVChild,
newVChild,
mountIndex: index
});
}
delete keyedOldMap[newKey];
lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex);
} else {
patch.push({
type: PLACEMENT,
newVChild,
mountIndex: index
});
}
});
const moveVChildren = patch.filter(action => action.type === MOVE).map(action => action.oldVChild);
Object.values(keyedOldMap).concat(moveVChildren).forEach(oldVChild => {
let currentDOM = findDOM(oldVChild);
parentDOM.removeChild(currentDOM);
});
patch.forEach(action => {
const { type, oldVChild, newVChild, mountIndex } = action;
let oldTrueDOMs = parentDOM.childNodes;
if (type === PLACEMENT) {
let newDOM = createDOM(newVChild);
const oldTrueDOM = oldTrueDOMs[mountIndex];
if (oldTrueDOM) {
parentDOM.insertBefore(newDOM, oldTrueDOM);
} else {
parentDOM.appendChild(newDOM);
}
} else if (type === MOVE) {
let oldDOM = findDOM(oldVChild);
let oldTrueDOM = oldTrueDOMs[mountIndex];
if (oldTrueDOM) {
parentDOM.insertBefore(oldDOM, oldTrueDOM);
} else {
parentDOM.appendChild(oldDOM);
}
}
});
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
childrenVdom[i].mountIndex = i;
mount(childrenVdom[i], parentDOM);
}
}
function unMountVdom(vdom) {
const { props, ref } = vdom;
let currentDOM = findDOM(vdom);
if (vdom.classInstance && vdom.classInstance.componentWillUnmount) {
vdom.classInstance.componentWillUnmount();
}
if (ref) {
ref.current = null;
}
if (props.children) {
const children = Array.isArray(props.children) ? props.children : [props.children];
children.forEach(unMountVdom);
}
if (currentDOM) currentDOM.remove();
}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot
}
export default ReactDOM
src\index.js
import React from './react';
import ReactDOM from './react-dom/client';
const DOMRoot = ReactDOM.createRoot(
document.getElementById('root')
);
class Dialog extends React.Component {
constructor(props) {
super(props);
this.node = document.createElement('div');
document.body.appendChild(this.node);
}
render() {
return ReactDOM.createPortal(
<div className="dialog">
{this.props.children}
</div>,
this.node
);
}
componentWillUnmount() {
window.document.body.removeChild(this.node);
}
}
class App extends React.Component {
render() {
return (
<div>
<Dialog>模态窗</Dialog>
</div>
)
}
}
let element = <App />
DOMRoot.render(element);
src\react-dom.js
import { REACT_TEXT, REACT_FORWARD_REF_TYPE, MOVE, PLACEMENT, REACT_PROVIDER, REACT_CONTEXT, REACT_MEMO } from "../constants";
import { addEvent } from '../event';
function render(vdom) {
mount(vdom, this.container);
}
export function mount(vdom, container) {
let newDOM = createDOM(vdom);
if (newDOM) {
container.appendChild(newDOM);
if (newDOM.componentDidMount) {
newDOM.componentDidMount()
}
}
}
export function createDOM(vdom) {
let { type, props, ref } = vdom;
let dom;
if (type && type.$$typeof === REACT_MEMO) {
return mountMemoComponent(vdom);
} else if (type && type.$$typeof === REACT_PROVIDER) {
return mountProviderComponent(vdom)
} else if (type && type.$$typeof === REACT_CONTEXT) {
return mountContextComponent(vdom)
} if (type && type.$$typeof === REACT_FORWARD_REF_TYPE) {
return mountForwardComponent(vdom);
} if (type === REACT_TEXT) {
dom = document.createTextNode(props);
} else if (typeof type === "function") {
if (type.isReactComponent) {
return mountClassComponent(vdom);
} else {
return mountFunctionComponent(vdom);
}
} else {
dom = document.createElement(type);
}
if (props) {
updateProps(dom, {}, props);
if (typeof props.children == "object" && props.children.type) {
props.children.mountIndex = 0;
mount(props.children, dom);
} else if (Array.isArray(props.children)) {
reconcileChildren(props.children, dom);
}
}
vdom.dom = dom;
if (ref) ref.current = dom;
return dom;
}
function mountMemoComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type.type(props);
+ if (!renderVdom) return null;
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountProviderComponent(vdom) {
let { type, props } = vdom;
let context = type._context;
context._currentValue = props.value;
let renderVdom = props.children;
+ if (!renderVdom) return null;
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountContextComponent(vdom) {
let { type, props } = vdom;
let context = type._context;
let renderVdom = props.children(context._currentValue);
+ if (!renderVdom) return null;
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountForwardComponent(vdom) {
let { type, props, ref } = vdom;
let renderVdom = type.render(props, ref);
+ if (!renderVdom) return null;
vdom.oldRenderVdom = renderVdom;
return createDOM(renderVdom);
}
function mountClassComponent(vdom) {
let { type, props, ref } = vdom;
let classInstance = new type(props);
if (type.contextType) {
classInstance.context = type.contextType._currentValue;
}
if (ref) ref.current = classInstance;
vdom.classInstance = classInstance;
if (classInstance.UNSAFE_componentWillMount) {
classInstance.UNSAFE_componentWillMount();
}
let renderVdom = classInstance.render();
+ if (!renderVdom) return null;
classInstance.oldRenderVdom = renderVdom;
let dom = createDOM(renderVdom);
if (classInstance.componentDidMount) {
dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
}
return dom;
}
function mountFunctionComponent(vdom) {
let { type, props } = vdom;
let renderVdom = type(props);
vdom.oldRenderVdom = renderVdom;
+ if (!renderVdom) return null;
return createDOM(renderVdom);
}
function updateProps(dom, oldProps = {}, newProps = {}) {
for (let key in newProps) {
if (key === 'children') {
continue;
} else if (key === 'style') {
let styleObj = newProps[key];
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr];
}
} else if (/^on[A-Z].*/.test(key)) {
addEvent(dom, key.toLocaleLowerCase(), newProps[key]);
} else {
dom[key] = newProps[key];
}
}
for (let key in oldProps) {
if (!newProps.hasOwnProperty(key)) {
dom[key] = null;
}
}
}
export function findDOM(vdom) {
if (!vdom) return null;
if (vdom.dom) {
return vdom.dom;
} else {
const renderVdom = vdom.classInstance ? vdom.classInstance.oldRenderVdom : vdom.oldRenderVdom;
return findDOM(renderVdom);
}
}
export function compareTwoVdom(parentDOM, oldVdom, newVdom, nextDOM) {
if (!oldVdom && !newVdom) {
return;
} else if (oldVdom && !newVdom) {
unMountVdom(oldVdom);
} else if (!oldVdom && newVdom) {
let newDOM = createDOM(newVdom);
if (nextDOM) {
parentDOM.insertBefore(newDOM, nextDOM);
} else {
parentDOM.appendChild(newDOM);
}
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
} else if (oldVdom && newVdom && (oldVdom.type !== newVdom.type)) {
unMountVdom(oldVdom)
let newDOM = createDOM(newVdom)
if (nextDOM) {
parentDOM.insertBefore(newDOM, nextDOM);
} else {
parentDOM.appendChild(newDOM);
}
if (newDOM.componentDidMount) {
newDOM.componentDidMount();
}
} else {
updateElement(oldVdom, newVdom);
}
}
function updateElement(oldVdom, newVdom) {
if (oldVdom.type && oldVdom.type.$$typeof === REACT_MEMO) {
updateMemoComponent(oldVdom, newVdom);
} if (oldVdom.type.$$typeof === REACT_CONTEXT) {
updateContextComponent(oldVdom, newVdom);
} else if (oldVdom.type.$$typeof === REACT_PROVIDER) {
updateProviderComponent(oldVdom, newVdom);
} else if (oldVdom.type === REACT_TEXT) {
let currentDOM = newVdom.dom = findDOM(oldVdom);
if (oldVdom.props !== newVdom.props) {
currentDOM.textContent = newVdom.props;
}
return;
} else if (typeof oldVdom.type === 'string') {
let currentDOM = newVdom.dom = findDOM(oldVdom);
updateProps(currentDOM, oldVdom.props, newVdom.props);
updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children);
} else if (typeof oldVdom.type === 'function') {
if (oldVdom.type.isReactComponent) {
updateClassComponent(oldVdom, newVdom);
} else {
updateFunctionComponent(oldVdom, newVdom);
}
}
}
function updateMemoComponent(oldVdom, newVdom) {
let { type } = oldVdom;
if (!type.compare(oldVdom.props, newVdom.props)) {
const oldDOM = findDOM(oldVdom);
const parentDOM = oldDOM.parentNode;
const { type } = newVdom;
let renderVdom = type.type(newVdom.props);
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
newVdom.oldRenderVdom = renderVdom;
} else {
newVdom.oldRenderVdom = oldVdom.oldRenderVdom;
}
}
function updateProviderComponent(oldVdom, newVdom) {
let parentDOM = findDOM(oldVdom).parentNode;
let { type, props } = newVdom;
let context = type._context;
context._currentValue = props.value;
let renderVdom = props.children;
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
newVdom.oldRenderVdom = renderVdom;
}
function updateContextComponent(oldVdom, newVdom) {
let parentDOM = findDOM(oldVdom).parentNode;
let { type, props } = newVdom;
let context = type._context;
let renderVdom = props.children(context._currentValue);
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom);
newVdom.oldRenderVdom = renderVdom;
}
function updateFunctionComponent(oldVdom, newVdom) {
let currentDOM = findDOM(oldVdom);
if (!currentDOM) return;
let parentDOM = currentDOM.parentNode;
const { type, props } = newVdom;
const newRenderVdom = type(props);
compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, newRenderVdom);
newVdom.oldRenderVdom = newRenderVdom;
}
function updateClassComponent(oldVdom, newVdom) {
let classInstance = newVdom.classInstance = oldVdom.classInstance;
if (classInstance.UNSAFE_componentWillReceiveProps) {
classInstance.UNSAFE_componentWillReceiveProps(newVdom.props);
}
classInstance.updater.emitUpdate(newVdom.props);
}
function updateChildren(parentDOM, oldVChildren, newVChildren) {
oldVChildren = (Array.isArray(oldVChildren) ? oldVChildren : oldVChildren ? [oldVChildren] : []);
newVChildren = (Array.isArray(newVChildren) ? newVChildren : newVChildren ? [newVChildren] : []);
const keyedOldMap = {}
let lastPlacedIndex = -1;
oldVChildren.forEach((oldVChild, index) => {
let oldKey = oldVChild.key ? oldVChild.key : index;
keyedOldMap[oldKey] = oldVChild;
})
let patch = [];
newVChildren.forEach((newVChild, index) => {
newVChild.mountIndex = index;
let newKey = newVChild.key ? newVChild.key : index;
let oldVChild = keyedOldMap[newKey];
if (oldVChild) {
updateElement(oldVChild, newVChild);
if (oldVChild.mountIndex < lastPlacedIndex) {
patch.push({
type: MOVE,
oldVChild,
newVChild,
mountIndex: index
});
}
delete keyedOldMap[newKey];
lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex);
} else {
patch.push({
type: PLACEMENT,
newVChild,
mountIndex: index
});
}
});
const moveVChildren = patch.filter(action => action.type === MOVE).map(action => action.oldVChild);
Object.values(keyedOldMap).concat(moveVChildren).forEach(oldVChild => {
let currentDOM = findDOM(oldVChild);
parentDOM.removeChild(currentDOM);
});
patch.forEach(action => {
const { type, oldVChild, newVChild, mountIndex } = action;
let oldTrueDOMs = parentDOM.childNodes;
if (type === PLACEMENT) {
let newDOM = createDOM(newVChild);
const oldTrueDOM = oldTrueDOMs[mountIndex];
if (oldTrueDOM) {
parentDOM.insertBefore(newDOM, oldTrueDOM);
} else {
parentDOM.appendChild(newDOM);
}
} else if (type === MOVE) {
let oldDOM = findDOM(oldVChild);
let oldTrueDOM = oldTrueDOMs[mountIndex];
if (oldTrueDOM) {
parentDOM.insertBefore(oldDOM, oldTrueDOM);
} else {
parentDOM.appendChild(oldDOM);
}
}
});
}
function reconcileChildren(childrenVdom, parentDOM) {
for (let i = 0; i < childrenVdom.length; i++) {
childrenVdom[i].mountIndex = i;
mount(childrenVdom[i], parentDOM);
}
}
function unMountVdom(vdom) {
const { props, ref } = vdom;
let currentDOM = findDOM(vdom);
if (vdom.classInstance && vdom.classInstance.componentWillUnmount) {
vdom.classInstance.componentWillUnmount();
}
if (ref) {
ref.current = null;
}
if (props.children) {
const children = Array.isArray(props.children) ? props.children : [props.children];
children.forEach(unMountVdom);
}
if (currentDOM) currentDOM.remove();
}
class Root {
constructor(container) {
this.container = container;
this.render = render
}
}
function createRoot(container) {
return new Root(container);
}
const ReactDOM = {
createRoot,
createPortal: function (vdom, container) {
mount(vdom, container);
}
}
export default ReactDOM;