导航菜单

  • 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.初始化项目
  • 2. 渲染文本
    • 2.1 渲染效果
    • 2.2 实现
      • 2.2.1 index.js
      • 2.2.2 react.js
  • 3. 重构
    • 3.2 类图
    • 3.3 实现
      • 3.3.1 index.js
      • 3.3.2 react\index.js
      • 3.3.3 react\unit.js
  • 4. 渲染原生DOM组件
    • 4.1 渲染效果
    • 4.2 类图
    • 4.3 JSX语法
      • 4.3.1 JSX
      • 4.3.2 JavaScript
    • 4.4 实现
      • 4.4.1 index.js
      • 4.4.2 react/index.js
      • 4.4.3 react/element.js
      • 4.4.4 react/unit.js
  • 5. 渲染自定义组件
    • 5.1 渲染效果
    • 5.2 类图
    • 5.3 实现
      • 5.3.1 src/index.js
      • 5.3.2 react/index.js
      • 5.3.3 react/component.js
      • 5.3.4 react/unit.js
  • 6. 实现setState
    • 6.1 src/index.js
    • 6.2 react/component.js
    • 6.3 react/unit.js
    • 6.4 react/element.js
  • 7. 对比属性
    • 7.1 src/index.js
    • 7.2 react/unit.js
  • 8. 对比子元素
    • 8.1 src/unit.js
  • 9. 获得补丁数组
    • 9.1 src/index.js
    • 9.2 src/react/unit.js
    • 9.3 types.js
  • 10. 打补丁
    • 10.1 src/index.js
    • 10.2 react/unit.js
  • 11. todos
    • 11.1 src/index.js
    • 11.2 react/unit.js
  • 10. diff 策略
    • 10.1 tree diff
    • 10.2 component diff
    • 10.3 element diff
    • 10.4 key
  • 11.delegate

1.初始化项目 #

create-react-app zhufeng_react5
cd zhufeng_react5
cnpm i jquery -S
npm start

2. 渲染文本 #

2.1 渲染效果 #

initrenderhtml

2.2 实现 #

2.2.1 index.js #

src\index.js

import React from './react';
React.render('hello',document.getElementById('root'));

2.2.2 react.js #

src\react.js

import $ from 'jquery';
let React = {
    rootIndex:0,
    render
}
function render(element,container){
   container.innerHTML = `<span data-reactid="${React.rootIndex}">${element}</span>`;
}

export default React;

3. 重构 #

3.2 类图 #

2.rendertext

3.3 实现 #

3.3.1 index.js #

src\index.js

import React from './react';
React.render('hello',document.getElementById('root'));

3.3.2 react\index.js #

src\react\index.js

import $ from 'jquery';
import {createUnit} from './unit';
let React = {
    rootIndex:0,
    render
}
function render(element,container){
   let unit = createUnit(element);
   let markup = unit.getMarkUp(React.rootIndex);
   $(container).html(markup);
   $(document).trigger('mounted');//componentDidMount
}

export default React;

3.3.3 react\unit.js #

src\react\unit.js

class Unit {
    constructor(element){
        this._currentElement = element;
    }
    getMarkUp(){
        throw new Error('不能调用此方法');
    }
}
class TextUnit extends Unit{
    getMarkUp(reactid){
         this._reactid = reactid;//保存记录rootId
        //返回文本节点对应的HTML字符串
        return `<span data-reactid="${reactid}">${this._currentElement}</span>`;
    }
}

function createUnit(element){
  if(typeof element =='string' || typeof element =='number'){
      return new TextUnit(element);
  }
}

export {
    createUnit
}

4. 渲染原生DOM组件 #

  • babeljs

4.1 渲染效果 #

renderdom

4.2 类图 #

20rendernativeunit

4.3 JSX语法 #

compile

4.3.1 JSX #

<button id="sayHello" onClick={sayHello}>say<b style={{color:'red'}}>Hello</b></button>

4.3.2 JavaScript #

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

4.4 实现 #

4.4.1 index.js #

src/index.js

import React from './react';
function sayHello(){
  alert('hello');
}
let element = React.createElement(
    'button',{id:'sayHello',onClick:sayHello},
    'say',
    React.createElement('b',{style:{color:'green'}},'hello'),
);
React.render(element,document.getElementById('root'));

4.4.2 react/index.js #

src/react/index.js

import $ from 'jquery';
import {createUnit} from './unit';
+import {createElement} from './element';
let React = {
    rootIndex:0,
    render,
+    createElement
}
function render(element,container){
   let unit = createUnit(element);
   let markup = unit.getMarkUp(React.rootIndex);
   $(container).html(markup);
   $(document).trigger('mounted');//componentDidMount
}

export default React;

4.4.3 react/element.js #

src/react/element.js

class Element{
    constructor(type,props){
        this.type = type;
        this.props = props;
    }
}
function createElement(type,props,...children){
    props=props||{};
    props.children  = children;
    return new Element(type,props);
}

export {
    createElement
}

4.4.4 react/unit.js #

src/react/unit.js

+import {Element} from './element';
+import $ from 'jquery';
class Unit {
    constructor(element){
        this._currentElement = element;
    }
    getMarkUp(){
        throw new Error('不能调用此方法');
    }
}
class TextUnit extends Unit{
    getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        return `<span data-reactid="${reactid}">${this._currentElement}</span>`;
    }
}
+class NativeUnit extends Unit {
+     getMarkUp(reactid){
+         this._reactid = reactid;//保存记录reactid
+        //返回文本节点对应的HTML字符串
+        let {type,props} = this._currentElement;
+        let tagOpen = `<${type} data-reactid="${reactid}" `;
+        let tagClose = `</$type>`;
+        let content = '';
+        for(let propName in props){
+            if(/^on[A-Z]/.test(propName)){
+                let eventName = propName.slice(2).toLowerCase();
+                $(document).delegate(`[data-reactid="${reactid}"]`,`${eventName}.${reactid}`,props[propName]);
+            }else if(propName === 'style'){
+                let styleObj = props[propName];
+                let styles = Object.keys(styleObj).map(attr=>`${attr}:${styleObj[attr]}`).join(';');
+                tagOpen += (` style="${styles}" `);
+            }else if (propName === 'children'){
+                let children = props.children||[];
+                children.map((child,index)=>{
+                    let childUnit = createUnit(child);
+                    let childMarkUp = childUnit.getMarkUp(`${reactid}.${index}`);
+                    content += childMarkUp;
+                });
+            }else{
+                tagOpen += ` ${propName}=${props[propName]} `;
+            }
+        }
+        return tagOpen + '>' + content + tagClose;
+    }
+}
function createUnit(element){
  if(typeof element =='string' || typeof element =='number'){
      return new TextUnit(element);
  }
+  if(element instanceof Element && typeof element.type === 'string'){
+      return new NativeUnit(element);
+  }
}

export {
    createUnit
}

5. 渲染自定义组件 #

5.1 渲染效果 #

customercomponent

5.2 类图 #

20rendercustomerunit

5.3 实现 #

5.3.1 src/index.js #

import React from './react';
class Counter extends React.Component{
  constructor(props){
    super(props);
    this.state = {number:0};
  }
  componentWillMount(){
    console.log('Counter componentWillMount')
  }
  componentDidMount() {
     console.log('Counter componentDidMount')
  }
  handleClick = ()=>{
    this.setState({number:this.state.number+1});
  }
  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);
React.render(element,document.getElementById('root'));

5.3.2 react/index.js #

src/react/index.js

import $ from 'jquery';
import {createUnit} from './unit';
import {createElement} from './element';
+import {Component} from './component';
let React = {
    rootIndex:0,
    render,
    createElement,
+    Component
}
function render(element,container){
   let unit = createUnit(element);
   let markup = unit.getMarkUp(React.rootIndex);
   $(container).html(markup);
   $(document).trigger('mounted');//componentDidMount
}

export default React;

5.3.3 react/component.js #

src/react/component.js

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

5.3.4 react/unit.js #

src/react/unit.js

import {Element} from './element';
import $ from 'jquery';
class Unit {
    constructor(element){
        this._currentElement = element;
    }
    getMarkUp(){
        throw new Error('不能调用此方法');
    }
}
class TextUnit extends Unit{
    getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        return `<span data-reactid="${reactid}">${this._currentElement}</span>`;
    }
}
class NativeUnit extends Unit {
     getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        let {type,props} = this._currentElement;
        let tagOpen = `<${type} data-reactid="${reactid}" `;
        let tagClose = `</$type>`;
        let content = '';
        for(let propName in props){
            if(/^on[A-Z]/.test(propName)){
                let eventName = propName.slice(2).toLowerCase();
                $(document).delegate(`[data-reactid="${reactid}"]`,`${eventName}.${reactid}`,props[propName]);
            }else if(propName === 'style'){
                let styleObj = props[propName];
                let styles = Object.keys(styleObj).map(attr=>`${attr}:${styleObj[attr]}`).join(';');
                tagOpen += (` style="${styles}" `);
            }else if (propName === 'children'){
                let children = props.children||[];
                children.map((child,index)=>{
                    let childUnit = createUnit(child);
                    let childMarkUp = childUnit.getMarkUp(`${reactid}.${index}`);
                    content += childMarkUp;
                });
            }else{
                tagOpen += ` ${propName}=${props[propName]} `;
            }
        }
        return tagOpen + '>' + content + tagClose;
    }
}

