导航菜单

  • 0.api
  • 0.Async
  • 0.module
  • 1.ES2015
  • 2.Promise
  • 3.Node
  • 4.NodeInstall
  • 5.REPL
  • 6.NodeCore
  • 7.module&NPM
  • 8.Encoding
  • 9.Buffer
  • 10.fs
  • 11.Stream-1
  • 11.Stream-2
  • 11.Stream-3
  • 11.Stream-4
  • 12-Network-2
  • 12.NetWork-3
  • 12.Network-1
  • 13.tcp
  • 14.http-1
  • 14.http-2
  • 15.compress
  • 16.crypto
  • 17.process
  • 18.yargs
  • 19.cache
  • 20.action
  • 21.https
  • 22.cookie
  • 23.session
  • 24.express-1
  • 24.express-2
  • 24.express-3
  • 24.express-4
  • 25.koa-1
  • 26.webpack-1-basic
  • 26.webpack-2-optimize
  • 26.webpack-3-file
  • 26.webpack-4.tapable
  • 26.webpack-5-AST
  • 26.webpack-6-sources
  • 26.webpack-7-loader
  • 26.webpack-8-plugin
  • 26.webpack-9-hand
  • 26.webpack-10-prepare
  • 28.redux
  • 28.redux-jwt-back
  • 28.redux-jwt-front
  • 29.mongodb-1
  • 29.mongodb-2
  • 29.mongodb-3
  • 29.mongodb-4
  • 29.mongodb-5
  • 29.mongodb-6
  • 30.cms-1-mysql
  • 30.cms-2-mysql
  • 30.cms-3-mysql
  • 30.cms-4-nunjucks
  • 30.cms-5-mock
  • 30.cms-6-egg
  • 30.cms-7-api
  • 30.cms-8-roadhog
  • 30.cms-9-yaml
  • 30.cms-10-umi
  • 30.cms-12-dva
  • 30.cms-13-dva-ant
  • 30.cms-14-front
  • 30.cms-15-deploy
  • 31.dva
  • 31.cms-13-dva-antdesign
  • 33.redis
  • 34.unittest
  • 35.jwt
  • 36.websocket-1
  • 36.websocket-2
  • 38.chat-api-1
  • 38.chat-api-2
  • 38.chat-3
  • 38.chat-api-3
  • 38.chat
  • 38.chat2
  • 38.chat2
  • 39.crawl-0
  • 39.crawl-1
  • 39.crawl-2
  • 40.deploy
  • 41.safe
  • 42.test
  • 43.nginx
  • 44.enzyme
  • 45.docker
  • 46.elastic
  • 47.oauth
  • 48.wxpay
  • index
  • 52.UML
  • 53.design
  • index
  • 54.linux
  • 57.ts
  • 56.react-ssr
  • 58.ts_react
  • 59.ketang
  • 59.ketang2
  • 61.1.devops-linux
  • 61.2.devops-vi
  • 61.3.devops-user
  • 61.4.devops-auth
  • 61.5.devops-shell
  • 61.6.devops-install
  • 61.7.devops-system
  • 61.8.devops-service
  • 61.9.devops-network
  • 61.10.devops-nginx
  • 61.11.devops-docker
  • 61.12.devops-jekins
  • 61.13.devops-groovy
  • 61.14.devops-php
  • 61.15.devops-java
  • 61.16.devops-node
  • 61.17.devops-k8s
  • 62.1.react-basic
  • 62.2.react-state
  • 62.3.react-high
  • 62.4.react-optimize
  • 62.5.react-hooks
  • 62.6.react-immutable
  • 62.7.react-mobx
  • 62.8.react-source
  • 63.1.redux
  • 63.2.redux-middleware
  • 63.3.redux-hooks
  • 63.4.redux-saga
  • 63.5.redux-saga-hand
  • 64.1.router
  • 64.2.router-connected
  • 65.1.typescript
  • 65.2.typescript
  • 65.3.typescript
  • 65.4.antd
  • 65.4.definition
  • 66-1.vue-base
  • 66-2.vue-component
  • 66-3.vue-cli3.0
  • 66-4.$message组件
  • 66-5.Form组件
  • 66-6.tree
  • 66-7.vue-router-apply
  • 66-8.axios-apply
  • 66-9.vuex-apply
  • 66-10.jwt-vue
  • 66-11.vue-ssr
  • 66-12.nuxt-apply
  • 66-13.pwa
  • 66-14.vue单元测试
  • 66-15.权限校验
  • 67-1-network
  • 68-2-wireshark
  • 7.npm2
  • 69-hooks
  • 70-deploy
  • 71-hmr
  • 72.deploy
  • 73.import
  • 74.mobile
  • 75.webpack-1.文件分析
  • 75.webpack-2.loader
  • 75.webpack-3.源码流程
  • 75.webpack-4.tapable
  • 75.webpack-5.prepare
  • 75.webpack-6.resolve
  • 75.webpack-7.loader
  • 75.webpack-8.module
  • 75.webpack-9.chunk
  • 75.webpack-10.asset
  • 75.webpack-11.实现
  • 76.react_optimize
  • 77.ts_ketang_back
  • 77.ts_ketang_front
  • 78.vue-domdiff
  • 79.grammar
  • 80.tree
  • 81.axios
  • 82.1.react
  • 82.2.react-high
  • 82.3.react-router
  • 82.4.redux
  • 82.5.redux_middleware
  • 82.6.connected
  • 82.7.saga
  • 82.8.dva
  • 82.8.dva-source
  • 82.9.roadhog
  • 82.10.umi
  • 82.11.antdesign
  • 82.12.ketang-front
  • 82.12.ketang-back
  • 83.upload
  • 84.graphql
  • 85.antpro
  • 86.1.uml
  • 86.2.design
  • 87.postcss
  • 88.react16-1
  • 89.nextjs
  • 90.react-test
  • 91.react-ts
  • 92.rbac
  • 93.tsnode
  • 94.1.JavaScript
  • 94.2.JavaScript
  • 94.3.MODULE
  • 94.4.EventLoop
  • 94.5.文件上传
  • 94.6.https
  • 94.7. nginx
  • 95.1. react
  • 95.2.react
  • 96.1.react16
  • 96.2.fiber
  • 96.3.fiber
  • 97.serverless
  • 98.websocket
  • 100.1.react-basic
  • 101.1.monitor
  • 101.2.monitor
  • 102.java
  • 103.1.webpack-usage
  • 103.2.webpack-bundle
  • 103.3.webpack-ast
  • 103.4.webpack-flow
  • 103.5.webpack-loader
  • 103.6.webpack-tapable
  • 103.7.webpack-plugin
  • 103.8.webpack-optimize1
  • 103.9.webpack-optimize2
  • 103.10.webpack-hand
  • 103.11.webpack-hmr
  • 103.11.webpack5
  • 103.13.splitChunks
  • 103.14.webpack-sourcemap
  • 103.15.webpack-compiler1
  • 103.15.webpack-compiler2
  • 103.16.rollup.1
  • 103.16.rollup.2
  • 103.16.rollup.3
  • 103.16.vite.basic
  • 103.16.vite.source
  • 103.16.vite.plugin
  • 103.16.vite.1
  • 103.16.vite.2
  • 103.17.polyfill
  • 104.1.binary
  • 104.2.binary
  • 105.skeleton
  • 106.1.react
  • 106.2.react_hooks
  • 106.3.react_router
  • 106.4.redux
  • 106.5.redux_middleware
  • 106.6.connected-react-router
  • 106.6.redux-first-history
  • 106.7.redux-saga
  • 106.8.dva
  • 106.9.umi
  • 106.10.ketang
  • 106.11.antdesign
  • 106.12.antpro
  • 106.13.router-6
  • 106.14.ssr
  • 106.15.nextjs
  • 106.16.1.cms
  • 106.16.2.cms
  • 106.16.3.cms
  • 106.16.4.cms
  • 106.16.mobx
  • 106.17.fomily
  • 107.fiber
  • 108.http
  • 109.1.webpack_usage
  • 109.2.webpack_source
  • 109.3.dll
  • 110.nest.js
  • 111.xstate
  • 112.Form
  • 113.redux-saga
  • 114.react+typescript
  • 115.immer
  • 116.pro5
  • 117.css-loader
  • 118.1.umi-core
  • 119.2.module-federation
  • 119.1.module-federation
  • 120.create-react-app
  • 121.react-scripts
  • 122.react-optimize
  • 123.jsx-runtime
  • 124.next.js
  • 125.1.linux
  • 125.2.linux-vi
  • 125.3.linux-user
  • 125.4.linux-auth
  • 125.5.linux-shell
  • 125.6.linux-install
  • 125.7.linux-system
  • 125.8.linux-service
  • 125.9.linux-network
  • 125.10.nginx
  • 125.11.docker
  • 125.12.ci
  • 125.13.k8s
  • 125.14.k8s
  • 125.15.k8s
  • 125.16.k8s
  • 126.11.react-1
  • 126.12.react-2
  • 126.12.react-3
  • 126.12.react-4
  • 126.12.react-5
  • 126.12.react-6
  • 126.12.react-7
  • 126.12.react-8
  • 127.frontend
  • 128.rollup
  • 129.px2rem-loader
  • 130.health
  • 131.hooks
  • 132.keepalive
  • 133.vue-cli
  • 134.react18
  • 134.2.react18
  • 134.3.react18
  • 135.function
  • 136.toolkit
  • 137.lerna
  • 138.create-vite
  • 139.cli
  • 140.antd
  • 141.react-dnd
  • 142.1.link
  • 143.1.gulp
  • 143.2.stream
  • 143.3.gulp
  • 144.1.closure
  • 144.2.v8
  • 144.3.gc
  • 145.react-router-v6
  • 146.browser
  • 147.lighthouse
  • 148.1.basic
  • 148.2.basic
  • 148.3.basic
  • 148.4.basic
  • 148.5.basic
  • 149.1.vite
  • 149.2.vite
  • 149.3.vite
  • 149.4.vite
  • 150.react-window
  • 151.react-query
  • 152.useRequest
  • 153.transition
  • 154.emotion
  • 155.1.formily
  • 155.2.formily
  • 155.3.formily
  • 155.3.1.mobx.usage
  • 155.3.2.mobx.source
  • 156.vue-loader
  • 103.11.mf
  • 157.1.react18
  • 158.umi4
  • 159.rxjs
  • 159.rxjs2
  • 160.bff
  • 161.zustand
  • 162.vscode
  • 163.emp
  • 164.cors
  • 1. 渲染原生组件
    • 1.1 src\index.js
    • 1.2 react\index.js
    • 1.3 react\constants.js
    • 1.4 react\vdom.js
    • 1.5 react-dom\index.js
    • 1.6 react\utils.js
    • 1.7 react\event.js
  • 2. 渲染类组件和函数组件
    • 2.1 src\index.js
    • 2.2 react\constants.js
    • 2.3 react\index.js
    • 2.4 react\vdom.js
    • 2.5 react\component.js
  • 3. 组件更新
    • 3.1 src\index.js
    • 3.2 react\utils.js
    • 3.3 react\vdom.js
    • 3.4 src\component.js
    • 3.5 react\event.js
  • 4. 比较更新子节点
    • 4.1 深度优先遍历
    • 4.2 src\index.js
    • 4.3 react\utils.js
    • 4.4 react\vdom.js
  • 5. key的处理
    • 5.1 src\index.js
    • 5.2 react\constants.js
    • 5.3 react\vdom.js
  • 6. 支持旧版生命周期
    • 6.1 src\index.js
    • 6.2 react\vdom.js
  • 7. 新版生命周期和ref
    • 7.1 getDerivedStateFromProps
      • 7.1.1 index.js
      • 7.1.2 react\vdom.js
    • 7.2 ScrollingList
      • 7.2.1 index.js
      • 7.2.2 react\component.js
      • 7.2.3 react\vdom.js
  • 8. context
    • 8.1 src\index.js
    • 8.2 react\index.js
    • 8.3 react\vdom.js
  • 9. DOM-DIFF
    • 9.1 Tree DIFF
    • 9.2 Component DIFF
    • 9.3 Element DIFF

1. 渲染原生组件 #

1.1 src\index.js #

import React from './react';
import ReactDOM from './react-dom';

let onClick = () => { alert('hello'); }
let element = React.createElement('button',
  { id: 'sayHello', onClick },
  "say", React.createElement('span', { style: { color: 'red' } }, 'Hello'));

