1. 原生JS实现计数器 #

<body>
<button id="counter-btn">
</button>

<script>
let counterBtn = document.getElementById('counter-btn');
let number = 0;
counterBtn.addEventListener('click',function(){
    counterBtn.innerHTML = ++number;
});
</script>
</body>

2. HTML结构的复用 #

2.1 index.html #

<body>
<div id="counter-app"></div>
<script src="index.js"></script>
<script>
let counterApp = document.getElementById('counter-app');
counterApp.innerHTML = new Counter().render();
</script>
</body>

2.2 index.js #

class Counter{
  render(){
      return (
          `
          <button id="counter-btn">
            0
          </button>
          `
      )
  }
}

3.生成DOM元素并添加事件 #

3.1 index.html #

<body>
<div id="counter-app"></div>

<script src="index.js"></script>
<script>
let counterApp = document.getElementById('counter-app');
counterApp.appendChild(new Counter().render());
</script>
</body>

3.2 index.js #

class Counter{
  constructor(){
      this.state = {number:0};
  }  
  createDOMFromString(domString){
    const div = document.createElement('div');
    div.innerHTML = domString;
    return div.children[0];
  }
  increment (){
    this.state = {number:this.state.number+1};
    let oldElement = this.domElement;
    let newElement = this.render();
    oldElement.parentElement.replaceChild(newElement,oldElement);
  }
  render(){
     this.domElement = this.createDOMFromString(`
     <button id="counter-btn">
        ${this.state.number}
     </button>
     `);
     this.domElement.addEventListener('click',this.increment.bind(this));
     return this.domElement;
  }
}

4.抽象Component #

4.1 index.html #

<body>
<div id="counter-app"></div>
<script src="index.js"></script>
<script>
let counterApp = document.getElementById('counter-app');
new Counter({name:'珠峰架构'}).mount(counterApp);
</script>
</body>

4.2 index.js #

window.trigger = function(event,name){
    let component = event.target.component;
    component[name].call(component,event);
}
class Component{
    constructor(props){
        this.props = props;
    }
    createDOMFromString(domString){
        const div = document.createElement('div');
        div.innerHTML = domString;
        return div.children[0];
    }
    setState(partialState){
        this.state = Object.assign(this.state,partialState);
        let oldElement = this.domElement;
        let newElement = this.renderElement();
        oldElement.parentElement.replaceChild(newElement,oldElement);
    }
    renderElement(){
        let renderString = this.render();
        this.domElement = this.createDOMFromString(renderString);
        this.domElement.component = this;
        return this.domElement;
    }
    mount(container){
        container.appendChild(this.renderElement());
    }
}
class Counter extends Component{
  constructor(props){
      super(props);
      this.state = {number:0};
  }  
  increment(){
   this.setState({number:this.state.number+1});
   console.log(this.state);
   this.setState({number:this.state.number+1});
   console.log(this.state);
   setTimeout(()=>{
    this.setState({number:this.state.number+1});
    console.log(this.state);
    this.setState({number:this.state.number+1});
    console.log(this.state);
   },1000);
  }
  render(){
      return (
        `
        <button id="counter-btn" onclick="trigger(event,'increment')">
         ${this.props.name}:${this.state.number}
        </button>
        `
      )
  }
}

5.setState可能是异步的 #

setState

5.1 index.html #

<body>
<div id="counter-app"></div>
<script src="index.js"></script>
<script>
let counterApp = document.getElementById('counter-app');
new Counter({name:'珠峰架构'}).mount(counterApp);
</script>
</body>

5.2 index.js #