+class CompositeUnit extends Unit{
+    getMarkUp(reactid){
+        this._reactid = reactid;
+        //type是一个自定义组件的类的定义
+        let {type:Component,props} = this._currentElement;
+        //创建Component类的实例
+        let componentInstance = new Component(props);
+        //组件将要渲染
+        componentInstance.componentWillMount&&componentInstance.componentWillMount();
+        //执行render方法获得虚拟DOM元素实例
+        let renderedElement = componentInstance.render();
+        //根据虚拟DOM元素得到unit,可能是TextUnit NativeUnit CompositeUnit
+        let renderedUnitInstance = this._renderedUnitInstance= createUnit(renderedElement);
+        //获得此unit的HTML标记字符串
+        let renderedMarkUp = renderedUnitInstance.getMarkUp(reactid);
+        //注册挂载完成的监听,越底层的组件越先监听,越先执行
+        $(document).on('mounted',()=>componentInstance.componentDidMount&&componentInstance.componentDidMount());
+        return renderedMarkUp;
+    }
+}

function createUnit(element){
  if(typeof element =='string' || typeof element =='number'){
      return new TextUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'string'){
      return new NativeUnit(element);
  }
+  if(element instanceof Element && typeof element.type === 'function'){
+      return new CompositeUnit(element);
+  }
}

export {
    createUnit
}

6. 实现setState #

6.1 src/index.js #

import React from './react';
class Counter extends React.Component{
  constructor(props){
    super(props);
    this.state = {number:0};
  }
  componentWillMount(){
    console.log('Counter componentWillMount')
  }
  componentDidMount() {
     setInterval(() => {
       this.setState({number:this.state.number+1});
     }, 1000);
  }
  render(){
   return this.state.number;
  }
}
let element = React.createElement(Counter);
React.render(element,document.getElementById('root'));

6.2 react/component.js #

src/react/component.js

class Component{
    constructor(props){
        this.props = props;
    }
    setState(partialState){
        this._currentUnit.update(null,partialState);
    }
}
export {Component}

6.3 react/unit.js #

src/react/unit.js

import {Element} from './element';
import $ from 'jquery';
class Unit {
    constructor(element){
        this._currentElement = element;
    }
    getMarkUp(){
        throw new Error('不能调用此方法');
    }
}
class TextUnit extends Unit{
    getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        return `<span data-reactid="${reactid}">${this._currentElement}</span>`;
    }
+    update(nextElement){
+        if(this._currentElement != nextElement){
+            this._currentElement = nextElement;
+            $(`[data-reactid="${this._reactid}"]`).html(this._currentElement);
+        }
+    }
}
class NativeUnit extends Unit {
     getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        let {type,props} = this._currentElement;
        let tagOpen = `<${type} data-reactid="${reactid}" `;
        let tagClose = `</$type>`;
        let content = '';
        for(let propName in props){
            if(/^on[A-Z]/.test(propName)){
                let eventName = propName.slice(2).toLowerCase();
                $(document).delegate(`[data-reactid="${reactid}"]`,`${eventName}.${reactid}`,props[propName]);
            }else if(propName === 'style'){
                let styleObj = props[propName];
                let styles = Object.keys(styleObj).map(attr=>`${attr}:${styleObj[attr]}`).join(';');
                tagOpen += (` style="${styles}" `);
            }else if (propName === 'children'){
                let children = props.children||[];
                children.map((child,index)=>{
                    let childUnit = createUnit(child);
                    let childMarkUp = childUnit.getMarkUp(`${reactid}.${index}`);
                    content += childMarkUp;
                });
            }else{
                tagOpen += ` ${propName}=${props[propName]} `;
            }
        }
        return tagOpen + '>' + content + tagClose;
    }
}

class CompositeUnit extends Unit{
    //接收到新的更新,自定义组件传第二个参数,原生组件和text传处一个参数
+    update(nextElement,partialState){
+        //如果传过来了新的元素,则使用新的元素
+        this._currentElement = nextElement||this._currentElement;
+        //获取新的状态对象和属性对象
+        let nextState = this._componentInstance.state= Object.assign(this._componentInstance.state,partialState);
+        let nextProps = this._currentElement.props;
+        //如果shouldComponentUpdate返回了false则不需要继续更新
+        if(this._componentInstance.shouldComponentUpdate&&this._componentInstance.shouldComponentUpdate(nextProps,nextState)===false){return;}
+        //获得上次渲染出来的unit实例 
+        let prevRenderedUnitInstance = this._renderedUnitInstance;
+        //从unit实例中获取
+        let prevRenderedElement = prevRenderedUnitInstance._currentElement;
+        //获取新的虚拟DOM
+        let nextRenderElement = this._componentInstance.render();
+        //进行domdiff对比
+        if(shouldDeepCompare(prevRenderedElement,nextRenderElement)){
+            //如果需要更新,则继续调用子节点的upate方法进行更新,传入新的element更新子节点
+            prevRenderedUnitInstance.update(nextRenderElement);
+            this._componentInstance.componentDidUpdate&&this._componentInstance.componentDidUpdate();
+        }else{
+            //如果发现不需要对比,干脆重新渲染
+            this._renderedUnitInstance =  createUnit(nextRenderElement);
+            let nextMarkUp = this._renderedUnitInstance.getMarkUp(this._reactid);
+            //替换整个节点
+            $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp);
+        }

    }
    getMarkUp(reactid){
        this._reactid = reactid;
        //type是一个自定义组件的类的定义
        let {type:Component,props} = this._currentElement;
        //创建Component类的实例
+        let componentInstance = this._componentInstance = new Component(props);
        //组件实例关联上自己的unit实例
+        componentInstance._currentUnit  = this;
        //组件将要渲染
        componentInstance.componentWillMount&&componentInstance.componentWillMount();
        //执行render方法获得虚拟DOM元素实例
        let renderedElement = componentInstance.render();
        //根据虚拟DOM元素得到unit,可能是TextUnit NativeUnit CompositeUnit
        let renderedUnitInstance = this._renderedUnitInstance= createUnit(renderedElement);
        //获得此unit的HTML标记字符串
        let renderedMarkUp = renderedUnitInstance.getMarkUp(reactid);
        //注册挂载完成的监听,越底层的组件越先监听,越先执行
        $(document).on('mounted',()=>componentInstance.componentDidMount&&componentInstance.componentDidMount());
        return renderedMarkUp;
    }
}

+function shouldDeepCompare(prevElement,nextElement){
+   if(prevElement!==null && nextElement!=null){
+       let prevType = typeof prevElement;
+       let nextType = typeof nextElement;
+       //如果新老节点都是文本可以进行比较
+       if((prevType === 'string' ||prevType === 'number')&&(nextType === 'string' ||nextType === 'number')){
+           return true;
+       }
+       if(prevElement instanceof Element && nextElement instanceof Element){
+           return prevElement.type === nextElement.type;
+       }
+   }  
+   return false;
+}
function createUnit(element){
  if(typeof element =='string' || typeof element =='number'){
      return new TextUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'string'){
      return new NativeUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'function'){
      return new CompositeUnit(element);
  }
}

export {
    createUnit
}

6.4 react/element.js #

src/react/element.js

class Element{
    constructor(type,props){
        this.type = type;
+       this.key = props.key;
        this.props = props;
    }
}

7. 对比属性 #

  • 实现点击加1功能

7.1 src/index.js #

src/index.js

import React from './react';
class Counter extends React.Component{
  constructor(props){
    super(props);
    this.state = {number:0};
  }
  componentWillMount(){
    console.log('Counter componentWillMount')
  }
  componentDidMount() {
     console.log('Counter componentDidMount')
  }
  handleClick= ()=>{
    this.setState({number:this.state.number+1});
  }
  render(){
   let p = React.createElement('p',{},this.state.number);
   let button = React.createElement('button',{onClick:this.handleClick},'+');
   return React.createElement('div',{id:'counter',style:{color:this.state.number%2===0?'red':'green',backgroundColor:this.state.number%2===0?'green':'red'}},p,button);
  }
}
let element = React.createElement(Counter);
React.render(element,document.getElementById('root'));

7.2 react/unit.js #

src/react/unit.js