ReactDOM.render(
  element,
  document.getElementById('root')
);

1.2 react\index.js #

src\react\index.js

import { TEXT, ELEMENT } from './constants';
import { ReactElement } from './vdom';

function createElement(type, config = {}, ...children) {
    delete config.__source;
    delete config.__self;
    let { key, ref, ...props } = config;
    let $$typeof = null;
    if (typeof type === 'string') {
        $$typeof = ELEMENT;
    }
    props.children = children.map(item => typeof item === 'object' || typeof item === 'function' ? item
        : { $$typeof: TEXT, type: TEXT, content: item });
    return ReactElement($$typeof, type, key, ref, props);
}

const React = {
    createElement
}
export default React;

1.3 react\constants.js #

src\react\constants.js

export const TEXT = Symbol.for('TEXT');
export const ELEMENT = Symbol.for('ELEMENT');

1.4 react\vdom.js #

src\react\vdom.js

import { TEXT, ELEMENT } from './constants';
import { setProps ,onlyOne,flatten} from './utils';
export function createDOM(element) {
     element = onlyOne(element);
    let { $$typeof } = element;
    let dom = null;
    if (!$$typeof) {
        dom = document.createTextNode(element);
    }else if ($$typeof === TEXT) {
        dom = document.createTextNode(element.content);
    }else if ($$typeof === ELEMENT) {
        dom = createNativeDOM(element);
    }
    element.dom = dom;
    return dom;
}

function createNativeDOM(element) {
    let { type, props } = element;
    let dom = document.createElement(type);
    createChildren(element, dom);
    setProps(dom, props);
    if (props.ref)
        props.ref.current = dom;
    return dom;
}

function createChildren(element, parentNode) {
    element.props.children && flatten(element.props.children).forEach((childElement, index) => {
        childElement._mountIndex = index;
        let childDOM = createDOM(childElement);
        parentNode.appendChild(childDOM);
    });
}

export function ReactElement($$typeof, type, key, ref, props) {
    let element = {
        $$typeof,
        type,
        props,
        key,
        ref
    };
    return element;
}

1.5 react-dom\index.js #

src\react-dom\index.js

import { createDOM } from '../react/vdom';
export function render(element, container) {
    let dom = createDOM(element);
    container.appendChild(dom);
}
export default {render}

1.6 react\utils.js #

src\react\utils.js

import { addEvent } from './event';
export function setProps(elem, props) {
    for (let key in props) {
        if (key !== 'children') {
            let value = props[key];
            setProp(elem, key, value);
        }
    }
}
function setProp(elem, key, value) {
    if (/^on/.test(key)) {
        addEvent(elem, key, value);
    } else if (key === 'style') {
        if (value) {
            for (let styleName in value) {
                if (value.hasOwnProperty(styleName)) {
                    elem.style[styleName] = value[styleName];
                }
            }
        }
    } else {
        elem.setAttribute(key, value);
    }
    return elem;
}
export function onlyOne(obj) {
    return Array.isArray(obj) ? obj[0] : obj;
}
export function isFunction(obj) {
    return typeof obj === 'function';
}
export function flatten(array) {
    var flattend = [];
    (function flat(array) {
        array.forEach(function (el) {
            if (Array.isArray(el)) flat(el);
            else flattend.push(el);
        });
    })(array);
    return flattend;
}

1.7 react\event.js #

src\react\event.js

export function addEvent(dom, eventType, listener) {
    eventType = eventType.toLowerCase();
    let eventStore = dom.eventStore || (dom.eventStore = {});
    eventStore[eventType] = listener;
    document.addEventListener(eventType.slice(2), dispatchEvent, false);
}
let syntheticEvent;

function dispatchEvent(event) {
    let { type, target } = event;
    syntheticEvent = getSyntheticEvent(event);
    while (target) {
        let { eventStore } = target;
        let listener = eventStore && eventStore[eventType];
        if (listener) {
            listener.call(target, syntheticEvent);
        }
        target = target.parentNode;
    }
    for (let key in syntheticEvent) {
        if (syntheticEvent.hasOwnProperty(key))
            delete syntheticEvent[key];
    }
}
function persist() {
    syntheticEvent = {};
    Object.setPrototypeOf(syntheticEvent, {
        persist
    });
}
function getSyntheticEvent(nativeEvent) {
    if (!syntheticEvent) {
        persist();
    }
    syntheticEvent.nativeEvent = nativeEvent;
    syntheticEvent.currentTarget = nativeEvent.target;
    for (let key in nativeEvent) {
        if (typeof nativeEvent[key] == 'function') {
            syntheticEvent[key] = nativeEvent[key].bind(nativeEvent);
        } else {
            syntheticEvent[key] = nativeEvent[key];
        }
    }
    return syntheticEvent;
}

2. 渲染类组件和函数组件 #

2.1 src\index.js #

src\index.js

import React from './react';
import ReactDOM from './react-dom';
+class ClassCounter extends React.Component {
+    constructor(props) {
+        super(props);
+    }
+    render() {
+        return React.createElement('div', { id: 'counter' }, 'hello');
+    }
+}
+function FunctionCounter(props) {
+    return React.createElement('div', { id: 'counter' }, 'hello');
+}

+let element1 = React.createElement('div', { id: 'counter' }, 'hello');
+let element2 = React.createElement(ClassCounter);
+let element3 = React.createElement(FunctionCounter);
ReactDOM.render(
+    element1,
    document.getElementById('root')
);

2.2 react\constants.js #

src\react\constants.js

export const TEXT = Symbol.for('TEXT');
+export const ELEMENT = Symbol.for('ELEMENT');
+export const FUNCTION_COMPONENT = Symbol.for('FUNCTION_COMPONENT');
+export const CLASS_COMPONENT = Symbol.for('CLASS_COMPONENT');

2.3 react\index.js #

src\react\index.js

+import { TEXT, ELEMENT, FUNCTION_COMPONENT, CLASS_COMPONENT } from './constants';
import { ReactElement } from './vdom';
+import { Component } from './component';
function createElement(type, props = {}, ...children) {
    let $$typeof = null;
    if (typeof type === 'string') {
        $$typeof = ELEMENT;
+    } else if (typeof type === 'function' && type.prototype.isReactComponent) {
+        $$typeof = CLASS_COMPONENT;
+    } else if (typeof type === 'function') {
+        $$typeof = FUNCTION_COMPONENT;
+    }
    props.children = children.map(item => typeof item === 'object' || typeof item === 'function' ? item
        : { $$typeof: TEXT, type: TEXT, content: item });
    return ReactElement($$typeof, type, props);
}
+export {
+    Component
+}
+const React = {
    createElement,
+    Component
+}
export default React;

2.4 react\vdom.js #

src\react\vdom.js

+import { TEXT, ELEMENT, FUNCTION_COMPONENT, CLASS_COMPONENT } from './constants';
import { setProps,onlyOne,flatten } from './utils';
export function createDOM(element) {
    element = onlyOne(element);
    let { $$typeof } = element;
    let dom = null;
    if (!$$typeof) {
        dom = document.createTextNode(element);
    } else if ($$typeof === TEXT) {
        dom = document.createTextNode(element.content);
    } else if ($$typeof === ELEMENT) {
        dom = createNativeDOM(element);
+    } else if ($$typeof === FUNCTION_COMPONENT) {
+        dom = createFunctionComponentDOM(element);
+    } else if ($$typeof === CLASS_COMPONENT) {
+        dom = createClassComponentDOM(element);
    }
    element.dom = dom;
    return dom;
}

function createNativeDOM(element) {
    let { type, props } = element;
    let dom = document.createElement(type);
    createChildren(element, dom);
    setProps(dom, props);
    return dom;
}

function createChildren(element, parentNode) {
    element.props.children && flatten(element.props.children).forEach((childElement, index) => {
        childElement._mountIndex = index;
        let childDOM = createDOM(childElement);
        childElement.dom = childDOM;
        parentNode.appendChild(childDOM);
    });
}
+function createFunctionComponentDOM(element) {
+    let { type, props } = element;
+    let renderElement = type(props);
+    element.renderElement = renderElement;
+    let newDOM = createDOM(renderElement);
+    renderElement.dom = newDOM;
+    return newDOM;
+}
+function createClassComponentDOM(element) {
+    let { type, props } = element;
+    let componentInstance = new type(props);
+    element.componentInstance = componentInstance;
+    let renderElement = componentInstance.render();
+    componentInstance.renderElement = renderElement;
+    let newDOM = createDOM(renderElement);
+    return newDOM;
+}
export function ReactElement($$typeof, type, key, ref, props) {
    let element = {
        $$typeof,
        type,
        props,
        key,
        ref
    };
    return element;
}

2.5 react\component.js #

src\react\component.js

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

3. 组件更新 #

setState2

3.1 src\index.js #

src\index.js

import React from './react';
import ReactDOM from './react-dom';
class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = { number: 0 };
+        setInterval(() => {
+            this.setState({ number: this.state.number + 1 });
+        }, 2000);
    }
    render() {
+        //return <div id={'counter' + this.state.number}></div>
+        //return <FunctionCounter number={this.state.number} />
+        return <ClassCounter number={this.state.number} />
    }
}
+class ClassCounter extends React.Component {
+    render() {
+        return (
+            <div id={'counter' + this.props.number}></div>
+        )
+    }
+}
+function FunctionCounter(props) {
+    return (
+        <div id={'counter' + props.number}></div>
+    )
+}
+let element = React.createElement(Counter, {});
ReactDOM.render(
    element,
    document.getElementById('root')
);

3.2 react\utils.js #

src\react\utils.js

import { addEvent } from './event';
export function setProps(elem, props) {
    for (let key in props) {
        if (key !== 'children') {
            let value = props[key];
            setProp(elem, key, value);
        }
    }
}
function setProp(elem, key, value) {
    if (/^on/.test(key)) {
        addEvent(elem, key, value);
    } else if (key === 'style') {
        if (value) {
            for (let styleName in value) {
                if (value.hasOwnProperty(styleName)) {
                    elem.style[styleName] = value[styleName];
                }
            }
        }
    } else {
        elem.setAttribute(key, value);
    }
    return elem;
}

export function patchProps(elem, oldProps, newProps) {
    for (let key in oldProps) {
        if (key !== 'children') {
            if (newProps.hasOwnProperty(key)) {
                setProp(elem, key, newProps[key]);
            } else {
                elem.removeAttribute(key);
            }
        }
    }
    for (let key in newProps) {
        if (key !== 'children' && !newProps.hasOwnProperty(key)) {
            setProp(elem, key, newProps[key])
        }
    }
}
export function onlyOne(obj) {
    return Array.isArray(obj) ? obj[0] : obj;
}
+export function isFunction(obj) {
+    return typeof obj === 'function';
+}

3.3 react\vdom.js #

src\react\vdom.js

import { TEXT, ELEMENT, FUNCTION_COMPONENT, CLASS_COMPONENT } from './constants';
+import { setProps, onlyOne, flatten,patchProps } from './utils';
export function createDOM(element) {
+    element = onlyOne(element);
    let { $$typeof } = element;
    let dom = null;
    if (!$$typeof) {
        dom = document.createTextNode(element);
    } else if ($$typeof === TEXT) {
        dom = document.createTextNode(element.content);
    } else if ($$typeof === ELEMENT) {
        dom = createNativeDOM(element);
    } else if ($$typeof === FUNCTION_COMPONENT) {
        dom = createFunctionComponentDOM(element);
    } else if ($$typeof === CLASS_COMPONENT) {
        dom = createClassComponentDOM(element);
    }
    element.dom = dom;
    return dom;
}

function createNativeDOM(element) {
    let { type, props } = element;
    let dom = document.createElement(type);
    createChildren(element, dom);
    setProps(dom, props);
    return dom;
}