let batchingStrategy = {
    isBatchingUpdates:false,
    updaters:[],
    batchedUpdates(){
        this.updaters.forEach(updater => {
            updater.component.updateComponent();
        });
    }
}
class Updater{
    constructor(component){
        this.component = component;
        this.pendingStates = [];
    }
    addState(particalState){
        this.pendingStates.push(particalState);
        batchingStrategy.isBatchingUpdates?batchingStrategy.updaters.push(this):this.component.updateComponent();
    }
}
let transaction = new Transaction({
    initialize() {
        batchingStrategy.isBatchingUpdates = true;
    },
    close() {
        batchingStrategy.isBatchingUpdates = false;
        batchingStrategy.batchedUpdates();
    }
});
window.trigger = function(event,name){
    batchingStrategy.isBatchingUpdates = true;
    let component = event.target.component;
    component[name].bind(component,event);
    batchingStrategy.isBatchingUpdates = false;
    batchingStrategy.batchedUpdates();
}
class Component{
    constructor(props){
        this.props = props;
        this.$updater = new Updater(this);
    }
    createDOMFromString(domString){
        const div = document.createElement('div');
        div.innerHTML = domString;
        return div.children[0];
    }
    setState(particalState){
        this.$updater.addState(particalState);
    }
    updateComponent(){
        let pendingStates = this.$updater.pendingStates;
        pendingStates.forEach(particalState=>Object.assign(this.state,particalState));
        this.$updater.pendingStates.length = 0;
        let oldElement = this.domElement;
        let newElement = this.renderElement();
        oldElement.parentElement.replaceChild(newElement,oldElement);
    }
    renderElement(){
        let renderString = this.render();
        this.domElement = this.createDOMFromString(renderString);
        this.domElement.component = this;
        return this.domElement;
    }
    mount(container){
        container.appendChild(this.renderElement());
    }
}
class Counter extends Component{
  constructor(props){
      super(props);
      this.state = {number:0};
  }  
  increment(){
   this.setState({number:this.state.number+1});
   console.log(this.state);
   this.setState({number:this.state.number+1});
   console.log(this.state);
   setTimeout(()=>{
    this.setState({number:this.state.number+1});
    console.log(this.state);
    this.setState({number:this.state.number+1});
    console.log(this.state);
   },1000);
  }
  render(){
      return (
        `
        <button id="counter-btn" onclick="trigger(event,'increment')">
         ${this.props.name}:${this.state.number}
        </button>
        `
      )
  }
}

6.事务 #

 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+
 * 
 

6.1 transaction #

function setState() {
    console.log('setState')
}
class Transaction {
    constructor(wrappers) {
        this.wrappers = wrappers;
    }
    perform(func) {
        this.wrappers.forEach(wrapper=>wrapper.initialize())
        func.call();
        this.wrappers.forEach(wrapper=>wrapper.close())
    }

}
let transaction = new Transaction([
    {
        initialize() {
            console.log('before1');
        },
        close() {
            console.log('after1');
        }
    },
    {
        initialize() {
            console.log('before2');
        },
        close() {
            console.log('after2');
        }
    }
]);
transaction.perform(setState);

6.2 index.js #

class Transaction {
    constructor(wrapper){
        this.wrapper = wrapper;
    }
    perform(func){
        this.wrapper.initialize();
        func.call();
        this.wrapper.close();
    }

}
let batchingStrategy = {
    isBatchingUpdates:false,
    updaters:[],
    batchedUpdates(){
        this.updaters.forEach(updater => {
            updater.component.updateComponent();
        });
    }
}
class Updater{
    constructor(component){
        this.component = component;
        this.pendingStates = [];
    }
    addState(particalState){
        this.pendingStates.push(particalState);
        batchingStrategy.isBatchingUpdates?batchingStrategy.updaters.push(this):this.component.updateComponent();
    }
}
let transaction = new Transaction({
    initialize() {
        batchingStrategy.isBatchingUpdates = true;
    },
    close() {
        batchingStrategy.isBatchingUpdates = false;
        batchingStrategy.batchedUpdates();
    }
});
window.trigger = function(event,name){
    let component = event.target.component;
    transaction.perform(component[name].bind(component,event));
}
class Component{
    constructor(props){
        this.props = props;
        this.$updater = new Updater(this);
    }
    createDOMFromString(domString){
        const div = document.createElement('div');
        div.innerHTML = domString;
        return div.children[0];
    }
    setState(particalState){
        this.$updater.addState(particalState);
    }
    updateComponent(){
        let pendingStates = this.$updater.pendingStates;
        pendingStates.forEach(particalState=>Object.assign(this.state,particalState));
        this.$updater.pendingStates.length = 0;
        let oldElement = this.domElement;
        let newElement = this.renderElement();
        oldElement.parentElement.replaceChild(newElement,oldElement);
    }
    renderElement(){
        let renderString = this.render();
        this.domElement = this.createDOMFromString(renderString);
        this.domElement.component = this;
        return this.domElement;
    }
    mount(container){
        container.appendChild(this.renderElement());
    }
}
class Counter extends Component{
  constructor(props){
      super(props);
      this.state = {number:0};
  }  
  increment(){
   this.setState({number:this.state.number+1});
   console.log(this.state);
   this.setState({number:this.state.number+1});
   console.log(this.state);
   setTimeout(()=>{
    this.setState({number:this.state.number+1});
    console.log(this.state);
    this.setState({number:this.state.number+1});
    console.log(this.state);
   },1000);
  }
  render(){
      return (
        `
        <button id="counter-btn" onclick="trigger(event,'increment')">
         ${this.props.name}:${this.state.number}
        </button>
        `
      )
  }
}