import {Element} from './element';
import $ from 'jquery';
class Unit {
    constructor(element){
        this._currentElement = element;
    }
    getMarkUp(){
        throw new Error('不能调用此方法');
    }
}
class TextUnit extends Unit{
    getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        return `<span data-reactid="${reactid}">${this._currentElement}</span>`;
    }
    update(nextElement){
        if(this._currentElement != nextElement){
            this._currentElement = nextElement;
            $(`[data-reactid="${this._reactid}"]`).html(this._currentElement);
        }
    }

}
class NativeUnit extends Unit {
     getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        let {type,props} = this._currentElement;
        let tagOpen = `<${type} data-reactid="${reactid}" `;
        let tagClose = `</$type>`;
        let content = '';
        for(let propName in props){
            if(/^on[A-Z]/.test(propName)){
                let eventName = propName.slice(2).toLowerCase();
                $(document).delegate(`[data-reactid="${reactid}"]`,`${eventName}.${reactid}`,props[propName]);
            }else if(propName === 'style'){
+                let styleObj = props[propName];
+                let styles = Object.keys(styleObj).map(attr=>{
+                    let attrName = attr.replace(/([A-Z])/g,function(matched,group){
+                        return `-${group.toLowerCase()}`;
+                    })
+                    return `${attrName}:${styleObj[attr]}`;
+                }).join(';');
+                tagOpen += (` style="${styles}" `);
            }else if (propName === 'children'){
                let children = props.children||[];
                children.map((child,index)=>{
                    let childUnit = createUnit(child);
                    let childMarkUp = childUnit.getMarkUp(`${reactid}.${index}`);
                    content += childMarkUp;
                });
            }else{
                tagOpen += ` ${propName}=${props[propName]} `;
            }
        }
        return tagOpen + '>' + content + tagClose;
    }
+    update(nextElement){
+        let oldProps = this._currentElement.props;
+        let newProps = nextElement.props;
+        this.updateDOMproperties(oldProps,newProps);
+        //this.updateDOMChildren(nextElement.props.children);
+    }
+    updateDOMproperties(oldProps,newProps){
+        let propName;
+        //把新属性对象上没有属性给删除掉
+        for(propName in oldProps){
+            if(!newProps.hasOwnProperty(propName)){
+                $(`[data-reactid="${this._reactid}"]`).removeAttr(propName);
+            }
+            if(/^on[A-Z]/.test(propName)){
+                $(document).undelegate(`.${this.reactid}`);
+            }
+        }
+        for(propName in newProps){
+            if(propName == 'children'){
+                
+            }else if(/^on[A-Z]/.test(propName)){
+                let eventName = propName.slice(2).toLowerCase();
+                $(document).delegate(`[data-reactid="${this._reactid}"]`,`${eventName}.${this._reactid}`,newProps[propName]);
+            }else if(propName === 'style'){
+                let styleObj = newProps[propName];
+                Object.entries(styleObj).forEach(([attr,value])=>{
+                  $(`[data-reactid="${this._reactid}"]`).css(attr,value);
+                })
+            }else{
+                $(`[data-reactid="${this._reactid}"]`).prop(propName,newProps[propName]);
+            }
+        }
+    }
+}

class CompositeUnit extends Unit{
    //接收到新的更新,自定义组件传第二个参数,原生组件和text传处一个参数
    update(nextElement,partialState){
        //如果传过来了新的元素,则使用新的元素
        this._currentElement = nextElement||this._currentElement;
        //获取新的状态对象和属性对象
        let nextState = this._componentInstance.state= Object.assign(this._componentInstance.state,partialState);
        let nextProps = this._currentElement.props;
        //如果shouldComponentUpdate返回了false则不需要继续更新
        if(this._componentInstance.shouldComponentUpdate&&this._componentInstance.shouldComponentUpdate(nextProps,nextState)===false){return;}
        //获得上次渲染出来的unit实例 
        let prevRenderedUnitInstance = this._renderedUnitInstance;
        //从unit实例中获取
        let prevRenderedElement = prevRenderedUnitInstance._currentElement;
        //获取新的虚拟DOM
        let nextRenderElement = this._componentInstance.render();
        //进行domdiff对比
        if(shouldDeepCompare(prevRenderedElement,nextRenderElement)){
            //如果需要更新,则继续调用子节点的upate方法进行更新,传入新的element更新子节点
            prevRenderedUnitInstance.update(nextRenderElement);
            this._componentInstance.componentDidUpdate&&this._componentInstance.componentDidUpdate();
        }else{
            //如果发现不需要对比,干脆重新渲染
            this._renderedUnitInstance =  createUnit(nextRenderElement);
            let nextMarkUp = this._renderedUnitInstance.getMarkUp(this._reactid);
            //替换整个节点
            $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp);
        }

    }
    getMarkUp(reactid){
        this._reactid = reactid;
        //type是一个自定义组件的类的定义
        let {type:Component,props} = this._currentElement;
        //创建Component类的实例
        let componentInstance = this._componentInstance = new Component(props);
        //组件实例关联上自己的unit实例
        componentInstance._currentUnit  = this;
        //组件将要渲染
        componentInstance.componentWillMount&&componentInstance.componentWillMount();
        //执行render方法获得虚拟DOM元素实例
        let renderedElement = componentInstance.render();
        //根据虚拟DOM元素得到unit,可能是TextUnit NativeUnit CompositeUnit
        let renderedUnitInstance = this._renderedUnitInstance= createUnit(renderedElement);
        //获得此unit的HTML标记字符串
        let renderedMarkUp = renderedUnitInstance.getMarkUp(reactid);
        //注册挂载完成的监听,越底层的组件越先监听,越先执行
        $(document).on('mounted',()=>componentInstance.componentDidMount&&componentInstance.componentDidMount());
        return renderedMarkUp;
    }
}

function shouldDeepCompare(prevElement,nextElement){
   if(prevElement!==null && nextElement!=null){
       let prevType = typeof prevElement;
       let nextType = typeof nextElement;
       //如果新老节点都是文本可以进行比较
       if((prevType === 'string' ||prevType === 'number')&&(nextType === 'string' ||nextType === 'number')){
           return true;
       }
       if(prevElement instanceof Element && nextElement instanceof Element){
           return prevElement.type === nextElement.type;
       }
   }  
   return false;
}
function createUnit(element){
  if(typeof element =='string' || typeof element =='number'){
      return new TextUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'string'){
      return new NativeUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'function'){
      return new CompositeUnit(element);
  }
}

export {
    createUnit
}

8. 对比子元素 #

8.1 src/unit.js #

src\unit.js

import {Element} from './element';
import $ from 'jquery';
+ let diffQueue = [];
class Unit {
    constructor(element){
        this._currentElement = element;
    }
    getMarkUp(){
        throw new Error('不能调用此方法');
    }
}
class TextUnit extends Unit{
    getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        return `<span data-reactid="${reactid}">${this._currentElement}</span>`;
    }
    update(nextElement){
        if(this._currentElement != nextElement){
            this._currentElement = nextElement;
            $(`[data-reactid="${this._reactid}"]`).html(this._currentElement);
        }
    }

}
class NativeUnit extends Unit {
     getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        let {type,props} = this._currentElement;
        let tagOpen = `<${type} data-reactid="${reactid}" `;
        let tagClose = `</$type>`;
        let content = '';
+        let renderedChildUnits=[];
        for(let propName in props){
            if(/^on[A-Z]/.test(propName)){
                let eventName = propName.slice(2).toLowerCase();
                $(document).delegate(`[data-reactid="${reactid}"]`,`${eventName}.${reactid}`,props[propName]);
            }else if(propName === 'style'){
                let styleObj = props[propName];
                let styles = Object.keys(styleObj).map(attr=>{
                    let attrName = attr.replace(/([A-Z])/g,function(matched,group){
                        return `-${group.toLowerCase()}`;
                    })
                    return `${attrName}:${styleObj[attr]}`;
                }).join(';');
                tagOpen += (` style="${styles}" `);
            }else if (propName === 'children'){
                let children = props.children||[];
                children.map((child,index)=>{
                    let childUnit = createUnit(child);
+                    renderedChildUnits.push(childUnit);
                    let childMarkUp = childUnit.getMarkUp(`${reactid}.${index}`);
                    content += childMarkUp;
                });
            }else{
                tagOpen += ` ${propName}=${props[propName]} `;
            }
        }
+        this._renderedChildUnits = renderedChildUnits;
        return tagOpen + '>' + content + tagClose;
    }
    update(nextElement){
        let oldProps = this._currentElement.props;
        let newProps = nextElement.props;
        this.updateDOMproperties(oldProps,newProps);
+        this.updateDOMChildren(nextElement.props.children);
    }