function createChildren(element, parentNode) {
    element.props.children && flatten(element.props.children).forEach((childElement, index) => {
        childElement._mountIndex = index;
        let childDOM = createDOM(childElement);
        childElement.dom = childDOM;
        parentNode.appendChild(childDOM);
    });
}
function createFunctionComponentDOM(element) {
    let { type, props } = element;
    let renderElement = type(props);
    element.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    renderElement.dom = newDOM;
    return newDOM;
}
function createClassComponentDOM(element) {
    let { type, props } = element;
    let componentInstance = new type(props);
    element.componentInstance = componentInstance;
    let renderElement = componentInstance.render();
    componentInstance.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    return newDOM;
}
+export function compareTwoElements(oldElement, newElement) {
+    oldElement = onlyOne(oldElement);
+    newElement = onlyOne(newElement);
+    let currentDOM = oldElement.dom;
+    let currentElement = oldElement;
+    if (newElement == null) {//如果新节点没有了,直接删除拉倒
+        currentDOM.parentNode.removeChild(currentDOM);
+        currentElement = null;
+    } else if (oldElement.type !== newElement.type) {//如果类型不同
+        let newDOM = createDOM(newElement);
+        currentDOM.parentNode.replaceChild(newDOM, currentDOM);
+        currentElement = newElement;
+    } else {
+        updateElement(oldElement, newElement);
+    }
+    return currentElement;
+}

+function updateElement(oldElement, newElement) {
+    let currentDOM = newElement.dom = oldElement.dom;
+    if (oldElement.$$typeof === TEXT && newElement.$$typeof === TEXT) {//如果都是文本类型,则直接改文本
+        if (oldElement.content !== newElement.content) {
+            currentDOM.textContent = newElement.content;
+        }
+    } else if (oldElement.$$typeof === ELEMENT) {
+        updateDOMProps(currentDOM, oldElement.props, newElement.props);
+        oldElement.props = newElement.props;
+    } else if (oldElement.$$typeof === CLASS_COMPONENT) {
+        updateClassComponent(oldElement, newElement);
+        newElement.componentInstance = oldElement.componentInstance;
+    } else if (oldElement.$$typeof === FUNCTION_COMPONENT) {
+        updateFunctionComponent(oldElement, newElement);
+    }
+}
+function updateDOMProps(dom, oldProps, newProps) {
+    return patchProps(dom, oldProps, newProps);
+}

+function updateClassComponent(oldElement, newElement) {
+    let componentInstance = oldElement.componentInstance;
+    let updater = componentInstance.$updater;
+    let nextProps = newElement.props;
+    updater.emitUpdate(nextProps);
+}

+function updateFunctionComponent(oldElement, newElement) {
+    let newRenderElement = newElement.type(newElement.props);
+    var oldRenderElement = oldElement.renderElement;
+    var currentElement = compareTwoElements(oldRenderElement, newRenderElement);
+    newElement.renderElement = currentElement;
+}

export function ReactElement($$typeof, type, key, ref, props) {
    let element = {
        $$typeof,
        type,
        props,
        key,
        ref
    };
    return element;
}

3.4 src\component.js #

src\component.js

import { isFunction } from './utils';
import { compareTwoElements } from './vdom';
+export let updateQueue = {
+    updaters: [],
+    isPending: false,
+    add(updater) {
+        this.updaters.push(updater);
+    },
+    batchUpdate() {
+        if (this.isPending) {
+            return;
+        }
+        this.isPending = true;
+        let { updaters } = this;
+        let updater;
+        while ((updater = updaters.pop())) {
+            updater.updateComponent();
+        }
+        this.isPending = false;
+    },
+};
+class Updater {
+    constructor(instance) {
+        this.instance = instance;
+        this.pendingStates = [];
+        this.nextProps = null;
+    }
+    addState(partialState) {
+        this.pendingStates.push(partialState);
+        this.emitUpdate();
+    }
+    emitUpdate(nextProps) {
+        this.nextProps = nextProps;
+        nextProps || !updateQueue.isPending
+            ? this.updateComponent()
+            : updateQueue.add(this);
+    }
+    updateComponent() {
+        let { instance, pendingStates, nextProps } = this;
+        if (nextProps || pendingStates.length > 0) {
+            shouldUpdate(
+                instance,
+                nextProps,
+                this.getState()
+            );
+        }
+    }

+    getState() {
+        let { instance, pendingStates } = this;
+        let { state } = instance;
+        if (pendingStates.length) {
+            pendingStates.forEach(nextState => {
+                if (isFunction(nextState)) {
+                    nextState = nextState.call(instance, state);
+                }
+                state = { ...state, ...nextState };
+            });
+            pendingStates.length = 0;
+        }
+        return state;
+    }
+}
+function shouldUpdate(component, nextProps, nextState) {
+    component.props = nextProps;
+    component.state = nextState;
+    if (component.shouldComponentUpdate && !component.shouldComponentUpdate(nextProps, nextState)) {
+        return;
+    }
+    component.forceUpdate();
+}

class Component {
    constructor(props) {
        this.props = props;
+        this.$updater = new Updater(this);
+        this.state = {};
+        this.nextProps = null;
    }
+    setState(partialState) {
+        this.$updater.addState(partialState);
+    }
+    forceUpdate() {
+        let { props, state, renderElement: oldRenderElement } = this;
+        if (this.componentWillUpdate) {
+            this.componentWillUpdate(props, state);
+        }
+        let newRenderElement = this.render();
+        let currentElement = compareTwoElements(oldRenderElement, newRenderElement);
+        this.renderElement = currentElement;
+        if (this.componentDidUpdate) {
+            this.componentDidUpdate(props, state);
+        }
+    }
+}
Component.prototype.isReactComponent = {};
export {
    Component
}

3.5 react\event.js #

src\react\event.js

+import { updateQueue } from './component';
export function addEvent(elem, eventType, listener) {
    eventType = eventType.toLowerCase();
    let eventStore = elem.eventStore || (elem.eventStore = {});
    eventStore[eventType] = listener;
    document.addEventListener(eventType.substr(2), dispatchEvent, false)
}
function dispatchEvent(event) {
    let { target, type } = event
    let eventType = 'on' + type;
    let syntheticEvent;
+   updateQueue.isPending = true;
    while (target) {
        let { eventStore } = target
        let listener = eventStore && eventStore[eventType];
        if (listener) {
            if (!syntheticEvent) {
                syntheticEvent = createSyntheticEvent(event);
            }
            syntheticEvent.currentTarget = target;
            listener.call(target, syntheticEvent);
        }
        target = target.parentNode;
    }
+    updateQueue.isPending = false;
+    updateQueue.batchUpdate();
}
function createSyntheticEvent(nativeEvent) {
    let syntheticEvent = {}
    syntheticEvent.nativeEvent = nativeEvent
    for (let key in nativeEvent) {
        if (typeof nativeEvent[key] == 'function') {
            syntheticEvent[key] = nativeEvent[key].bind(nativeEvent)
        } else {
            syntheticEvent[key] = nativeEvent[key]
        }
    }
    return syntheticEvent
}

4. 比较更新子节点 #

4.1 深度优先遍历 #

domdiff2


let tree = {
    value: 'A',
    left: {
        value: 'B',
        left: {
            value: 'D',

        },
        right: {
            value: 'E'
        }
    },
    right: {
        value: 'C',
        left: {
            value: 'F',

        },
        right: {
            value: 'G'
        }
    }
}
let depth = 0;
function visit(tree) {
    depth++;
    if (tree) {
        console.log(depth, tree.value);
        visit(tree.left);
        visit(tree.right);
    }
    depth--;
}
visit(tree);
console.log(depth);

4.2 src\index.js #

src\index.js

import React from './react';
import ReactDOM from './react-dom';

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.number);
        this.setState({ number: this.state.number + 1 });
        console.log(this.state.number);
        setTimeout(() => {
            this.setState({ number: this.state.number + 1 });
            console.log(this.state.number);
        }, 0);
    }
    render() {
        let p = React.createElement('p', { style: { color: 'red' } }, this.state.number);
        let button = React.createElement('button', { onClick: this.handleClick }, '+');
        return React.createElement('div', { id: 'counter' }, p, button);
    }
}
let element = React.createElement(Counter, {});
ReactDOM.render(
    element,
    document.getElementById('root')
);

4.3 react\utils.js #

src\react\utils.js

import { addEvent } from './event';
export function setProps(elem, props) {
    for (let key in props) {
        if (key !== 'children') {
            let value = props[key];
            setProp(elem, key, value);
        }
    }
}
function setProp(elem, key, value) {
    if (/^on/.test(key)) {
        addEvent(elem, key, value);
    } else if (key === 'style') {
        if (value) {
            for (let styleName in value) {
                if (value.hasOwnProperty(styleName)) {
                    elem.style[styleName] = value[styleName];
                }
            }
        }
    } else {
        elem.setAttribute(key, value);
    }
    return elem;
}

export function patchProps(elem, oldProps, newProps) {
    for (let key in oldProps) {
        if (key !== 'children') {
            if (newProps.hasOwnProperty(key)) {
                setProp(elem, key, newProps[key]);
            } else {
                elem.removeAttribute(key);
            }
        }
    }
    for (let key in newProps) {
        if (key !== 'children' && !newProps.hasOwnProperty(key)) {
            setProp(elem, key, newProps[key])
        }
    }
}
export function onlyOne(obj) {
    return Array.isArray(obj) ? obj[0] : obj;
}

export function isFunction(obj) {
    return typeof obj === 'function';
}

4.4 react\vdom.js #

src\react\vdom.js

import { TEXT, ELEMENT, FUNCTION_COMPONENT, CLASS_COMPONENT } from './constants';
+import { setProps, onlyOne, patchProps, deepEqual } from './utils';
export function createDOM(element) {
    element = onlyOne(element);
    let { $$typeof } = element;
    let dom = null;
    if (!$$typeof) {
        dom = document.createTextNode(element);
    } else if ($$typeof === TEXT) {
        dom = document.createTextNode(element.content);
    } else if ($$typeof === ELEMENT) {
        dom = createNativeDOM(element);
    } else if ($$typeof === FUNCTION_COMPONENT) {
        dom = createFunctionComponentDOM(element);
    } else if ($$typeof === CLASS_COMPONENT) {
        dom = createClassComponentDOM(element);
    }
    element.dom = dom;
    return dom;
}

function createNativeDOM(element) {
    let { type, props } = element;
    let dom = document.createElement(type);
    createChildren(element, dom);
    setProps(dom, props);
    return dom;
}

function createChildren(element, parentNode) {
    element.props.children && flatten(element.props.children).forEach((childElement, index) => {
        childElement._mountIndex = index;
        let childDOM = createDOM(childElement);
        childElement.dom = childDOM;
        parentNode.appendChild(childDOM);
    });
}
function createFunctionComponentDOM(element) {
    let { type, props } = element;
    let renderElement = type(props);
    element.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    renderElement.dom = newDOM;
    return newDOM;
}
function createClassComponentDOM(element) {
    let { type, props } = element;
    let componentInstance = new type(props);
    element.componentInstance = componentInstance;
    let renderElement = componentInstance.render();
    componentInstance.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    return newDOM;
}
export function compareTwoElements(oldElement, newElement) {
    oldElement = onlyOne(oldElement);
    newElement = onlyOne(newElement);
    let currentDOM = oldElement.dom;
    let currentElement = oldElement;
    if (newElement == null) {//如果新节点没有了,直接删除拉倒
        currentDOM.parentNode.removeChild(currentDOM);
        currentElement = null;
    } else if (oldElement.type !== newElement.type) {//如果类型不同
        let newDOM = createDOM(newElement);
        currentDOM.parentNode.replaceChild(newDOM, currentDOM);
        currentElement = newElement;
    } else {
        updateElement(oldElement, newElement);
    }
    return currentElement;
}

function updateElement(oldElement, newElement) {
    let currentDOM = newElement.dom = oldElement.dom;
    if (oldElement.$$typeof === TEXT && newElement.$$typeof === TEXT) {//如果都是文本类型,则直接改文本
        if (oldElement.content !== newElement.content) {
            currentDOM.textContent = newElement.content;
        }
    } else if (oldElement.$$typeof === ELEMENT) {
+        updateChildrenElements(currentDOM, oldElement.props.children, newElement.props.children);
        updateDOMProps(currentDOM, oldElement.props, newElement.props);
        oldElement.props = newElement.props;
    } else if (oldElement.$$typeof === CLASS_COMPONENT) {
        updateClassComponent(oldElement, newElement);
        newElement.componentInstance = oldElement.componentInstance;
    } else if (oldElement.$$typeof === FUNCTION_COMPONENT) {
        updateFunctionComponent(oldElement, newElement);
    }
}

+function updateChildrenElements(dom, oldChildrenElements, newChildrenElement) {
+    diff(dom, oldChildrenElements, newChildrenElement);
+}

