1. 什么是React #

2.创建项目 #

create-react-app zhufengreact
cd zhufengreact
npm install cross-env

3.JSX渲染 #

3.1 什么是JSX #

3.2 什么是元素 #

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"
}

3.3 JSX实现 #

3.3.1 package.json #

{
  "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"
  },
}

3.3.2 src\index.js #

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);

3.3.3 constants.js #

src\constants.js

export const REACT_TEXT = Symbol('REACT_TEXT');
export const REACT_ELEMENT = Symbol('react.element');

3.3.4 src\utils.js #

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;
}

3.3.5 react.js #

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;

3.3.6 client.js #

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

4.组件 #

4.1 函数(定义的)组件 #

xuan_ran_han_shu_zu_jian_1626351799850

4.2 实现 #

4.2.1 src\index.js #

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);

4.2.2 src\react-dom.js #

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

4.2 类(定义的)组件 #

lei_zu_jian_xuan_ran_1626352042061

4.2.1 src\index.js #

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);

4.2.2 src\Component.js #

src\Component.js

export class Component {
    static isReactComponent = true
    constructor(props) {
        this.props = props;
    }
}

4.2.3 src\react.js #

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;

4.2.4 src\react-dom.js #

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

5.类组件的更新 #

5.1 组件状态 #

5.3 更新组新实现 #

5.3.1 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);
    }
    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);

5.3.2 src\Component.js #

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();
+    }
}

5.3.3 react-dom/client.js #

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

6.合成事件和批量更新 #

6.1 src\index.js #

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);

6.2 src\Component.js #

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();
    }
}

6.3 event.js #

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;
    }
}

6.4 src\react-dom.js #

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

7.ref #

7.1 为 DOM 元素添加 ref #

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);

7.2 为 class 组件添加 Ref #

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);

7.3 Ref转发 #

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);

7.4 ref实现 #

7.4.1 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');

7.4.2 src\react.js #

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;

7.4.3 react-dom.js #

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

8.基本生命周期 #

react15_1626405471667.jpg

8.1 src\index.js #

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
*/

8.2 src\Component.js #

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);
+        }
    }
}

8.3 src\react-dom.js #

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

9.子组件生命周期 #

counterdomdiff_1626412843123

9.1 src\index.js #

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);

9.2 src\react-dom.js #

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

9.3 Component.js #

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);
        }
    }
}

10.DOM-DIFF算法 #

domdiff_1626417725773

10.1 src\index.js #

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);

10.2 src\constants.js #

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';

10.3 src\react-dom\client.js #

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

11.新的生命周期 #

react16_1626532331619

11.1 getDerivedStateFromProps #

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);

11.2 getSnapshotBeforeUpdate #

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);

11.3 实现 #

11.3.1 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.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);
        }
    }
}

12. Context(上下文) #

contextapi_1626532435193

12.1 src\index.js #

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);

12.2 src\constants.js #

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';

12.3 src\Component.js #

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);
        }
    }
}

12.4 src\react.js #

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;

12.5 src\react-dom.js #

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

13. 高阶组件 #

const NewComponent = higherOrderComponent(OldComponent)

13.1 属性代理 #

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);

13.3 反向继承 #

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);

13.3 实现 #

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;

14. render props #

14.1 原生实现 #

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'));

14.2 children #

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'));

14.3 render属性 #

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'));

14.4 HOC #

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'));

15.性能优化 #

15.1 src\index.js #

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);

15.2 src\constants.js #

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')

15.3 src\utils.js #

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;
+}

15.4 src\react.js #

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;

15.5 client.js #

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

16.Portal #

16.1 src\index.js #

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);

16.2 src\react-dom.js #

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;