+    //对比子元素
+    updateDOMChildren(newChildrenElements){
+        this.diff(diffQueue,newChildrenElements);
+    }
+    diff(diffQueue,newChildrenElements){
+        let oldChildUnitsMap = this.getChildrenMap(this._renderedChildUnits);
+        let newChildren = this.getNewChildren(oldChildUnitsMap,newChildrenElements);
+    }
+    getNewChildren(oldChildUnitsMap,newChildrenElements){
+        let newChildren = [];
+        newChildrenElements.forEach((newElement,index)=>{
+            let newKey = newElement.key||index.toString();
+            let oldUnit = oldChildUnitsMap[newKey];//获得老的unit
+            let oldElement = oldUnit&&oldUnit._currentElement;//获得老的element
+            if(shouldDeepCompare(oldElement,newElement)){//如果可以更进一步深比较
+                oldUnit.update(newElement);
+                newChildren.push(oldUnit);
+            }else{
+                let newChildUnit = createUnit(newElement);//如果不需要深比较则直接创建新的unit
+                newChildren.push(newChildUnit);
+            }
+        });
+        return newChildren;
+    }
+    getChildrenMap(childUnits=[]){
+        let map = {};
+        for(let i=0;i<childUnits.length;i++){
+            let key = childUnits[i].key||i.toString();
+            map[key]=childUnits[i];
+        }
+        return map;
+    }
    updateDOMproperties(oldProps,newProps){
        let propName;
        //把新属性对象上没有属性给删除掉
        for(propName in oldProps){
            if(!newProps.hasOwnProperty(propName)){
                $(`[data-reactid="${this._reactid}"]`).removeAttr(propName);
            }
            if(/^on[A-Z]/.test(propName)){
                $(document).undelegate(`.${this._reactid}`);
            }
        }
        for(propName in newProps){
            if(propName == 'children'){

            }else if(/^on[A-Z]/.test(propName)){
                let eventName = propName.slice(2).toLowerCase();
                $(document).undelegate(`.${this._reactid}`);
                $(document).delegate(`[data-reactid="${this._reactid}"]`,`${eventName}.${this._reactid}`,newProps[propName]);
            }else if(propName === 'style'){
                let styleObj = newProps[propName];
                Object.entries(styleObj).forEach(([attr,value])=>{
                  $(`[data-reactid="${this._reactid}"]`).css(attr,value);
                })
            }else{
                $(`[data-reactid="${this._reactid}"]`).prop(propName,newProps[propName]);
            }
        }
    }
}

class CompositeUnit extends Unit{
    //接收到新的更新,自定义组件传第二个参数,原生组件和text传处一个参数
    update(nextElement,partialState){
        //如果传过来了新的元素,则使用新的元素
        this._currentElement = nextElement||this._currentElement;
        //获取新的状态对象和属性对象
        let nextState = this._componentInstance.state= Object.assign(this._componentInstance.state,partialState);
        let nextProps = this._currentElement.props;
        //如果shouldComponentUpdate返回了false则不需要继续更新
        if(this._componentInstance.shouldComponentUpdate&&this._componentInstance.shouldComponentUpdate(nextProps,nextState)===false){return;}
        //获得上次渲染出来的unit实例 
        let prevRenderedUnitInstance = this._renderedUnitInstance;
        //从unit实例中获取
        let prevRenderedElement = prevRenderedUnitInstance._currentElement;
        //获取新的虚拟DOM
        let nextRenderElement = this._componentInstance.render();
        //进行domdiff对比
        if(shouldDeepCompare(prevRenderedElement,nextRenderElement)){
            //如果需要更新,则继续调用子节点的upate方法进行更新,传入新的element更新子节点
            prevRenderedUnitInstance.update(nextRenderElement);
            this._componentInstance.componentDidUpdate&&this._componentInstance.componentDidUpdate();
        }else{
            //如果发现不需要对比,干脆重新渲染
            this._renderedUnitInstance =  createUnit(nextRenderElement);
            let nextMarkUp = this._renderedUnitInstance.getMarkUp(this._reactid);
            //替换整个节点
            $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp);
        }

    }
    getMarkUp(reactid){
        this._reactid = reactid;
        //type是一个自定义组件的类的定义
        let {type:Component,props} = this._currentElement;
        //创建Component类的实例
        let componentInstance = this._componentInstance = new Component(props);
        //组件实例关联上自己的unit实例
        componentInstance._currentUnit  = this;
        //组件将要渲染
        componentInstance.componentWillMount&&componentInstance.componentWillMount();
        //执行render方法获得虚拟DOM元素实例
        let renderedElement = componentInstance.render();
        //根据虚拟DOM元素得到unit,可能是TextUnit NativeUnit CompositeUnit
        let renderedUnitInstance = this._renderedUnitInstance= createUnit(renderedElement);
        //获得此unit的HTML标记字符串
        let renderedMarkUp = renderedUnitInstance.getMarkUp(reactid);
        //注册挂载完成的监听,越底层的组件越先监听,越先执行
        $(document).on('mounted',()=>componentInstance.componentDidMount&&componentInstance.componentDidMount());
        return renderedMarkUp;
    }
}

function shouldDeepCompare(prevElement,nextElement){
   if(prevElement!==null && nextElement!=null){
       let prevType = typeof prevElement;
       let nextType = typeof nextElement;
       //如果新老节点都是文本可以进行比较
       if((prevType === 'string' ||prevType === 'number')&&(nextType === 'string' ||nextType === 'number')){
           return true;
       }
       if(prevElement instanceof Element && nextElement instanceof Element){
           return prevElement.type === nextElement.type;
       }
   }  
   return false;
}
function createUnit(element){
  if(typeof element =='string' || typeof element =='number'){
      return new TextUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'string'){
      return new NativeUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'function'){
      return new CompositeUnit(element);
  }
}

export {
    createUnit
}

9. 获得补丁数组 #

diffold

diffnew

domdiff2

9.1 src/index.js #

src/index.js

import React from './react';
class Counter extends React.Component{
  constructor(props){
    super(props);
    this.state = {odd:true};
  }
  componentDidMount(){
   setTimeout(()=>{
    this.setState({odd:!this.state.odd});
   },1000);
  }
  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'),
      );
    }
    return React.createElement('ul',{key:'wrapper'},
      React.createElement('li',{key:'A'},'A1'),
      React.createElement('li',{key:'C'},'C1'),
      React.createElement('li',{key:'B'},'B1'),
      React.createElement('li',{key:'E'},'E1'),
      React.createElement('li',{key:'F'},'F1')
      );
  }
}
let element = React.createElement(Counter);
React.render(element,document.getElementById('root'));

9.2 src/react/unit.js #

src/react/unit.js

import {Element} from './element';
import $ from 'jquery';
+import types from './types';
+let diffQueue = [];
+let updateDepth=0;
class Unit {
    constructor(element){
        this._currentElement = element;
    }
    getMarkUp(){
        throw new Error('不能调用此方法');
    }
}
class TextUnit extends Unit{
    getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        return `<span data-reactid="${reactid}">${this._currentElement}</span>`;
    }
    update(nextElement){
        if(this._currentElement != nextElement){
            this._currentElement = nextElement;
            $(`[data-reactid="${this._reactid}"]`).html(this._currentElement);
        }
    }

}
class NativeUnit extends Unit {
     getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        let {type,props} = this._currentElement;
        let tagOpen = `<${type} data-reactid="${reactid}" `;
        let tagClose = `</$type>`;
        let content = '';
        let renderedChildUnits=[];
        for(let propName in props){
            if(/^on[A-Z]/.test(propName)){
                let eventName = propName.slice(2).toLowerCase();
                $(document).delegate(`[data-reactid="${reactid}"]`,`${eventName}.${reactid}`,props[propName]);
            }else if(propName === 'style'){
                let styleObj = props[propName];
                let styles = Object.keys(styleObj).map(attr=>{
                    let attrName = attr.replace(/([A-Z])/g,function(matched,group){
                        return `-${group.toLowerCase()}`;
                    })
                    return `${attrName}:${styleObj[attr]}`;
                }).join(';');
                tagOpen += (` style="${styles}" `);
            }else if (propName === 'children'){
                let children = props.children||[];
                children.map((child,index)=>{
                    let childUnit = createUnit(child);
                    childUnit._mountIndex = index;
                    renderedChildUnits.push(childUnit);
                    let childMarkUp = childUnit.getMarkUp(`${reactid}.${index}`);
                    content += childMarkUp;
                });
            }else{
                tagOpen += ` ${propName}=${props[propName]} `;
            }
        }
        this._renderedChildUnits = renderedChildUnits;
        return tagOpen + '>' + content + tagClose;
    }
    update(nextElement){
        let oldProps = this._currentElement.props;
        let newProps = nextElement.props;
        this.updateDOMproperties(oldProps,newProps);
        this.updateDOMChildren(nextElement.props.children);
    }
    //对比子元素