+function diff(parentNode, oldChildrenElements, newChildrenElements) {
+    let oldChildrenElementMap = getChildrenElementMap(oldChildrenElements);
+    let newChildrenElementMap = getNewChildren(oldChildrenElementMap, newChildrenElements);
+}
+function getChildrenElementMap(childrenElements) {
+    let childrenElementMap = {};
+    for (let i = 0; i < childrenElements.length; i++) {
+        let key = childrenElements[i].key || i.toString();
+        childrenElementMap[key] = childrenElements[i];
+    }
+    return childrenElementMap;
+}

+function getNewChildren(oldChildrenElementMap, newChildrenElements) {
+    let newChildrenElementMap = {};
+    newChildrenElements.forEach((newChildElement, index) => {
+        if (newChildElement) {
+            let newKey = newChildElement.key || index.toString();
+            let oldChildElement = oldChildrenElementMap[newKey];
+            if (canDeepCompare(oldChildElement, newChildElement)) {
+                updateElement(oldChildElement, newChildElement);
+                newChildrenElements[index] = oldChildElement;
+            }
+            newChildrenElementMap[newKey] = newChildrenElements[index];
+        }
+    });
+    return newChildrenElementMap;
+}

+function canDeepCompare(oldChildElement, newChildElement) {
+    if (!!oldChildElement && !!newChildElement) {
+        return oldChildElement.type === newChildElement.type;
+    }
+    return false;
+}

+function updateDOMProps(dom, oldProps, newProps) {
+    patchProps(dom, oldProps, newProps);
+}

function updateClassComponent(oldElement, newElement) {
    let componentInstance = oldElement.componentInstance;
    let updater = componentInstance.$updater;
    let nextProps = newElement.props;
    updater.emitUpdate(nextProps);
}
function updateFunctionComponent(oldElement, newElement) {
    let newRenderElement = newElement.type(newElement.props);
    var oldRenderElement = oldElement.renderElement;
    var currentElement = compareTwoElements(oldRenderElement, newRenderElement);
    newElement.renderElement = currentElement;
}

export function ReactElement($$typeof, type, key, ref, props) {
    let element = {
        $$typeof,
        type,
        props,
        key,
        ref
    };
    return element;
}

5. key的处理 #

5.1 src\index.js #

src\index.js

import React from './react';
import ReactDOM from './react-dom';

+class App extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = { odd: true };
+        setTimeout(() => {
+            this.setState({ odd: !this.state.odd });
+        }, 1000);
+    }
+    handleClick = () => {
+        this.setState({ number: this.state.number + 1 });
+    }
+    render() {
+        if (this.state.odd) {
+            return React.createElement('ul', { key: 'wrapper' },
+                React.createElement('li', { key: 'A' }, 'A'),
+                React.createElement('li', { key: 'B' }, 'B'),
+                React.createElement('li', { key: 'C' }, 'C'),
+                React.createElement('li', { key: 'D' }, 'D'),
+            );
+        } else {
+            return React.createElement('ul', { key: 'wrapper' },
+                React.createElement('li', { key: 'A' }, 'A'),
+                React.createElement('li', { key: 'C' }, 'C1'),
+                React.createElement('li', { key: 'B' }, 'B1'),
+                React.createElement('li', { key: 'E' }, 'E1'),
+                React.createElement('li', { key: 'F' }, 'F1')
+            );
+        }
+    }
+}

+class Todos extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = { list: [], text: '' };
+    }
+    add = () => {
+        if (this.state.text && this.state.text.length > 0) {
+            this.setState({ list: [...this.state.list, this.state.text] });
+        }
+    }
+    onChange = (event) => {
+        this.setState({ text: event.target.value });
+    }
+    onDel = (index) => {
+        this.state.list.splice(index, 1);
+        this.setState({ list: this.state.list });
+    }
+    render() {
+        var createItem = (itemText, index) => {
+            return React.createElement("li", {}, itemText, React.createElement('button',
+                { onClick: () => this.onDel(index) }, 'X'));
+        };
+        var lists = this.state.list.map(createItem);
+        let ul = React.createElement("ul", {}, ...lists);
+        var input = React.createElement("input", { onKeyup: this.onChange, value: this.state.text });
+        var button = React.createElement("button", { onClick: this.add }, 'Add')
+        return React.createElement('div', {}, input, button, ul);
+    }
+}
let element = React.createElement(Todos, {});
ReactDOM.render(
    element,
    document.getElementById('root')
);

5.2 react\constants.js #

src\react\constants.js

export const TEXT = Symbol.for('TEXT');
export const ELEMENT = Symbol.for('ELEMENT');
export const FUNCTION_COMPONENT = Symbol.for('FUNCTION_COMPONENT');
export const CLASS_COMPONENT = Symbol.for('CLASS_COMPONENT');

+export const MOVE = 'MOVE';
+export const INSERT = 'INSERT';
+export const REMOVE = 'REMOVE';

5.3 react\vdom.js #

src\react\vdom.js

+import { TEXT, ELEMENT, FUNCTION_COMPONENT, CLASS_COMPONENT, MOVE, INSERT, REMOVE } from './constants';
+import { setProps, onlyOne,flatten, patchProps} from './utils';
+const diffQueue = [];
+let updateDepth = 0;

export function createDOM(element) {
    element = onlyOne(element);
    let { $$typeof } = element;
    let dom = null;
    if (!$$typeof) {
        dom = document.createTextNode(element);
    } else if ($$typeof === TEXT) {
        dom = document.createTextNode(element.content);
    } else if ($$typeof === ELEMENT) {
        dom = createNativeDOM(element);
    } else if ($$typeof === FUNCTION_COMPONENT) {
        dom = createFunctionComponentDOM(element);
    } else if ($$typeof === CLASS_COMPONENT) {
        dom = createClassComponentDOM(element);
    }
    element.dom = dom;
    return dom;
}

function createNativeDOM(element) {
    let { type, props } = element;
    let dom = document.createElement(type);
    createChildren(element, dom);
    setProps(dom, props);
    return dom;
}

function createChildren(element, parentNode) {
    element.props.children && flatten(element.props.children).forEach((childElement, index) => {
        childElement._mountIndex = index;
        let childDOM = createDOM(childElement);
        parentNode.appendChild(childDOM);
    });
}
function createFunctionComponentDOM(element) {
    let { type, props } = element;
    let renderElement = type(props);
    element.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    renderElement.dom = newDOM;
    return newDOM;
}
function createClassComponentDOM(element) {
    let { type, props } = element;
    let componentInstance = new type(props);
    element.componentInstance = componentInstance;
    let renderElement = componentInstance.render();
    componentInstance.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    return newDOM;
}
export function compareTwoElements(oldElement, newElement) {
    oldElement = onlyOne(oldElement);
    newElement = onlyOne(newElement);
    let currentDOM = oldElement.dom;
    let currentElement = oldElement;
    if (newElement == null) {//如果新节点没有了,直接删除拉倒
        currentDOM.parentNode.removeChild(currentDOM);
        currentElement = null;
    } else if (oldElement.type !== newElement.type) {//如果类型不同
        let newDOM = createDOM(newElement);
        currentDOM.parentNode.replaceChild(newDOM, currentDOM);
        currentElement = newElement;
    } else {
        updateElement(oldElement, newElement);
    }
    return currentElement;
}

function updateElement(oldElement, newElement) {
    let currentDOM = newElement.dom = oldElement.dom;
    if (oldElement.$$typeof === TEXT && newElement.$$typeof === TEXT) {//如果都是文本类型,则直接改文本
        if (oldElement.content !== newElement.content) {
            currentDOM.textContent = newElement.content;
        }
    } else if (oldElement.$$typeof === ELEMENT) {
+        updateChildrenElements(currentDOM, oldElement.props.children, newElement.props.children);
        updateDOMProps(currentDOM, oldElement.props, newElement.props);
        oldElement.props = newElement.props;
    } else if (oldElement.$$typeof === CLASS_COMPONENT) {
        updateClassComponent(oldElement, newElement);
        newElement.componentInstance = oldElement.componentInstance;
    } else if (oldElement.$$typeof === FUNCTION_COMPONENT) {
        updateFunctionComponent(oldElement, newElement);
    }
}
function updateChildrenElements(dom, oldChildrenElements, newChildrenElement) {
+    updateDepth++;
    diff(dom, flatten(oldChildrenElements), (newChildrenElement), diffQueue);
+    updateDepth--;
+    if (updateDepth === 0) {
+        patch(diffQueue);
+        diffQueue.length = 0;
+    }
+}

function diff(parentNode, oldChildrenElements, newChildrenElements, diffQueue) {
    let oldChildrenElementMap = getChildrenElementMap(oldChildrenElements);
    let newChildrenElementMap = getNewChildren(oldChildrenElementMap, newChildrenElements);
+    let lastIndex = 0;
+    for (let i = 0; i < newChildrenElements.length; i++) {
+        let newElement = newChildrenElements[i];//取得新元素
+        if (newElement) {
+            let newKey = (newElement.key) || i.toString();//取得新key
+            let oldElement = oldChildrenElementMap[newKey];
+            if (oldElement === newElement) {
+                if (oldElement._mountIndex < lastIndex) {
+                    diffQueue.push({
+                        parentNode,
+                        type: MOVE,
+                        fromIndex: oldElement._mountIndex,
+                        toIndex: i
+                    });
+                }
+                lastIndex = Math.max(oldElement._mountIndex, lastIndex);
+            } else {
+                diffQueue.push({
+                    parentNode,
+                    type: INSERT,
+                    toIndex: i,
+                    dom: createDOM(newElement)
+                });
+            }
+            newElement._mountIndex = i;
+        }
+
+    }
+    for (let oldKey in oldChildrenElementMap) {
+        if (!newChildrenElementMap.hasOwnProperty(oldKey)) {
+            let oldElement = oldChildrenElementMap[oldKey];
+            diffQueue.push({
+                parentNode,
+                type: REMOVE,
+                fromIndex: oldElement._mountIndex
+            });
+        }
+    }
+}
+function patch(diffQueue) {
+    let deleteChildren = [];
+    let deleteMap = {};
+    for (let i = 0; i < diffQueue.length; i++) {
+        let difference = diffQueue[i];
+        if (difference.type === MOVE || difference.type === REMOVE) {
+            let fromIndex = difference.fromIndex;
+            let oldChild = difference.parentNode.children[fromIndex];
+            deleteMap[fromIndex] = oldChild;
+            deleteChildren.push(oldChild);
+        }
+    }
+    deleteChildren.forEach(child => {
+        child.parentNode.removeChild(child);
+    });
+
+    for (let k = 0; k < diffQueue.length; k++) {
+        let difference = diffQueue[k];
+        switch (difference.type) {
+            case INSERT:
+                insertChildAt(difference.parentNode, difference.dom, difference.toIndex);
+                break;
+            case MOVE:
+                insertChildAt(difference.parentNode, deleteMap[difference.fromIndex], difference.toIndex);
+                break;
+            default:
+                break;
+        }
+    }
+}
+
+function insertChildAt(parentNode, childNode, index) {
+    let oldChild = parentNode.children[index]
+    oldChild ? parentNode.insertBefore(childNode, oldChild) : parentNode.appendChild(childNode);
+}

function getChildrenElementMap(childrenElements) {
    let childrenElementMap = {};
    for (let i = 0; i < childrenElements.length; i++) {
        let key = childrenElements[i].key || i.toString();
        childrenElementMap[key] = childrenElements[i];
    }
    return childrenElementMap;
}

function getNewChildren(oldChildrenElementMap, newChildrenElements) {
    let newChildrenElementMap = {};
    newChildrenElements.forEach((newChildElement, index) => {
        if (newChildElement) {
            let newKey = newChildElement.key || index.toString();
            let oldChildElement = oldChildrenElementMap[newKey];
            if (canDeepCompare(oldChildElement, newChildElement)) {
                updateElement(oldChildElement, newChildElement);
                newChildrenElements[index] = oldChildElement;
            }
            newChildrenElementMap[newKey] = newChildrenElements[index];
        }
    });
    return newChildrenElementMap;
}

function canDeepCompare(oldChildElement, newChildElement) {
    if (!!oldChildElement && !!newChildElement) {
        return oldChildElement.type === newChildElement.type;
    }
    return false;
}

function updateDOMProps(dom, oldProps, newProps) {
    return patchProps(dom, oldProps, newProps);
}

function updateClassComponent(oldElement, newElement) {
    let componentInstance = oldElement.componentInstance;
    let updater = componentInstance.$updater;
    let nextProps = newElement.props;
    updater.emitUpdate(nextProps);
}

function updateFunctionComponent(oldElement, newElement) {
    let newRenderElement = newElement.type(newElement.props);
    var oldRenderElement = oldElement.renderElement;
    var currentElement = compareTwoElements(oldRenderElement, newRenderElement);
    newElement.renderElement = currentElement;
}
export function ReactElement($$typeof, type, key, ref, props) {
    let element = {
        $$typeof,
        type,
        props,
        key,
        ref
    };
    return element;
}

6. 支持旧版生命周期 #

lifecycle

lifecycle

6.1 src\index.js #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom';
+class Counter extends React.Component { // 他会比较两个状态相等就不会刷新视图 PureComponent是浅比较
+    static defaultProps = {
+        name: '前端架构'
+    };
+    constructor(props) {
+        super(props);
+        this.state = { number: 0 }
+        console.log('Counter constructor')
+    }
+    componentWillMount() { // 取本地的数据 同步的方式:采用渲染之前获取数据,只渲染一次
+        console.log('Counter  componentWillMount');
+    }
+    componentDidMount() {
+        console.log('Counter  componentDidMount');
+    }
+    handleClick = () => {
+        this.setState({ number: this.state.number + 1 });
+    };
+    // react可以shouldComponentUpdate方法中优化 PureComponent 可以帮我们做这件事
+    shouldComponentUpdate(nextProps, nextState) { // 代表的是下一次的属性 和 下一次的状态
+        console.log('Counter  shouldComponentUpdate');
+        return nextState.number > 1;
+        // return nextState.number!==this.state.number; //如果此函数种返回了false 就不会调用render方法了
+    } //不要随便用setState 可能会死循环
+    componentWillUpdate() {
+        console.log('Counter  componentWillUpdate');
+    }
+    componentDidUpdate() {
+        console.log('Counter  componentDidUpdate');
+    }
+    render() {
+        console.log('Counter render');
+        return (
+            <div>
+                <p>{this.state.number}</p>
+                {this.state.number > 3 ? null : <ChildCounter n={this.state.number} />}
+                <button onClick={this.handleClick}>+</button>
+            </div>
+        )
+    }
+}
+class ChildCounter extends React.Component {
+    componentWillUnmount() {
+        console.log('ChildCounter  componentWillUnmount')
+    }
+    componentWillMount() {
+        console.log('ChildCounter componentWillMount')
+    }
+    render() {
+        console.log('ChildCounter render')
+        return (<div>
+            {this.props.n}
+        </div>)
+    }
+    componentDidMount() {
+        console.log('ChildCounter componentDidMount')
+    }
+    componentWillReceiveProps(newProps) { // 第一次不会执行,之后属性更新时才会执行
+        console.log('ChildCounter componentWillReceiveProps')
+    }
+    shouldComponentUpdate(nextProps, nextState) {
+        console.log('ChildCounter shouldComponentUpdate')
+        return nextProps.n > 2; //子组件判断接收的属性 是否满足更新条件 为true则更新
+    }
+    componentWillUpdate() {
+        console.log('ChildCounter  componentWillUpdate');
+    }
+    componentDidUpdate() {
+        console.log('ChildCounter  componentDidUpdate');
+    }
+}
let element = React.createElement(Counter, {});
ReactDOM.render(
    element,
    document.getElementById('root')
);

6.2 react\vdom.js #

src\react\vdom.js

import { TEXT, ELEMENT, FUNCTION_COMPONENT, CLASS_COMPONENT, MOVE, INSERT, REMOVE } from './constants';
import { setProps, onlyOne, patchProps, flatten } from './utils';
const diffQueue = [];
let updateDepth = 0;

export function createDOM(element) {
    element = onlyOne(element);
    let { $$typeof } = element;
    let dom = null;
    if (!$$typeof) {
        dom = document.createTextNode(element);
    } else if ($$typeof === TEXT) {
        dom = document.createTextNode(element.content);
    } else if ($$typeof === ELEMENT) {
        dom = createNativeDOM(element);
    } else if ($$typeof === FUNCTION_COMPONENT) {
        dom = createFunctionComponentDOM(element);
    } else if ($$typeof === CLASS_COMPONENT) {
        dom = createClassComponentDOM(element);
    }
    element.dom = dom;
    return dom;
}

function createNativeDOM(element) {
    let { type, props } = element;
    let dom = document.createElement(type);
    createChildren(element, dom);
    setProps(dom, props);
    return dom;
}

function createChildren(element, parentNode) {
    element.props.children && flatten(element.props.children).forEach((childElement, index) => {
        childElement._mountIndex = index;
        let childDOM = createDOM(childElement);
        parentNode.appendChild(childDOM);
    });
}
function createFunctionComponentDOM(element) {
    let { type, props } = element;
    let renderElement = type(props);
    element.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    renderElement.dom = newDOM;
    return newDOM;
}
function createClassComponentDOM(element) {
    let { type, props } = element;
    let componentInstance = new type(props);
+    if (componentInstance.componentWillMount)
+        componentInstance.componentWillMount();
    element.componentInstance = componentInstance;
    let renderElement = componentInstance.render();
    componentInstance.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
+    if (componentInstance.componentDidMount)
+        componentInstance.componentDidMount();
    return newDOM;
}
export function compareTwoElements(oldElement, newElement) {
    oldElement = onlyOne(oldElement);
    newElement = onlyOne(newElement);
    let currentDOM = oldElement.dom;
    let currentElement = oldElement;
    if (newElement == null) {//如果新节点没有了,直接删除拉倒
        currentDOM.parentNode.removeChild(currentDOM);
        currentElement = null;
    } else if (oldElement.type !== newElement.type) {//如果类型不同
        let newDOM = createDOM(newElement);
        currentDOM.parentNode.replaceChild(newDOM, currentDOM);
        currentElement = newElement;
    } else {
        updateElement(oldElement, newElement);
    }
    return currentElement;
}

function updateElement(oldElement, newElement) {
    let currentDOM = newElement.dom = oldElement.dom;
    if (oldElement.$$typeof === TEXT && newElement.$$typeof === TEXT) {//如果都是文本类型,则直接改文本
        if (oldElement.content !== newElement.content) {
            currentDOM.textContent = newElement.content;
        }
    } else if (oldElement.$$typeof === ELEMENT) {
        updateChildrenElements(currentDOM, oldElement.props.children, newElement.props.children);
        updateDOMProps(currentDOM, oldElement.props, newElement.props);
        oldElement.props = newElement.props;
    } else if (oldElement.$$typeof === CLASS_COMPONENT) {
        updateClassComponent(oldElement, newElement);
        newElement.componentInstance = oldElement.componentInstance;
    } else if (oldElement.$$typeof === FUNCTION_COMPONENT) {
        updateFunctionComponent(oldElement, newElement);
    }
}
function updateChildrenElements(dom, oldChildrenElements, newChildrenElement) {
    updateDepth++;
    diff(dom, flatten(oldChildrenElements), flatten(newChildrenElement), diffQueue);
    updateDepth--;
    if (updateDepth === 0) {
        patch(diffQueue);
        diffQueue.length = 0;
    }
}

function diff(parentNode, oldChildrenElements, newChildrenElements, diffQueue) {
    let oldChildrenElementMap = getChildrenElementMap(oldChildrenElements);
    let newChildrenElementMap = getNewChildren(oldChildrenElementMap, newChildrenElements);
    let lastIndex = 0;

    for (let i = 0; i < newChildrenElements.length; i++) {
        let newElement = newChildrenElements[i];//取得新元素
        if (newElement) {
            let newKey = (newElement.key) || i.toString();//取得新key
            let oldElement = oldChildrenElementMap[newKey];
            if (oldElement === newElement) {
                if (oldElement._mountIndex < lastIndex) {
                    diffQueue.push({
                        parentNode,
                        type: MOVE,
                        fromIndex: oldElement._mountIndex,
                        toIndex: i
                    });
                }
                lastIndex = Math.max(oldElement._mountIndex, lastIndex);
            } else {
                diffQueue.push({
                    parentNode,
                    type: INSERT,
                    toIndex: i,
                    dom: createDOM(newElement)
                });
            }
            newElement._mountIndex = i;
+        } else {
+            if (oldChildrenElements[i].componentInstance && oldChildrenElements[i].componentInstance.componentWillUnmount) {
+                oldChildrenElements[i].componentInstance.componentWillUnmount();
+            }
+        }
    }
    for (let oldKey in oldChildrenElementMap) {
        if (!newChildrenElementMap.hasOwnProperty(oldKey)) {
            let oldElement = oldChildrenElementMap[oldKey];
            diffQueue.push({
                parentNode,
                type: REMOVE,
                fromIndex: oldElement._mountIndex
            });
        }
    }
}
function patch(diffQueue) {
    let deleteChildren = [];
    let deleteMap = {};
    for (let i = 0; i < diffQueue.length; i++) {
        let difference = diffQueue[i];
        if (difference.type === MOVE || difference.type === REMOVE) {
            let fromIndex = difference.fromIndex;
            let oldChild = difference.parentNode.children[fromIndex];
            deleteMap[fromIndex] = oldChild;
            deleteChildren.push(oldChild);
        }
    }
    deleteChildren.forEach(child => {
        child.parentNode.removeChild(child);
    });

    for (let k = 0; k < diffQueue.length; k++) {
        let difference = diffQueue[k];
        switch (difference.type) {
            case INSERT:
                insertChildAt(difference.parentNode, difference.dom, difference.toIndex);
                break;
            case MOVE:
                insertChildAt(difference.parentNode, deleteMap[difference.fromIndex], difference.toIndex);
                break;
            default:
                break;
        }
    }
}

function insertChildAt(parentNode, childNode, index) {
    let oldChild = parentNode.children[index]
    oldChild ? parentNode.insertBefore(childNode, oldChild) : parentNode.appendChild(childNode);
}

function getChildrenElementMap(childrenElements) {
    let childrenElementMap = {};
    for (let i = 0; i < childrenElements.length; i++) {
        let key = childrenElements[i].key || i.toString();
        childrenElementMap[key] = childrenElements[i];
    }
    return childrenElementMap;
}

function getNewChildren(oldChildrenElementMap, newChildrenElements) {
    let newChildrenElementMap = {};
    newChildrenElements.forEach((newChildElement, index) => {
        if (newChildElement) {
            let newKey = newChildElement.key || index.toString();
            let oldChildElement = oldChildrenElementMap[newKey];
            if (canDeepCompare(oldChildElement, newChildElement)) {
                updateElement(oldChildElement, newChildElement);
                newChildrenElements[index] = oldChildElement;
            }
            newChildrenElementMap[newKey] = newChildrenElements[index];
        }
    });
    return newChildrenElementMap;
}

function canDeepCompare(oldChildElement, newChildElement) {
    if (!!oldChildElement && !!newChildElement) {
        return oldChildElement.type === newChildElement.type;
    }
    return false;
}

function updateDOMProps(dom, oldProps, newProps) {
    return patchProps(dom, oldProps, newProps);
}

function updateClassComponent(oldElement, newElement) {
    let componentInstance = oldElement.componentInstance;
    let updater = componentInstance.$updater;
    let nextProps = newElement.props;
+    if (componentInstance.componentWillReceiveProps) {
+        componentInstance.componentWillReceiveProps(nextProps);
+    }
    updater.emitUpdate(nextProps);
}

function updateFunctionComponent(oldElement, newElement) {
    let newRenderElement = newElement.type(newElement.props);
    var oldRenderElement = oldElement.renderElement;
    var currentElement = compareTwoElements(oldRenderElement, newRenderElement);
    newElement.renderElement = currentElement;
}
export function ReactElement($$typeof, type, key, ref, props) {
    let element = {
        $$typeof,
        type,
        props,
        key,
        ref
    };
    return element;
}

7. 新版生命周期和ref #

7.1 getDerivedStateFromProps #

7.1.1 index.js #

src/index.js

import React from './react';
import ReactDOM from './react-dom';
class Counter extends React.Component {
    static defaultProps = {
        name: '前端架构'
    };
    constructor(props) {
        super(props);
        this.state = { number: 0 }
    }

    handleClick = () => {
        this.setState({ number: this.state.number + 1 });
    };