+    updateDOMChildren(newChildrenElements){
+        updateDepth++;
+        this.diff(diffQueue,newChildrenElements);
+        updateDepth--;
+        if(updateDepth===0){
+            console.log('diffQueue',diffQueue);
+            diffQueue=[];
+        }
+    }
+    diff(diffQueue,newChildrenElements){
+        let oldChildUnitsMap = this.getChildrenMap(this._renderedChildUnits);
+        let {newChildrenMap,newChildren} = this.getNewChildren(oldChildUnitsMap,newChildrenElements);
+        // lastIndex里存放着被复用的子元素的最大索引
+        let lastIndex = 0;
+        for(let i=0;i<newChildren.length;i++){
+            let newChild = newChildren[i];//取得新元素
+            let newKey = (newChild._currentElement.props&&newChild._currentElement.key)||i.toString();//取得新key
+            let oldChild = oldChildUnitsMap[newKey];
+            if(oldChild === newChild){
+                if(oldChild._mountIndex < lastIndex){
+                    diffQueue.push({
+                        parentId:this._reactid,
+                        parentNode:$(`[data-reactid="${this._reactid}"]`),
+                        type:types.MOVE,
+                        fromIndex:oldChild._mountIndex,
+                        toIndex:i
+                    });
+                }
+                lastIndex = Math.max(oldChild._mountIndex,lastIndex);
+                //否则根本不用移动,直接修改挂载索引为新索引i即可
+            }else{
+                if(oldChild){
+                    diffQueue.push({
+                        parentId:this._reactid,
+                        parentNode:$(`[data-reactid="${this._reactid}"]`),
+                        type:types.REMOVE,
+                        fromIndex:oldChild._mountIndex
+                    });
+                    $(document).undelegate(`.${oldChild._reactid}`);
+                }
+                 diffQueue.push({
+                        parentId:this._reactid,
+                        parentNode:$(`[data-reactid="${this._reactid}"]`),
+                        type:types.INSERT,
+                        toIndex:i,
+                        markUp:newChild.getMarkUp(`${this._reactid}.${i}`)
+                });
+            }
+            newChild._mountIndex = i;
+        }
+        for(let oldKey in oldChildUnitsMap){
+            if(!newChildrenMap.hasOwnProperty(oldKey)){
+                let oldChild = oldChildUnitsMap[oldKey];
+                diffQueue.push({
+                        parentId:this._reactid,
+                        parentNode:$(`[data-reactid="${this._reactid}"]`),
+                        type:types.REMOVE,
+                        fromIndex:oldChild._mountIndex
+                });
+            }
+        }
+    }
+    getNewChildren(oldChildUnitsMap,newChildrenElements){
+        let newChildren = [];
+        let newChildrenMap={};
+        newChildrenElements.forEach((newElement,index)=>{
+            let newKey = newElement.key||index.toString();
+            let oldUnit = oldChildUnitsMap[newKey];//获得老的unit
+            let oldElement = oldUnit&&oldUnit._currentElement;//获得老的element
+            if(shouldDeepCompare(oldElement,newElement)){//如果可以更进一步深比较
+                oldUnit.update(newElement);
+                newChildren.push(oldUnit);
+                newChildrenMap[newKey]=oldUnit;
+            }else{
+                let newChildUnit = createUnit(newElement);//如果不需要深比较则直接创建新的unit
+                newChildren.push(newChildUnit);
+                 newChildrenMap[newKey]=newChildUnit;
+            }
+        });
+        return {newChildrenMap,newChildren};
+    }
+    getChildrenMap(childUnits=[]){
+        let map = {};
+        for(let i=0;i<childUnits.length;i++){
+            let key = (childUnits[i]._currentElement.props&&childUnits[i]._currentElement.props.key)||i.toString();
+            map[key]=childUnits[i];
+        }
+        return map;
+    }
+    updateDOMproperties(oldProps,newProps){
+        let propName;
+        //把新属性对象上没有属性给删除掉
+        for(propName in oldProps){
+            if(!newProps.hasOwnProperty(propName)){
+                $(`[data-reactid="${this._reactid}"]`).removeAttr(propName);
+            }
+            if(/^on[A-Z]/.test(propName)){
+                $(document).undelegate(`.${this._reactid}`);
+            }
+        }
+        for(propName in newProps){
+            if(propName == 'children'){
+                
+            }else if(/^on[A-Z]/.test(propName)){
+                let eventName = propName.slice(2).toLowerCase();
+                $(document).undelegate(`.${this._reactid}`);
+                $(document).delegate(`[data-reactid="${this._reactid}"]`,`${eventName}.${this._reactid}`,newProps[propName]);
+            }else if(propName === 'style'){
+                let styleObj = newProps[propName];
+                Object.entries(styleObj).forEach(([attr,value])=>{
+                  $(`[data-reactid="${this._reactid}"]`).css(attr,value);
+                })
+            }else{
+                $(`[data-reactid="${this._reactid}"]`).prop(propName,newProps[propName]);
+            }
+        }
+    }
+}

class CompositeUnit extends Unit{
    //接收到新的更新,自定义组件传第二个参数,原生组件和text传处一个参数
    update(nextElement,partialState){
        //如果传过来了新的元素,则使用新的元素
        this._currentElement = nextElement||this._currentElement;
        //获取新的状态对象和属性对象
        let nextState = this._componentInstance.state= Object.assign(this._componentInstance.state,partialState);
        let nextProps = this._currentElement.props;
        //如果shouldComponentUpdate返回了false则不需要继续更新
        if(this._componentInstance.shouldComponentUpdate&&this._componentInstance.shouldComponentUpdate(nextProps,nextState)===false){return;}
        //获得上次渲染出来的unit实例 
        let prevRenderedUnitInstance = this._renderedUnitInstance;
        //从unit实例中获取
        let prevRenderedElement = prevRenderedUnitInstance._currentElement;
        //获取新的虚拟DOM
        let nextRenderElement = this._componentInstance.render();
        //进行domdiff对比
        if(shouldDeepCompare(prevRenderedElement,nextRenderElement)){
            //如果需要更新,则继续调用子节点的upate方法进行更新,传入新的element更新子节点
            prevRenderedUnitInstance.update(nextRenderElement);
            this._componentInstance.componentDidUpdate&&this._componentInstance.componentDidUpdate();
        }else{
            //如果发现不需要对比,干脆重新渲染
            this._renderedUnitInstance =  createUnit(nextRenderElement);
            let nextMarkUp = this._renderedUnitInstance.getMarkUp(this._reactid);
            //替换整个节点
            $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp);
        }

    }
    getMarkUp(reactid){
        this._reactid = reactid;
        //type是一个自定义组件的类的定义
        let {type:Component,props} = this._currentElement;
        //创建Component类的实例
        let componentInstance = this._componentInstance = new Component(props);
        //组件实例关联上自己的unit实例
        componentInstance._currentUnit  = this;
        //组件将要渲染
        componentInstance.componentWillMount&&componentInstance.componentWillMount();
        //执行render方法获得虚拟DOM元素实例
        let renderedElement = componentInstance.render();
        //根据虚拟DOM元素得到unit,可能是TextUnit NativeUnit CompositeUnit
        let renderedUnitInstance = this._renderedUnitInstance= createUnit(renderedElement);
        //获得此unit的HTML标记字符串
        let renderedMarkUp = renderedUnitInstance.getMarkUp(reactid);
        //注册挂载完成的监听,越底层的组件越先监听,越先执行
        $(document).on('mounted',()=>componentInstance.componentDidMount&&componentInstance.componentDidMount());
        return renderedMarkUp;
    }
}

function shouldDeepCompare(prevElement,nextElement){
   if(prevElement!==null && nextElement!=null){
       let prevType = typeof prevElement;
       let nextType = typeof nextElement;
       //如果新老节点都是文本可以进行比较
       if((prevType === 'string' ||prevType === 'number')&&(nextType === 'string' ||nextType === 'number')){
           return true;
       }
       if(prevElement instanceof Element && nextElement instanceof Element){
           return prevElement.type === nextElement.type;
       }
   }  
   return false;
}
function createUnit(element){
  if(typeof element =='string' || typeof element =='number'){
      return new TextUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'string'){
      return new NativeUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'function'){
      return new CompositeUnit(element);
  }
}

export {
    createUnit
}

9.3 types.js #

src\types.js

export default  {
    MOVE:'MOVE',
    INSERT:"INSERT",
    REMOVE:"REMOVE"
}

10. 打补丁 #

10.1 src/index.js #

src/index.js

import React from './react';
class Counter extends React.Component{
  constructor(props){
    super(props);
    this.state = {odd:true};
  }
  componentDidMount(){
   setTimeout(()=>{
    this.setState({odd:!this.state.odd});
   },5000);
  }
  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'),
      );
    }
    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')
      );
  }
}
let element = React.createElement(Counter);
React.render(element,document.getElementById('root'));

10.2 react/unit.js #

src/react/unit.js

import {Element} from './element';
import $ from 'jquery';
import types from './types';
let diffQueue = [];
let updateDepth=0;
class Unit {
    constructor(element){
        this._currentElement = element;
    }
    getMarkUp(){
        throw new Error('不能调用此方法');
    }
}
class TextUnit extends Unit{
    getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        return `<span data-reactid="${reactid}">${this._currentElement}</span>`;
    }
    update(nextElement){
        if(this._currentElement != nextElement){
            this._currentElement = nextElement;
            $(`[data-reactid="${this._reactid}"]`).html(this._currentElement);
        }
    }

}
class NativeUnit extends Unit {
     getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        let {type,props} = this._currentElement;
        let tagOpen = `<${type} data-reactid="${reactid}" `;
        let tagClose = `</$type>`;
        let content = '';
        let renderedChildUnits=[];
        for(let propName in props){
            if(/^on[A-Z]/.test(propName)){
                let eventName = propName.slice(2).toLowerCase();
                $(document).delegate(`[data-reactid="${reactid}"]`,`${eventName}.${reactid}`,props[propName]);
            }else if(propName === 'style'){
                let styleObj = props[propName];
                let styles = Object.keys(styleObj).map(attr=>{
                    let attrName = attr.replace(/([A-Z])/g,function(matched,group){
                        return `-${group.toLowerCase()}`;
                    })
                    return `${attrName}:${styleObj[attr]}`;
                }).join(';');
                tagOpen += (` style="${styles}" `);
            }else if (propName === 'children'){
                let children = props.children||[];
                children.map((child,index)=>{
                    let childUnit = createUnit(child);
                    childUnit._mountIndex = index;
                    renderedChildUnits.push(childUnit);
                    let childMarkUp = childUnit.getMarkUp(`${reactid}.${index}`);
                    content += childMarkUp;
                });
            }else{
                tagOpen += ` ${propName}=${props[propName]} `;
            }
        }
        this._renderedChildUnits = renderedChildUnits;
        return tagOpen + '>' + content + tagClose;
    }
    update(nextElement){
        let oldProps = this._currentElement.props;
        let newProps = nextElement.props;
        this.updateDOMproperties(oldProps,newProps);
        this.updateDOMChildren(nextElement.props.children);
    }
    //对比子元素
    updateDOMChildren(newChildrenElements){
        updateDepth++;
        this.diff(diffQueue,newChildrenElements);
        updateDepth--;
        if(updateDepth===0){
            console.log('diffQueue',diffQueue);
+            this.patch(diffQueue);
            diffQueue=[];
        }
    }
+    patch(diffQueue){
+        let deleteChildren = [];
+        let deleteMap={};
+        for(let i=0;i<diffQueue.length;i++){
+            let difference = diffQueue[i];
+            if(difference.type===types.MOVE || difference.type===types.REMOVE){
+                let fromIndex = difference.fromIndex;
+                let oldChild = $(difference.parentNode.children().get(fromIndex));
+                deleteMap[fromIndex]=oldChild;
+                deleteChildren.push(oldChild);
+            }
+        }
+        $.each(deleteChildren,(idx,child)=>{
+            $(child).remove();
+        });

+        for(let k=0;k<diffQueue.length;k++){
+            let difference = diffQueue[k];
+            switch(difference.type){
+              case types.INSERT:
+                this.insertChildAt(difference.parentNode,$(difference.markUp),difference.toIndex);
+                break;
+              case types.MOVE:
+                this.insertChildAt(difference.parentNode,deleteMap[difference.fromIndex],difference.toIndex);
+                break;
+              default:
+               break;   
+            }
+        }
+    }
+    insertChildAt(parentNode,childNode,index){
+        let oldChild = parentNode.children().get(index);
+        oldChild?childNode.insertBefore(oldChild):childNode.appendTo(parentNode);
+    }
    diff(diffQueue,newChildrenElements){
        let oldChildUnitsMap = this.getChildrenMap(this._renderedChildUnits);
        let {newChildrenMap,newChildren} = this.getNewChildren(oldChildUnitsMap,newChildrenElements);
        // lastIndex里存放着被复用的子元素的最大索引
        let lastIndex = 0;
        for(let i=0;i<newChildren.length;i++){
            let newChild = newChildren[i];//取得新元素
            let newKey = (newChild._currentElement.props&&newChild._currentElement.key)||i.toString();//取得新key
            let oldChild = oldChildUnitsMap[newKey];
            if(oldChild === newChild){
                if(oldChild._mountIndex < lastIndex){
                    diffQueue.push({
                        parentId:this._reactid,
                        parentNode:$(`[data-reactid="${this._reactid}"]`),
                        type:types.MOVE,
                        fromIndex:oldChild._mountIndex,
                        toIndex:i
                    });
                }
                lastIndex = Math.max(oldChild._mountIndex,lastIndex);
                //否则根本不用移动,直接修改挂载索引为新索引i即可
            }else{
                if(oldChild){
                    diffQueue.push({
                        parentId:this._reactid,
                        parentNode:$(`[data-reactid="${this._reactid}"]`),
                        type:types.REMOVE,
                        fromIndex:oldChild._mountIndex
                    });
                    $(document).undelegate(`.${oldChild._reactid}`);
                }
                 diffQueue.push({
                        parentId:this._reactid,
                        parentNode:$(`[data-reactid="${this._reactid}"]`),
                        type:types.INSERT,
                        toIndex:i,
                        markUp:newChild.getMarkUp(`${this._reactid}.${i}`)
                });
            }
            newChild._mountIndex = i;
        }
        for(let oldKey in oldChildUnitsMap){
            if(!newChildrenMap.hasOwnProperty(oldKey)){
                let oldChild = oldChildUnitsMap[oldKey];
                diffQueue.push({
                        parentId:this._reactid,
                        parentNode:$(`[data-reactid="${this._reactid}"]`),
                        type:types.REMOVE,
                        fromIndex:oldChild._mountIndex
                });
            }
        }
    }
    getNewChildren(oldChildUnitsMap,newChildrenElements){
        let newChildren = [];
        let newChildrenMap={};
        newChildrenElements.forEach((newElement,index)=>{
            let newKey = newElement.key||index.toString();
            let oldUnit = oldChildUnitsMap[newKey];//获得老的unit
            let oldElement = oldUnit&&oldUnit._currentElement;//获得老的element
            if(shouldDeepCompare(oldElement,newElement)){//如果可以更进一步深比较
                oldUnit.update(newElement);
                newChildren.push(oldUnit);
                newChildrenMap[newKey]=oldUnit;
            }else{
                let newChildUnit = createUnit(newElement);//如果不需要深比较则直接创建新的unit
                newChildren.push(newChildUnit);
                 newChildrenMap[newKey]=newChildUnit;
            }
        });
        return {newChildrenMap,newChildren};
    }
    getChildrenMap(childUnits=[]){
        let map = {};
        for(let i=0;i<childUnits.length;i++){
            let key = (childUnits[i]._currentElement.props&&childUnits[i]._currentElement.props.key)||i.toString();
            map[key]=childUnits[i];
        }
        return map;
    }
    updateDOMproperties(oldProps,newProps){
        let propName;
        //把新属性对象上没有属性给删除掉
        for(propName in oldProps){
            if(!newProps.hasOwnProperty(propName)){
                $(`[data-reactid="${this._reactid}"]`).removeAttr(propName);
            }
            if(/^on[A-Z]/.test(propName)){
                $(document).undelegate(`.${this._reactid}`);
            }
        }
        for(propName in newProps){
            if(propName == 'children'){

            }else if(/^on[A-Z]/.test(propName)){
                let eventName = propName.slice(2).toLowerCase();
                $(document).undelegate(`.${this._reactid}`);
                $(document).delegate(`[data-reactid="${this._reactid}"]`,`${eventName}.${this._reactid}`,newProps[propName]);
            }else if(propName === 'style'){
                let styleObj = newProps[propName];
                Object.entries(styleObj).forEach(([attr,value])=>{
                  $(`[data-reactid="${this._reactid}"]`).css(attr,value);
                })
            }else{
                $(`[data-reactid="${this._reactid}"]`).prop(propName,newProps[propName]);
            }
        }
    }
}