    render() {
        return (
            <div>
                <p>{this.state.number}</p>
                <ChildCounter number={this.state.number} />
                <button onClick={this.handleClick}>+</button>
            </div>
        )
    }
}
class ChildCounter extends React.Component {
    constructor(props) {
        super(props);
        this.state = { number: 0 };
    }
    static getDerivedStateFromProps(nextProps, prevState) {
        const { number } = nextProps;
        // 当传入的type发生变化的时候,更新state
        if (number % 2 == 0) {
            return { number: number * 2 };
        } else {
            return { number: number * 3 };
        }
        // 否则,对于state不进行任何操作
        return null;
    }
    render() {
        return (<div>
            {this.state.number}
        </div>)
    }

}

ReactDOM.render(
    <Counter />,
    document.getElementById('root')
);

7.1.2 react\vdom.js #

src\react\vdom.js

import { TEXT, ELEMENT, FUNCTION_COMPONENT, CLASS_COMPONENT, MOVE, INSERT, REMOVE } from './constants';
import { setProps, onlyOne, patchProps, flatten } from './utils';
const diffQueue = [];
let updateDepth = 0;

export function createDOM(element) {
    element = onlyOne(element);
    let { $$typeof } = element;
    let dom = null;
    if (!$$typeof) {
        dom = document.createTextNode(element);
    } else if ($$typeof === TEXT) {
        dom = document.createTextNode(element.content);
    } else if ($$typeof === ELEMENT) {
        dom = createNativeDOM(element);
    } else if ($$typeof === FUNCTION_COMPONENT) {
        dom = createFunctionComponentDOM(element);
    } else if ($$typeof === CLASS_COMPONENT) {
        dom = createClassComponentDOM(element);
    }
    element.dom = dom;
    return dom;
}

function createNativeDOM(element) {
    let { type, props } = element;
    let dom = document.createElement(type);
    createChildren(element, dom);
    setProps(dom, props);
    return dom;
}

function createChildren(element, parentNode) {
    element.props.children && flatten(element.props.children).forEach((childElement, index) => {
        childElement._mountIndex = index;
        let childDOM = createDOM(childElement);
        parentNode.appendChild(childDOM);
    });
}
function createFunctionComponentDOM(element) {
    let { type, props } = element;
    let renderElement = type(props);
    element.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    renderElement.dom = newDOM;
    return newDOM;
}
function createClassComponentDOM(element) {
    let { type, props } = element;
    let componentInstance = new type(props);
    if (componentInstance.componentWillMount)
        componentInstance.componentWillMount();
    element.componentInstance = componentInstance;
    let renderElement = componentInstance.render();
    componentInstance.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    if (componentInstance.componentDidMount)
        componentInstance.componentDidMount();
    return newDOM;
}
export function compareTwoElements(oldElement, newElement) {
    oldElement = onlyOne(oldElement);
    newElement = onlyOne(newElement);
    let currentDOM = oldElement.dom;
    let currentElement = oldElement;
    if (newElement == null) {//如果新节点没有了,直接删除拉倒
        currentDOM.parentNode.removeChild(currentDOM);
        currentElement = null;
    } else if (oldElement.type !== newElement.type) {//如果类型不同
        let newDOM = createDOM(newElement);
        currentDOM.parentNode.replaceChild(newDOM, currentDOM);
        currentElement = newElement;
    } else {
        updateElement(oldElement, newElement);
    }
    return currentElement;
}

function updateElement(oldElement, newElement) {
    let currentDOM = newElement.dom = oldElement.dom;
    if (oldElement.$$typeof === TEXT && newElement.$$typeof === TEXT) {//如果都是文本类型,则直接改文本
        if (oldElement.content !== newElement.content) {
            currentDOM.textContent = newElement.content;
        }
    } else if (oldElement.$$typeof === ELEMENT) {
        updateChildrenElements(currentDOM, oldElement.props.children, newElement.props.children);
        updateDOMProps(currentDOM, oldElement.props, newElement.props);
        oldElement.props = newElement.props;
    } else if (oldElement.$$typeof === CLASS_COMPONENT) {
        updateClassComponent(oldElement, newElement);
        newElement.componentInstance = oldElement.componentInstance;
    } else if (oldElement.$$typeof === FUNCTION_COMPONENT) {
        updateFunctionComponent(oldElement, newElement);
    }
}
function updateChildrenElements(dom, oldChildrenElements, newChildrenElement) {
    updateDepth++;
    diff(dom, flatten(oldChildrenElements), flatten(newChildrenElement), diffQueue);
    updateDepth--;
    if (updateDepth === 0) {
        patch(diffQueue);
        diffQueue.length = 0;
    }
}

function diff(parentNode, oldChildrenElements, newChildrenElements, diffQueue) {
    let oldChildrenElementMap = getChildrenElementMap(oldChildrenElements);
    let newChildrenElementMap = getNewChildren(oldChildrenElementMap, newChildrenElements);
    let lastIndex = 0;

    for (let i = 0; i < newChildrenElements.length; i++) {
        let newElement = newChildrenElements[i];//取得新元素
        if (newElement) {
            let newKey = (newElement.key) || i.toString();//取得新key
            let oldElement = oldChildrenElementMap[newKey];
            if (oldElement === newElement) {
                if (oldElement._mountIndex < lastIndex) {
                    diffQueue.push({
                        parentNode,
                        type: MOVE,
                        fromIndex: oldElement._mountIndex,
                        toIndex: i
                    });
                }
                lastIndex = Math.max(oldElement._mountIndex, lastIndex);
            } else {
                diffQueue.push({
                    parentNode,
                    type: INSERT,
                    toIndex: i,
                    dom: createDOM(newElement)
                });
            }
            newElement._mountIndex = i;
        } else {
            if (oldChildrenElements[i].componentInstance && oldChildrenElements[i].componentInstance.componentWillUnmount) {
                oldChildrenElements[i].componentInstance.componentWillUnmount();
            }
        }

    }
    for (let oldKey in oldChildrenElementMap) {
        if (!newChildrenElementMap.hasOwnProperty(oldKey)) {
            let oldElement = oldChildrenElementMap[oldKey];
            diffQueue.push({
                parentNode,
                type: REMOVE,
                fromIndex: oldElement._mountIndex
            });
        }
    }
}
function patch(diffQueue) {
    let deleteChildren = [];
    let deleteMap = {};
    for (let i = 0; i < diffQueue.length; i++) {
        let difference = diffQueue[i];
        if (difference.type === MOVE || difference.type === REMOVE) {
            let fromIndex = difference.fromIndex;
            let oldChild = difference.parentNode.children[fromIndex];
            deleteMap[fromIndex] = oldChild;
            deleteChildren.push(oldChild);
        }
    }
    deleteChildren.forEach(child => {
        child.parentNode.removeChild(child);
    });

    for (let k = 0; k < diffQueue.length; k++) {
        let difference = diffQueue[k];
        switch (difference.type) {
            case INSERT:
                insertChildAt(difference.parentNode, difference.dom, difference.toIndex);
                break;
            case MOVE:
                insertChildAt(difference.parentNode, deleteMap[difference.fromIndex], difference.toIndex);
                break;
            default:
                break;
        }
    }
}

function insertChildAt(parentNode, childNode, index) {
    let oldChild = parentNode.children[index]
    oldChild ? parentNode.insertBefore(childNode, oldChild) : parentNode.appendChild(childNode);
}

function getChildrenElementMap(childrenElements) {
    let childrenElementMap = {};
    for (let i = 0; i < childrenElements.length; i++) {
        let key = childrenElements[i].key || i.toString();
        childrenElementMap[key] = childrenElements[i];
    }
    return childrenElementMap;
}

function getNewChildren(oldChildrenElementMap, newChildrenElements) {
    let newChildrenElementMap = {};
    newChildrenElements.forEach((newChildElement, index) => {
        if (newChildElement) {
            let newKey = newChildElement.key || index.toString();
            let oldChildElement = oldChildrenElementMap[newKey];
            if (canDeepCompare(oldChildElement, newChildElement)) {
                updateElement(oldChildElement, newChildElement);
                newChildrenElements[index] = oldChildElement;
            }
            newChildrenElementMap[newKey] = newChildrenElements[index];
        }
    });
    return newChildrenElementMap;
}

function canDeepCompare(oldChildElement, newChildElement) {
    if (!!oldChildElement && !!newChildElement) {
        return oldChildElement.type === newChildElement.type;
    }
    return false;
}

function updateDOMProps(dom, oldProps, newProps) {
    return patchProps(dom, oldProps, newProps);
}

function updateClassComponent(oldElement, newElement) {
    let componentInstance = oldElement.componentInstance;
    let updater = componentInstance.$updater;
    let nextProps = newElement.props;
    if (componentInstance.componentWillReceiveProps) {
        componentInstance.componentWillReceiveProps(nextProps);
    }
+   if (newElement.type.getDerivedStateFromProps) {
+       let newState = newElement.type.getDerivedStateFromProps(nextProps, componentInstance.state);
+       if (newState)
+           componentInstance.state = newState;
+   }
    updater.emitUpdate(nextProps);
}

function updateFunctionComponent(oldElement, newElement) {
    let newRenderElement = newElement.type(newElement.props);
    var oldRenderElement = oldElement.renderElement;
    var currentElement = compareTwoElements(oldRenderElement, newRenderElement);
    newElement.renderElement = currentElement;
}
export function ReactElement($$typeof, type, key, ref, props) {
    let element = {
        $$typeof,
        type,
        props,
        key,
        ref
    };
    return element;
}

7.2 ScrollingList #

7.2.1 index.js #

src/index.js

import React from './react';
import ReactDOM from './react-dom';
+class ScrollingList extends React.Component {
+    wrapper
+    timeID
+    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();
+        }, 3000)
+    }
+    componentWillUnmount() {//清除定时器
+        window.clearInterval(this.timeID);
+    }
+    getSnapshotBeforeUpdate = () => {//很关键的,我们获取当前rootNode的scrollHeight,传到componentDidUpdate 的参数perScrollHeight
+        return this.wrapper.current.scrollHeight;
+    }
+    componentDidUpdate(pervProps, pervState, prevScrollHeight) {
+        const curScrollTop = this.wrapper.current.scrollTop;//当前向上卷去的高度
+        //当前向上卷去的高度加上增加的内容高度
+        this.wrapper.current.scrollTop = curScrollTop + (this.wrapper.current.scrollHeight - prevScrollHeight);
+    }
+    render() {
+        let style = {
+            height: '100px',
+            width: '200px',
+            border: '1px solid red',
+            overflow: 'auto'
+        }
+        return (
+            <div style={style} ref={this.wrapper} >
+                {this.state.messages.map((message, index) => (
+                    <div key={index}>{message}</div>
+                ))}
+            </div>
+        );
+    }
+}

ReactDOM.render(
    <ScrollingList />,
    document.getElementById('root')
);

7.2.2 react\component.js #

src\react\component.js

import { isFunction } from './utils';
import { compareTwoElements } from './vdom';
export let updateQueue = {
    updaters: [],
    isPending: false,
    add(updater) {
        this.updaters.push(updater);
    },
    batchUpdate() {
        if (this.isPending) {
            return;
        }
        this.isPending = true;
        let { updaters } = this;
        let updater;
        while ((updater = updaters.pop())) {
            updater.updateComponent();
        }
        this.isPending = false;
    },
};
class Updater {
    constructor(instance) {
        this.instance = instance;
        this.pendingStates = [];
        this.nextProps = null;
    }
    addState(partialState) {
        this.pendingStates.push(partialState);
        this.emitUpdate();
    }
    emitUpdate(nextProps) {
        this.nextProps = nextProps;
        nextProps || !updateQueue.isPending
            ? this.updateComponent()
            : updateQueue.add(this);
    }
    updateComponent() {
        let { instance, pendingStates, nextProps } = this;
        if (nextProps || pendingStates.length > 0) {
            shouldUpdate(
                instance,
                nextProps,
                this.getState()
            );
        }
    }

    getState() {
        let { instance, pendingStates } = this;
        let { state } = instance;
        if (pendingStates.length) {
            pendingStates.forEach(nextState => {
                if (isFunction(nextState)) {
                    nextState = nextState.call(instance, state);
                }
                state = { ...state, ...nextState };
            });
            pendingStates.length = 0;
        }
        return state;
    }
}
function shouldUpdate(component, nextProps, nextState) {
    component.props = nextProps;
    component.state = nextState;
    if (component.shouldComponentUpdate && !component.shouldComponentUpdate(nextProps, nextState)) {
        return;
    }
    component.forceUpdate();
}