class CompositeUnit extends Unit{
    //接收到新的更新,自定义组件传第二个参数,原生组件和text传处一个参数
    update(nextElement,partialState){
        //如果传过来了新的元素,则使用新的元素
        this._currentElement = nextElement||this._currentElement;
        //获取新的状态对象和属性对象
        let nextState = this._componentInstance.state= Object.assign(this._componentInstance.state,partialState);
        let nextProps = this._currentElement.props;
        //如果shouldComponentUpdate返回了false则不需要继续更新
        if(this._componentInstance.shouldComponentUpdate&&this._componentInstance.shouldComponentUpdate(nextProps,nextState)===false){return;}
        //获得上次渲染出来的unit实例 
        let prevRenderedUnitInstance = this._renderedUnitInstance;
        //从unit实例中获取
        let prevRenderedElement = prevRenderedUnitInstance._currentElement;
        //获取新的虚拟DOM
        let nextRenderElement = this._componentInstance.render();
        //进行domdiff对比
        if(shouldDeepCompare(prevRenderedElement,nextRenderElement)){
            //如果需要更新,则继续调用子节点的upate方法进行更新,传入新的element更新子节点
            prevRenderedUnitInstance.update(nextRenderElement);
            this._componentInstance.componentDidUpdate&&this._componentInstance.componentDidUpdate();
        }else{
            //如果发现不需要对比,干脆重新渲染
            this._renderedUnitInstance =  createUnit(nextRenderElement);
            let nextMarkUp = this._renderedUnitInstance.getMarkUp(this._reactid);
            //替换整个节点
            $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp);
        }

    }
    getMarkUp(reactid){
        this._reactid = reactid;
        //type是一个自定义组件的类的定义
        let {type:Component,props} = this._currentElement;
        //创建Component类的实例
        let componentInstance = this._componentInstance = new Component(props);
        //组件实例关联上自己的unit实例
        componentInstance._currentUnit  = this;
        //组件将要渲染
        componentInstance.componentWillMount&&componentInstance.componentWillMount();
        //执行render方法获得虚拟DOM元素实例
        let renderedElement = componentInstance.render();
        //根据虚拟DOM元素得到unit,可能是TextUnit NativeUnit CompositeUnit
        let renderedUnitInstance = this._renderedUnitInstance= createUnit(renderedElement);
        //获得此unit的HTML标记字符串
        let renderedMarkUp = renderedUnitInstance.getMarkUp(reactid);
        //注册挂载完成的监听,越底层的组件越先监听,越先执行
        $(document).on('mounted',()=>componentInstance.componentDidMount&&componentInstance.componentDidMount());
        return renderedMarkUp;
    }
}

function shouldDeepCompare(prevElement,nextElement){
   if(prevElement!==null && nextElement!=null){
       let prevType = typeof prevElement;
       let nextType = typeof nextElement;
       //如果新老节点都是文本可以进行比较
       if((prevType === 'string' ||prevType === 'number')&&(nextType === 'string' ||nextType === 'number')){
           return true;
       }
       if(prevElement instanceof Element && nextElement instanceof Element){
           return prevElement.type === nextElement.type;
       }
   }  
   return false;
}
function createUnit(element){
  if(typeof element =='string' || typeof element =='number'){
      return new TextUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'string'){
      return new NativeUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'function'){
      return new CompositeUnit(element);
  }
}

export {
    createUnit
}

11. todos #

commit

11.1 src/index.js #

src/index.js

import React from './react';
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],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("div", {}, itemText,React.createElement('button',{onClick: this.onDel.bind(this,index)},'X'));
        };

        var lists = this.state.list.map(createItem);
        var input = React.createElement("input", {onKeyup: this.onChange.bind(this),value: this.state.text});
        var button = React.createElement("button", {onClick: this.add.bind(this)}, 'Add')
        return React.createElement('div',{},input,button,...lists);
    }
}
let todos = React.createElement(Todos);
React.render(todos,document.getElementById('root'));

11.2 react/unit.js #

src/react/unit.js

import {Element} from './element';
import $ from 'jquery';
import types from './types';
let diffQueue = [];
let updateDepth=0;
class Unit {
    constructor(element){
        this._currentElement = element;
    }
    getMarkUp(){
        throw new Error('不能调用此方法');
    }
}
class TextUnit extends Unit{
    getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        return `<span data-reactid="${reactid}">${this._currentElement}</span>`;
    }
    update(nextElement){
        if(this._currentElement != nextElement){
            this._currentElement = nextElement;
            $(`[data-reactid="${this._reactid}"]`).html(this._currentElement);
        }
    }

}
class NativeUnit extends Unit {
     getMarkUp(reactid){
         this._reactid = reactid;//保存记录reactid
        //返回文本节点对应的HTML字符串
        let {type,props} = this._currentElement;
        let tagOpen = `<${type} data-reactid="${reactid}" `;
        let tagClose = `</$type>`;
        let content = '';
        let renderedChildUnits=[];
        for(let propName in props){
            if(/^on[A-Z]/.test(propName)){
                let eventName = propName.slice(2).toLowerCase();
                $(document).delegate(`[data-reactid="${reactid}"]`,`${eventName}.${reactid}`,props[propName]);
            }else if(propName === 'style'){
                let styleObj = props[propName];
                let styles = Object.keys(styleObj).map(attr=>{
                    let attrName = attr.replace(/([A-Z])/g,function(matched,group){
                        return `-${group.toLowerCase()}`;
                    })
                    return `${attrName}:${styleObj[attr]}`;
                }).join(';');
                tagOpen += (` style="${styles}" `);
            }else if (propName === 'children'){
                let children = props.children||[];
                children.map((child,index)=>{
                    let childUnit = createUnit(child);
                    childUnit._mountIndex = index;
                    renderedChildUnits.push(childUnit);
                    let childMarkUp = childUnit.getMarkUp(`${reactid}.${index}`);
                    content += childMarkUp;
                });
            }else{
                tagOpen += ` ${propName}=${props[propName]} `;
            }
        }
        this._renderedChildUnits = renderedChildUnits;
        return tagOpen + '>' + content + tagClose;
    }
    update(nextElement){
        let oldProps = this._currentElement.props;
        let newProps = nextElement.props;
        this.updateDOMproperties(oldProps,newProps);
        this.updateDOMChildren(nextElement.props.children);
    }
    //对比子元素
    updateDOMChildren(newChildrenElements){
        updateDepth++;
        this.diff(diffQueue,newChildrenElements);
        updateDepth--;
        if(updateDepth===0){
            console.log('diffQueue',diffQueue);
            this.patch(diffQueue);
            diffQueue=[];
        }
    }
    patch(diffQueue){
        let deleteChildren = [];
        let deleteMap={};
        for(let i=0;i<diffQueue.length;i++){
            let difference = diffQueue[i];
            if(difference.type===types.MOVE || difference.type===types.REMOVE){
                let fromIndex = difference.fromIndex;
+                let parentId = difference.parentId;
+                let oldChild = $(difference.parentNode.children().get(fromIndex));
+                deleteMap[parentId]={};
+                deleteMap[parentId][fromIndex]=oldChild;
                deleteChildren.push(oldChild);
            }
        }
        $.each(deleteChildren,(idx,child)=>{
            $(child).remove();
        });

        for(let k=0;k<diffQueue.length;k++){
            let difference = diffQueue[k];
            switch(difference.type){
              case types.INSERT:
                this.insertChildAt(difference.parentNode,$(difference.markUp),difference.toIndex);
                break;
              case types.MOVE:
+                this.insertChildAt(difference.parentNode,deleteMap[difference.parentId][difference.fromIndex],difference.toIndex);
                break;
              default:
               break;   
            }
        }
    }
    insertChildAt(parentNode,childNode,index){
        let oldChild = parentNode.children().get(index);
        oldChild?childNode.insertBefore(oldChild):childNode.appendTo(parentNode);
    }
    diff(diffQueue,newChildrenElements){
        let oldChildUnitsMap = this.getChildrenMap(this._renderedChildUnits);
        let {newChildrenMap,newChildren} = this.getNewChildren(oldChildUnitsMap,newChildrenElements);
        // lastIndex里存放着被复用的子元素的最大索引
        let lastIndex = 0;
        for(let i=0;i<newChildren.length;i++){
            let newChild = newChildren[i];//取得新元素
            let newKey = (newChild._currentElement.props&&newChild._currentElement.key)||i.toString();//取得新key
            let oldChild = oldChildUnitsMap[newKey];
            if(oldChild === newChild){
                if(oldChild._mountIndex < lastIndex){
                    diffQueue.push({
                        parentId:this._reactid,
                        parentNode:$(`[data-reactid="${this._reactid}"]`),
                        type:types.MOVE,
                        fromIndex:oldChild._mountIndex,
                        toIndex:i
                    });
                }
                lastIndex = Math.max(oldChild._mountIndex,lastIndex);
                //否则根本不用移动,直接修改挂载索引为新索引i即可
            }else{
                if(oldChild){
                    diffQueue.push({
                        parentId:this._reactid,
                        parentNode:$(`[data-reactid="${this._reactid}"]`),
                        type:types.REMOVE,
                        fromIndex:oldChild._mountIndex
                    });
                    $(document).undelegate(`.${oldChild._reactid}`);
                }
                 diffQueue.push({
                        parentId:this._reactid,
                        parentNode:$(`[data-reactid="${this._reactid}"]`),
                        type:types.INSERT,
                        toIndex:i,
                        markUp:newChild.getMarkUp(`${this._reactid}.${i}`)
                });
            }
            newChild._mountIndex = i;
        }
        for(let oldKey in oldChildUnitsMap){
            if(!newChildrenMap.hasOwnProperty(oldKey)){
                let oldChild = oldChildUnitsMap[oldKey];
                diffQueue.push({
                        parentId:this._reactid,
                        parentNode:$(`[data-reactid="${this._reactid}"]`),
                        type:types.REMOVE,
                        fromIndex:oldChild._mountIndex
                });
+                this._renderedChildUnits = this._renderedChildUnits.filter(item=>item != oldChild);
+                $(document).undelegate(`.${oldChild._reactid}`);
            }
        }
    }
    getNewChildren(oldChildUnitsMap,newChildrenElements){
        let newChildren = [];
        let newChildrenMap={};
        newChildrenElements.forEach((newElement,index)=>{
            let newKey = newElement.key||index.toString();
            let oldUnit = oldChildUnitsMap[newKey];//获得老的unit
            let oldElement = oldUnit&&oldUnit._currentElement;//获得老的element
            if(shouldDeepCompare(oldElement,newElement)){//如果可以更进一步深比较
                oldUnit.update(newElement);
                newChildren.push(oldUnit);
                newChildrenMap[newKey]=oldUnit;
            }else{
                let newChildUnit = createUnit(newElement);//如果不需要深比较则直接创建新的unit
                newChildren.push(newChildUnit);
                newChildrenMap[newKey]=newChildUnit;
                this._renderedChildUnits[index]=newChildUnit;
            }
        });
        return {newChildrenMap,newChildren};
    }
    getChildrenMap(childUnits=[]){
        let map = {};
        for(let i=0;i<childUnits.length;i++){
            let key = (childUnits[i]._currentElement.props&&childUnits[i]._currentElement.props.key)||i.toString();
            map[key]=childUnits[i];
        }
        return map;
    }
    updateDOMproperties(oldProps,newProps){
        let propName;
        //把新属性对象上没有属性给删除掉
        for(propName in oldProps){
            if(!newProps.hasOwnProperty(propName)){
                $(`[data-reactid="${this._reactid}"]`).removeAttr(propName);
            }
            if(/^on[A-Z]/.test(propName)){
                $(document).undelegate(`.${this._reactid}`);
            }
        }
        for(propName in newProps){
            if(propName == 'children'){

            }else if(/^on[A-Z]/.test(propName)){
                let eventName = propName.slice(2).toLowerCase();
                $(document).undelegate(`.${this._reactid}`);
                $(document).delegate(`[data-reactid="${this._reactid}"]`,`${eventName}.${this._reactid}`,newProps[propName]);
            }else if(propName === 'style'){
                let styleObj = newProps[propName];
                Object.entries(styleObj).forEach(([attr,value])=>{
                  $(`[data-reactid="${this._reactid}"]`).css(attr,value);
                })
            }else{
                $(`[data-reactid="${this._reactid}"]`).prop(propName,newProps[propName]);
            }
        }
    }
}

class CompositeUnit extends Unit{
    //接收到新的更新,自定义组件传第二个参数,原生组件和text传处一个参数
    update(nextElement,partialState){
        //如果传过来了新的元素,则使用新的元素
        this._currentElement = nextElement||this._currentElement;
        //获取新的状态对象和属性对象
        let nextState = this._componentInstance.state= Object.assign(this._componentInstance.state,partialState);
        let nextProps = this._currentElement.props;
        //如果shouldComponentUpdate返回了false则不需要继续更新
        if(this._componentInstance.shouldComponentUpdate&&this._componentInstance.shouldComponentUpdate(nextProps,nextState)===false){return;}
        //获得上次渲染出来的unit实例 
        let prevRenderedUnitInstance = this._renderedUnitInstance;
        //从unit实例中获取
        let prevRenderedElement = prevRenderedUnitInstance._currentElement;
        //获取新的虚拟DOM
        let nextRenderElement = this._componentInstance.render();
        //进行domdiff对比
        if(shouldDeepCompare(prevRenderedElement,nextRenderElement)){
            //如果需要更新,则继续调用子节点的upate方法进行更新,传入新的element更新子节点
            prevRenderedUnitInstance.update(nextRenderElement);
            this._componentInstance.componentDidUpdate&&this._componentInstance.componentDidUpdate();
        }else{
            //如果发现不需要对比,干脆重新渲染
            this._renderedUnitInstance =  createUnit(nextRenderElement);
            let nextMarkUp = this._renderedUnitInstance.getMarkUp(this._reactid);
            //替换整个节点
            $(`[data-reactid="${this._reactid}"]`).replaceWith(nextMarkUp);
        }

    }
    getMarkUp(reactid){
        this._reactid = reactid;
        //type是一个自定义组件的类的定义
        let {type:Component,props} = this._currentElement;
        //创建Component类的实例
        let componentInstance = this._componentInstance = new Component(props);
        //组件实例关联上自己的unit实例
        componentInstance._currentUnit  = this;
        //组件将要渲染
        componentInstance.componentWillMount&&componentInstance.componentWillMount();
        //执行render方法获得虚拟DOM元素实例
        let renderedElement = componentInstance.render();
        //根据虚拟DOM元素得到unit,可能是TextUnit NativeUnit CompositeUnit
        let renderedUnitInstance = this._renderedUnitInstance= createUnit(renderedElement);
        //获得此unit的HTML标记字符串
        let renderedMarkUp = renderedUnitInstance.getMarkUp(reactid);
        //注册挂载完成的监听,越底层的组件越先监听,越先执行
        $(document).on('mounted',()=>componentInstance.componentDidMount&&componentInstance.componentDidMount());
        return renderedMarkUp;
    }
}

function shouldDeepCompare(prevElement,nextElement){
   if(prevElement!==null && nextElement!=null){
       let prevType = typeof prevElement;
       let nextType = typeof nextElement;
       //如果新老节点都是文本可以进行比较
       if((prevType === 'string' ||prevType === 'number')&&(nextType === 'string' ||nextType === 'number')){
           return true;
       }
       if(prevElement instanceof Element && nextElement instanceof Element){
           return prevElement.type === nextElement.type;
       }
   }  
   return false;
}
function createUnit(element){
  if(typeof element =='string' || typeof element =='number'){
      return new TextUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'string'){
      return new NativeUnit(element);
  }
  if(element instanceof Element && typeof element.type === 'function'){
      return new CompositeUnit(element);
  }
}

export {
    createUnit
}

10. diff 策略 #

  • Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。
  • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
  • 对于同一层级的一组子节点,它们可以通过唯一key进行区分。

10.1 tree diff #

  • React 对树的算法进行了简洁明了的优化,即对树进行分层比较,两棵树只会对同一层次的节点进行比较

sametree

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

movemytree

10.2 component diff #

  • 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree
  • 如果不是,则将该组件判断为dirty component,从而替换整个组件下的所有子节点

deleteall

10.3 element diff #

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

10.4 key #

oldnewmove

oldnewmove2

oldnewmove3

11.delegate #

  • delegate() 方法为指定的元素(属于被选元素的子元素)添加一个或多个事件处理程序,并规定当这些事件发生时运行的函数,使用 delegate() 方法的事件处理程序适用于当前或未来的元素(比如由脚本创建的新元素)
参数名称 参数含义
childSelector 必需,规定要附加事件处理程序的一个或多个子元素
event 必需,规定附加到元素的一个或多个事件,由空格分隔多个事件值。必须是有效的事件
data 可选,规定传递到函数的额外数据
function 必需,规定当事件发生时运行的函数
  • delegate
  • undelegate

  • 参数events还支持为事件类型附加额外的命名空间

  • 当为同一元素绑定多个相同类型的事件处理函数时。使用命名空间,可以在触发事件、移除事件时限定触发或移除的范围。
var $document = $(document);
//为#btn1元素绑定click事件,定义在foo和bar两个命名空间下
$document.delegate("#btn1", "click.foo.bar", function(event){
    alert("click-1");
});
//为#btn1元素绑定click事件,定义在test命名空间下
$document.delegate("#btn1", "click.test", function(event){
    alert("click-2");
});
//为#btn1元素绑定click事件,定义在test和foo两个命名空间下
$document.delegate("#btn1", "click.test.foo", function(event){
    alert("click-3");
});


// 触发所有click事件
$btn1.trigger("click"); // 触发A和B (event.namespace = "")
// 触发定义在foo命名空间下的click事件
$btn1.trigger("click.foo"); // 触发A (event.namespace = "foo")
// 触发定义在bar命名空间下的click事件
$btn1.trigger("click.bar"); // 触发A (event.namespace = "bar")
// 移除所有btn1元素定义在foo命名空间下的click事件处理函数
$btn1.undelegate( "click.foo" ); // 移除A

// $document.undelegate(".test"); // 移除click-2、click-3

// $document.undelegate(".foo");  // 移除click-1、click-3

// $document.undelegate(".foo.bar");  // 移除click-1

访问验证

请输入访问令牌

Token不正确,请重新输入