class Component {
    constructor(props) {
        this.props = props;
        this.$updater = new Updater(this);
        this.state = {};
        this.nextProps = null;
    }
    setState(partialState) {
        this.$updater.addState(partialState);
    }
    forceUpdate() {
        let { props, state, renderElement: oldRenderElement } = this;
        if (this.componentWillUpdate) {
            this.componentWillUpdate(props, state);
        }
+        let { getSnapshotBeforeUpdate } = this;
+        let extraArgs = getSnapshotBeforeUpdate && getSnapshotBeforeUpdate();
        let newRenderElement = this.render();
        let currentElement = compareTwoElements(oldRenderElement, newRenderElement);
        this.renderElement = currentElement;
        if (this.componentDidUpdate) {
+            this.componentDidUpdate(props, state, extraArgs);
        }
    }
}
Component.prototype.isReactComponent = {};
export {
    Component
}

7.2.3 react\vdom.js #

src\react\vdom.js

import { TEXT, ELEMENT, FUNCTION_COMPONENT, CLASS_COMPONENT, MOVE, INSERT, REMOVE } from './constants';
import { setProps, onlyOne, patchProps, flatten } from './utils';
const diffQueue = [];
let updateDepth = 0;

export function createDOM(element) {
    element = onlyOne(element);
    let { $$typeof } = element;
    let dom = null;
    if (!$$typeof) {
        dom = document.createTextNode(element);
    } else if ($$typeof === TEXT) {
        dom = document.createTextNode(element.content);
    } else if ($$typeof === ELEMENT) {
        dom = createNativeDOM(element);
    } else if ($$typeof === FUNCTION_COMPONENT) {
        dom = createFunctionComponentDOM(element);
    } else if ($$typeof === CLASS_COMPONENT) {
        dom = createClassComponentDOM(element);
    }
    element.dom = dom;
    return dom;
}

function createNativeDOM(element) {
    let { type, props, ref } = element;
    let dom = document.createElement(type);
    createChildren(element, dom);
    setProps(dom, props);
+    if (ref)
+        ref.current = dom;
    return dom;
}

function createChildren(element, parentNode) {
    element.props.children && flatten(element.props.children).forEach((childElement, index) => {
        childElement._mountIndex = index;
        let childDOM = createDOM(childElement);
        parentNode.appendChild(childDOM);
    });
}
function createFunctionComponentDOM(element) {
    let { type, props } = element;
    let renderElement = type(props);
    element.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    renderElement.dom = newDOM;
    return newDOM;
}
function createClassComponentDOM(element) {
    let { type, props, ref } = element;
    let componentInstance = new type(props);
+    if (ref)
+        ref.current = componentInstance;
    if (componentInstance.componentWillMount)
        componentInstance.componentWillMount();
    element.componentInstance = componentInstance;
    let renderElement = componentInstance.render();
    componentInstance.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    if (componentInstance.componentDidMount)
        componentInstance.componentDidMount();
    return newDOM;
}
export function compareTwoElements(oldElement, newElement) {
    oldElement = onlyOne(oldElement);
    newElement = onlyOne(newElement);
    let currentDOM = oldElement.dom;
    let currentElement = oldElement;
    if (newElement == null) {//如果新节点没有了,直接删除拉倒
        currentDOM.parentNode.removeChild(currentDOM);
        currentElement = null;
    } else if (oldElement.type !== newElement.type) {//如果类型不同
        let newDOM = createDOM(newElement);
        currentDOM.parentNode.replaceChild(newDOM, currentDOM);
        currentElement = newElement;
    } else {
        updateElement(oldElement, newElement);
    }
    return currentElement;
}

function updateElement(oldElement, newElement) {
    let currentDOM = newElement.dom = oldElement.dom;
    if (oldElement.$$typeof === TEXT && newElement.$$typeof === TEXT) {//如果都是文本类型,则直接改文本
        if (oldElement.content !== newElement.content) {
            currentDOM.textContent = newElement.content;
        }
    } else if (oldElement.$$typeof === ELEMENT) {
        updateChildrenElements(currentDOM, oldElement.props.children, newElement.props.children);
        updateDOMProps(currentDOM, oldElement.props, newElement.props);
        oldElement.props = newElement.props;
    } else if (oldElement.$$typeof === CLASS_COMPONENT) {
        updateClassComponent(oldElement, newElement);
        newElement.componentInstance = oldElement.componentInstance;
    } else if (oldElement.$$typeof === FUNCTION_COMPONENT) {
        updateFunctionComponent(oldElement, newElement);
    }
}
function updateChildrenElements(dom, oldChildrenElements, newChildrenElement) {
    updateDepth++;
    diff(dom, flatten(oldChildrenElements), flatten(newChildrenElement), diffQueue);
    updateDepth--;
    if (updateDepth === 0) {
        patch(diffQueue);
        diffQueue.length = 0;
    }
}

function diff(parentNode, oldChildrenElements, newChildrenElements, diffQueue) {
    let oldChildrenElementMap = getChildrenElementMap(oldChildrenElements);
    let newChildrenElementMap = getNewChildren(oldChildrenElementMap, newChildrenElements);
    let lastIndex = 0;

    for (let i = 0; i < newChildrenElements.length; i++) {
        let newElement = newChildrenElements[i];//取得新元素
        if (newElement) {
            let newKey = (newElement.key) || i.toString();//取得新key
            let oldElement = oldChildrenElementMap[newKey];
            if (oldElement === newElement) {
                if (oldElement._mountIndex < lastIndex) {
                    diffQueue.push({
                        parentNode,
                        type: MOVE,
                        fromIndex: oldElement._mountIndex,
                        toIndex: i
                    });
                }
                lastIndex = Math.max(oldElement._mountIndex, lastIndex);
            } else {
                diffQueue.push({
                    parentNode,
                    type: INSERT,
                    toIndex: i,
                    dom: createDOM(newElement)
                });
            }
            newElement._mountIndex = i;
        } else {
            if (oldChildrenElements[i].componentInstance && oldChildrenElements[i].componentInstance.componentWillUnmount) {
                oldChildrenElements[i].componentInstance.componentWillUnmount();
            }
        }

    }
    for (let oldKey in oldChildrenElementMap) {
        if (!newChildrenElementMap.hasOwnProperty(oldKey)) {
            let oldElement = oldChildrenElementMap[oldKey];
            diffQueue.push({
                parentNode,
                type: REMOVE,
                fromIndex: oldElement._mountIndex
            });
        }
    }
}
function patch(diffQueue) {
    let deleteChildren = [];
    let deleteMap = {};
    for (let i = 0; i < diffQueue.length; i++) {
        let difference = diffQueue[i];
        if (difference.type === MOVE || difference.type === REMOVE) {
            let fromIndex = difference.fromIndex;
            let oldChild = difference.parentNode.children[fromIndex];
            deleteMap[fromIndex] = oldChild;
            deleteChildren.push(oldChild);
        }
    }
    deleteChildren.forEach(child => {
        child.parentNode.removeChild(child);
    });

    for (let k = 0; k < diffQueue.length; k++) {
        let difference = diffQueue[k];
        switch (difference.type) {
            case INSERT:
                insertChildAt(difference.parentNode, difference.dom, difference.toIndex);
                break;
            case MOVE:
                insertChildAt(difference.parentNode, deleteMap[difference.fromIndex], difference.toIndex);
                break;
            default:
                break;
        }
    }
}

function insertChildAt(parentNode, childNode, index) {
    let oldChild = parentNode.children[index]
    oldChild ? parentNode.insertBefore(childNode, oldChild) : parentNode.appendChild(childNode);
}

function getChildrenElementMap(childrenElements) {
    let childrenElementMap = {};
    for (let i = 0; i < childrenElements.length; i++) {
        let key = childrenElements[i].key || i.toString();
        childrenElementMap[key] = childrenElements[i];
    }
    return childrenElementMap;
}

function getNewChildren(oldChildrenElementMap, newChildrenElements) {
    let newChildrenElementMap = {};
    newChildrenElements.forEach((newChildElement, index) => {
        if (newChildElement) {
            let newKey = newChildElement.key || index.toString();
            let oldChildElement = oldChildrenElementMap[newKey];
            if (canDeepCompare(oldChildElement, newChildElement)) {
                updateElement(oldChildElement, newChildElement);
                newChildrenElements[index] = oldChildElement;
            }
            newChildrenElementMap[newKey] = newChildrenElements[index];
        }
    });
    return newChildrenElementMap;
}

function canDeepCompare(oldChildElement, newChildElement) {
    if (!!oldChildElement && !!newChildElement) {
        return oldChildElement.type === newChildElement.type;
    }
    return false;
}

function updateDOMProps(dom, oldProps, newProps) {
    return patchProps(dom, oldProps, newProps);
}

function updateClassComponent(oldElement, newElement) {
    let componentInstance = oldElement.componentInstance;
    let updater = componentInstance.$updater;
    let nextProps = newElement.props;
    if (componentInstance.componentWillReceiveProps) {
        componentInstance.componentWillReceiveProps(nextProps);
    }
    if (newElement.type.getDerivedStateFromProps) {
        let newState = newElement.type.getDerivedStateFromProps(nextProps, componentInstance.state);
        if (newState)
            componentInstance.state = newState;
    }
    updater.emitUpdate(nextProps);
}

function updateFunctionComponent(oldElement, newElement) {
    let newRenderElement = newElement.type(newElement.props);
    var oldRenderElement = oldElement.renderElement;
    var currentElement = compareTwoElements(oldRenderElement, newRenderElement);
    newElement.renderElement = currentElement;
}
export function ReactElement($$typeof, type, key, ref, props) {
    let element = {
        $$typeof,
        type,
        props,
        key,
        ref
    };
    return element;
}

8. context #

8.1 src\index.js #

src\index.js

import React, { Component } from './react';
import ReactDOM from './react-dom';
let ThemeContext = React.createContext(null);
let root = document.querySelector('#root');
class Header extends Component {
    static contextType = ThemeContext;
    render() {
        return (
            <div style={{ border: `5px solid ${this.context.color}`, padding: '5px' }}>
                header
                <Title />
            </div>
        )
    }
}
class Title extends Component {
    static contextType = ThemeContext;
    render() {
        return (
            <div style={{ border: `5px solid ${this.context.color}` }}>
                title
            </div>
        )
    }
}
class Main extends Component {
    static contextType = ThemeContext;
    render() {
        return (
            <div style={{ border: `5px solid ${this.context.color}`, margin: '5px', padding: '5px' }}>
                main
                <Content />
            </div>
        )
    }
}
class Content extends Component {
    static contextType = ThemeContext;
    render() {
        return (
            <div style={{ border: `5px solid ${this.context.color}`, padding: '5px' }}>
                Content
                <button onClick={() => this.context.changeColor('red')} style={{ color: 'red' }}>红色</button>
                <button onClick={() => this.context.changeColor('green')} style={{ color: 'green' }}>绿色</button>
            </div>
        )
    }
}

class ClassPage extends Component {
    constructor(props) {
        super(props);
        this.state = { color: 'red' };
    }
    changeColor = (color) => {
        this.setState({ color });
    }
    render() {
        let contextVal = { changeColor: this.changeColor, color: this.state.color };
        return (
            <ThemeContext.Provider value={contextVal}>
                <div style={{ margin: '10px', border: `5px solid ${this.state.color}`, padding: '5px', width: '200px' }}>
                    page
                    <Header />
                    <Main />
                </div>
            </ThemeContext.Provider>

        )
    }
}
class FunctionHeader extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value) => (
                        <div style={{ border: `5px solid ${value.color}`, padding: '5px' }}>
                            header
                            <FunctionTitle />
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class FunctionTitle extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value) => (
                        <div style={{ border: `5px solid ${value.color}` }}>
                            title
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class FunctionMain extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value) => (
                        <div style={{ border: `5px solid ${value.color}`, margin: '5px', padding: '5px' }}>
                            main
                            <FunctionContent />
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class FunctionContent extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value) => (
                        <div style={{ border: `5px solid ${value.color}`, padding: '5px' }}>
                            Content
                            <button onClick={() => value.changeColor('red')} style={{ color: 'red' }}>红色</button>
                            <button onClick={() => value.changeColor('green')} style={{ color: 'green' }}>绿色</button>
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class FunctionPage extends Component {
    constructor(props) {
        super(props);
        this.state = { color: 'red' };
    }
    changeColor = (color) => {
        this.setState({ color });
    }
    render() {
        let contextVal = { changeColor: this.changeColor, color: this.state.color };
        return (
            <ThemeContext.Provider value={contextVal}>
                <div style={{ margin: '10px', border: `5px solid ${this.state.color}`, padding: '5px', width: '200px' }}>
                    page
                    <FunctionHeader />
                    <FunctionMain />
                </div>
            </ThemeContext.Provider>

        )
    }
}
ReactDOM.render(<FunctionPage />, root);

8.2 react\index.js #

src\react\index.js

import { TEXT, ELEMENT, FUNCTION_COMPONENT, CLASS_COMPONENT } from './constants';
import { ReactElement } from './vdom';
import { Component } from './component';
import { onlyOne } from './utils';
function createElement(type, config = {}, ...children) {
    delete config.__source;
    delete config.__self;
    let { key, ref, ...props } = config;
    let $$typeof = null;
    if (typeof type === 'string') {
        $$typeof = ELEMENT;
    } else if (typeof type === 'function' && type.prototype.isReactComponent) {
        $$typeof = CLASS_COMPONENT;
    } else if (typeof type === 'function') {
        $$typeof = FUNCTION_COMPONENT;
    }
    props.children = children.map(item => typeof item === 'object' || typeof item === 'function' ? item
        : { $$typeof: TEXT, type: TEXT, content: item });
    return ReactElement($$typeof, type, key, ref, props);
}
+function createContext(defaultValue) {
+    Provider.value = defaultValue;
+    function Provider(props) {
+        Provider.value = props.value;
+        return props.children;
+    }
+    function Consumer(props) {
+        return onlyOne(props.children)(Provider.value);
+    }
+    return {
+        Provider,
+        Consumer
+    }
+}
export {
    Component
}
const React = {
    createElement,
    Component,
+    createContext
}
export default React;

8.3 react\vdom.js #

src\react\vdom.js

import { TEXT, ELEMENT, FUNCTION_COMPONENT, CLASS_COMPONENT, MOVE, INSERT, REMOVE } from './constants';
import { setProps, onlyOne, patchProps, flatten } from './utils';
const diffQueue = [];
let updateDepth = 0;

export function createDOM(element) {
    element = onlyOne(element);
    let { $$typeof } = element;
    let dom = null;
    if (!$$typeof) {
        dom = document.createTextNode(element);
    } else if ($$typeof === TEXT) {
        dom = document.createTextNode(element.content);
    } else if ($$typeof === ELEMENT) {
        dom = createNativeDOM(element);
    } else if ($$typeof === FUNCTION_COMPONENT) {
        dom = createFunctionComponentDOM(element);
    } else if ($$typeof === CLASS_COMPONENT) {
        dom = createClassComponentDOM(element);
    }
    element.dom = dom;
    return dom;
}

function createNativeDOM(element) {
    let { type, props, ref } = element;
    let dom = document.createElement(type);
    createChildren(element, dom);
    setProps(dom, props);
    if (ref)
        ref.current = dom;
    return dom;
}

function createChildren(element, parentNode) {
    element.props.children && flatten(element.props.children).forEach((childElement, index) => {
        childElement._mountIndex = index;
        let childDOM = createDOM(childElement);
        parentNode.appendChild(childDOM);
    });
}
function createFunctionComponentDOM(element) {
    let { type, props } = element;
    let renderElement = type(props);
    element.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    renderElement.dom = newDOM;
    return newDOM;
}
function createClassComponentDOM(element) {
    let { type, props, ref } = element;
    let componentInstance = new type(props);
+    if (element.type.contextType) {
+        componentInstance.context = element.type.contextType.Provider.value;
+    }
    if (ref)
        ref.current = componentInstance;
    if (componentInstance.componentWillMount)
        componentInstance.componentWillMount();
    element.componentInstance = componentInstance;
    let renderElement = componentInstance.render();
    componentInstance.renderElement = renderElement;
    let newDOM = createDOM(renderElement);
    if (componentInstance.componentDidMount)
        componentInstance.componentDidMount();
    return newDOM;
}
export function compareTwoElements(oldElement, newElement) {
    oldElement = onlyOne(oldElement);
    newElement = onlyOne(newElement);
    let currentDOM = oldElement.dom;
    let currentElement = oldElement;
    if (newElement == null) {//如果新节点没有了,直接删除拉倒
        currentDOM.parentNode.removeChild(currentDOM);
        currentElement = null;
    } else if (oldElement.type !== newElement.type) {//如果类型不同
        let newDOM = createDOM(newElement);
        currentDOM.parentNode.replaceChild(newDOM, currentDOM);
        currentElement = newElement;
    } else {
        updateElement(oldElement, newElement);
    }
    return currentElement;
}

function updateElement(oldElement, newElement) {
    let currentDOM = newElement.dom = oldElement.dom;
    if (oldElement.$$typeof === TEXT && newElement.$$typeof === TEXT) {//如果都是文本类型,则直接改文本
        if (oldElement.content !== newElement.content) {
            currentDOM.textContent = newElement.content;
        }
    } else if (oldElement.$$typeof === ELEMENT) {
        updateChildrenElements(currentDOM, oldElement.props.children, newElement.props.children);
        updateDOMProps(currentDOM, oldElement.props, newElement.props);
        oldElement.props = newElement.props;
    } else if (oldElement.$$typeof === CLASS_COMPONENT) {
        updateClassComponent(oldElement, newElement);
        newElement.componentInstance = oldElement.componentInstance;
    } else if (oldElement.$$typeof === FUNCTION_COMPONENT) {
        updateFunctionComponent(oldElement, newElement);
    }
}
function updateChildrenElements(dom, oldChildrenElements, newChildrenElement) {
    updateDepth++;
    diff(dom, flatten(oldChildrenElements), flatten(newChildrenElement), diffQueue);
    updateDepth--;
    if (updateDepth === 0) {
        patch(diffQueue);
        diffQueue.length = 0;
    }
}

function diff(parentNode, oldChildrenElements, newChildrenElements, diffQueue) {
    let oldChildrenElementMap = getChildrenElementMap(oldChildrenElements);
    let newChildrenElementMap = getNewChildren(oldChildrenElementMap, newChildrenElements);
    let lastIndex = 0;

    for (let i = 0; i < newChildrenElements.length; i++) {
        let newElement = newChildrenElements[i];//取得新元素
        if (newElement) {
            let newKey = (newElement.key) || i.toString();//取得新key
            let oldElement = oldChildrenElementMap[newKey];
            if (oldElement === newElement) {
                if (oldElement._mountIndex < lastIndex) {
                    diffQueue.push({
                        parentNode,
                        type: MOVE,
                        fromIndex: oldElement._mountIndex,
                        toIndex: i
                    });
                }
                lastIndex = Math.max(oldElement._mountIndex, lastIndex);
            } else {
                diffQueue.push({
                    parentNode,
                    type: INSERT,
                    toIndex: i,
                    dom: createDOM(newElement)
                });
            }
            newElement._mountIndex = i;
        } else {
            if (oldChildrenElements[i].componentInstance && oldChildrenElements[i].componentInstance.componentWillUnmount) {
                oldChildrenElements[i].componentInstance.componentWillUnmount();
            }
        }

    }
    for (let oldKey in oldChildrenElementMap) {
        if (!newChildrenElementMap.hasOwnProperty(oldKey)) {
            let oldElement = oldChildrenElementMap[oldKey];
            diffQueue.push({
                parentNode,
                type: REMOVE,
                fromIndex: oldElement._mountIndex
            });
        }
    }
}
function patch(diffQueue) {
    let deleteChildren = [];
    let deleteMap = {};
    for (let i = 0; i < diffQueue.length; i++) {
        let difference = diffQueue[i];
        if (difference.type === MOVE || difference.type === REMOVE) {
            let fromIndex = difference.fromIndex;
            let oldChild = difference.parentNode.children[fromIndex];
            deleteMap[fromIndex] = oldChild;
            deleteChildren.push(oldChild);
        }
    }
    deleteChildren.forEach(child => {
        child.parentNode.removeChild(child);
    });

    for (let k = 0; k < diffQueue.length; k++) {
        let difference = diffQueue[k];
        switch (difference.type) {
            case INSERT:
                insertChildAt(difference.parentNode, difference.dom, difference.toIndex);
                break;
            case MOVE:
                insertChildAt(difference.parentNode, deleteMap[difference.fromIndex], difference.toIndex);
                break;
            default:
                break;
        }
    }
}

function insertChildAt(parentNode, childNode, index) {
    let oldChild = parentNode.children[index]
    oldChild ? parentNode.insertBefore(childNode, oldChild) : parentNode.appendChild(childNode);
}

function getChildrenElementMap(childrenElements) {
    let childrenElementMap = {};
    for (let i = 0; i < childrenElements.length; i++) {
        let key = childrenElements[i].key || i.toString();
        childrenElementMap[key] = childrenElements[i];
    }
    return childrenElementMap;
}

function getNewChildren(oldChildrenElementMap, newChildrenElements) {
    let newChildrenElementMap = {};
    newChildrenElements.forEach((newChildElement, index) => {
        if (newChildElement) {
            let newKey = newChildElement.key || index.toString();
            let oldChildElement = oldChildrenElementMap[newKey];
            if (canDeepCompare(oldChildElement, newChildElement)) {
                updateElement(oldChildElement, newChildElement);
                newChildrenElements[index] = oldChildElement;
            }
            newChildrenElementMap[newKey] = newChildrenElements[index];
        }
    });
    return newChildrenElementMap;
}

function canDeepCompare(oldChildElement, newChildElement) {
    if (!!oldChildElement && !!newChildElement) {
        return oldChildElement.type === newChildElement.type;
    }
    return false;
}

function updateDOMProps(dom, oldProps, newProps) {
    return patchProps(dom, oldProps, newProps);
}

function updateClassComponent(oldElement, newElement) {
    let componentInstance = oldElement.componentInstance;
    let updater = componentInstance.$updater;
    let nextProps = newElement.props;
+    if (oldElement.type.contextType) {
+        componentInstance.context = oldElement.type.contextType.Provider.value;
+    }
    if (componentInstance.componentWillReceiveProps) {
        componentInstance.componentWillReceiveProps(nextProps);
    }
    if (newElement.type.getDerivedStateFromProps) {
        let newState = newElement.type.getDerivedStateFromProps(nextProps, componentInstance.state);
        if (newState)
            componentInstance.state = newState;
    }
    updater.emitUpdate(nextProps);
}

function updateFunctionComponent(oldElement, newElement) {
    let newRenderElement = newElement.type(newElement.props);
    var oldRenderElement = oldElement.renderElement;
    var currentElement = compareTwoElements(oldRenderElement, newRenderElement);
    newElement.renderElement = currentElement;
}
export function ReactElement($$typeof, type, key, ref, props) {
    let element = {
        $$typeof,
        type,
        props,
        key,
        ref
    };
    return element;
}

9. DOM-DIFF #

  • DOM 节点跨层级的移动操作特别少,可以忽略不计
  • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
  • 对于同一层级的一组子节点,它们可以通过唯一key进行区分
  • DIFF算法在执行时有三个维度,分别是Tree DIFF、Component DIFF和Element DIFF,执行时按顺序依次执行,它们的差异仅仅因为DIFF粒度不同、执行先后顺序不同

9.1 Tree DIFF #

  • Tree DIFF是对树的每一层进行遍历,如果某组件不存在了,则会直接销毁

sametree

  • 当出现节点跨层级移动时,并不会出现想象中的移动操作,而是以 A 为根节点的树被整个重新创建

movemytree

9.2 Component DIFF #

  • 如果是同一类型的组件,按照原策略继续比较
  • 类型不同则直接替换

deleteall

9.3 Element DIFF #

  • 当节点处于同一层级时,React diff 提供了三种节点操作,分别为:INSERT(插入)、MOVE(移动)和 REMOVE(删除)
    • INSERT: 新的 component 类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作
    • MOVE: 在老集合有新 component 类型,就需要做更新和移动操作,可以复用以前的 DOM 节点
    • REMOVE: 老 component 不在新集合里的,也需要执行删除操作

oldnewmove

oldnewmove2

oldnewmove3

访问验证

请输入访问令牌

Token不正确,请重新输入