11.2.React-setState原理

248 阅读2分钟

1. 原生计数器

<button id="counter-app">0</button>
<script>
  let counterApp = document.getElementById('counter-app');
  let number = 0;
  counterApp.addEventListener('click', () => {
    counterApp.innerHTML = ++number;
  })
</script>

2. 类实现计数器

  1. index.html
<div id="counter-app"></div>
<script src="index.js"></script>
<script>
  let counterApp = document.getElementById('counter-app');
  counterApp.appendChild( new Counter().render() );
</script>
  1. index.js
class Counter {
  constructor() {
    this.state = { number: 0 }
  }
  createDOMFromDOMString(domString) {
    let div = document.createElement('div');
    div.innerHTML = domString;
    return div.children[0];
  }
  add() {
    this.state = { number: this.state.number + 1 };
    let oldElement = this.domElement;
    let newElement = this.render();
    oldElement.parentElement.replaceChild(newElement, oldElement);
  }
  render() {
    // return `<button id="counter-app">${this.state.number}</button>`
    this.domElement = this.createDOMFromDOMString(
      `<button id="counter-app">${this.state.number}</button>`
    );
    this.domElement.addEventListener('click', this.add.bind(this));
    return this.domElement;
  }
}

3. setState同步

3.1. index.html

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

3.2. index.js

// 公共组件
class Component {
  constructor(props) {
    this.props = props;
  }
  createDOMFromDOMString(domString) {
    let div = document.createElement('div');
    div.innerHTML = domString;
    return div.children[0];
  }
  setState(partcialState) {
    this.state = Object.assign(this.state, partcialState);
    let oldElement = this.domElement;
    let newElement = this.renderElement();
    oldElement.parentElement.replaceChild(newElement, oldElement);
  }
  // 把一个DOM模板字符串转成真实的DOM元素
  renderElement() {
    let htmlString = this.render();
    this.domElement = this.createDOMFromDOMString(htmlString);
    this.domElement.addEventListener('click', this.add.bind(this));
    return this.domElement;
  }
  mount(container) {
    container.appendChild(this.renderElement());
  }
}
window.trigger = function (event, method) {
  event.target.component[method].call(event.target.component);
}
class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 }
  }
  add() {
    this.setState({ number: this.state.number + 1 });
  }
  render() {
    return `<button id="counter-app">${this.state.number}${this.props.name}</button>`;
  }
}

4. setState如果有异步

  1. 添加更新器 Updater, 每次更新存起来, 最后合并起来一起计算
  2. ? 默认非批量更新 : 批量更新方法
  3. 脏组件数组
  4. 批量更新脏组件
  5. 脏组件: 组件的状态和界面显示不一样了;
  6. 批量更新: true 更新器存起来 : false 直接更新
  7. 批量更新方法updateComponent
  8. 事件开启批量更新模式trigger
  9. 事件结束关闭更新模式 并 执行批量更新
  10. 0 0 2 3
  11. 事务 事件执行前开启 事件执行 事件完成后关闭
  12. 包装器有对象 对象中有初始化和结束两个方法 执行方法

4.1. index.js

// 事务
class Transaction {
  constructor(wrappers) {
    this.wrappers = wrappers; // {initialize,close}
  }
  perform(anyMethod) {
    this.wrappers.forEach(wrapper => wrapper.initialize());
    anyMethod.call();
    this.wrappers.forEach(wrapper => wrapper.close());
  }
}
// batchingStrategy.isBatchingUpdates batchedUpdates
let batchingStrategy = {
  isBatchingUpdates: false, // 默认是非批量更新模式
  dirtyComponents: [],
  batchedUpdates() { // 
    this.dirtyComponents.forEach(component => component.updateComponent());
  }
}
class Updater {
  constructor(component) {
    this.component = component;
    this.pendingStates = []; // 等待状态更新数组
  }
  addState(partcialState) {
    this.pendingStates.push(partcialState);
    batchingStrategy.isBatchingUpdates ? batchingStrategy.dirtyComponents.push(this.component) : this.component.updateComponent();
  }

}
// 公共组件
class Component {
  constructor(props) {
    this.props = props;
    this.$updater = new Updater(this);
  }
  createDOMFromDOMString() {
    //this;
    let htmlString = this.render();
    let div = document.createElement('div');
    div.innerHTML = htmlString;
    this.domElement = div.children[0];
    //让这个BUTTONDOM节点的component属性等于当前Counter组建的实例
    this.domElement.component = this;
    //this.domElement.addEventListener('click',this.add.bind(this));
    return this.domElement;
  }
  setState(partcialState) {
    // 对更新状态进行管理
    this.$updater.addState(partcialState);
  }
  updateComponent() {
    this.$updater.pendingStates.forEach(partcialState => Object.assign(this.state, partcialState));
    this.$updater.pendingStates.length = 0;
    let oldElement = this.domElement;
    let newElement = this.createDOMFromDOMString();
    oldElement.parentElement.replaceChild(newElement, oldElement);
  }
  // 把一个DOM模板字符串转成真实的DOM元素
  mount(container) {
    container.appendChild(this.createDOMFromDOMString());
  }
}
let transaction = new Transaction([
  {
    initialize() {
      batchingStrategy.isBatchingUpdates = true;
    },
    close() {
      batchingStrategy.isBatchingUpdates = false;
      batchingStrategy.batchedUpdates(); // 进行批量更新
    }
  }
])
window.trigger = function (event, method) {
  let component = event.target.component;//event.target=this.domElement
  transaction.perform(component[method].bind(component));
}
class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 }
  }
  add() {
    this.setState({ number: this.state.number + 1 });
    console.log(this.state.number);
    this.setState({ number: this.state.number + 1 });
    console.log(this.state.number);
    setTimeout(() => {
      this.setState({ number: this.state.number + 1 });
      console.log(this.state.number);
      this.setState({ number: this.state.number + 1 });
      console.log(this.state.number);
    }, 1000)
  }
  render() {
    return `<button onclick="trigger(event,'add')">${this.state.number}${this.props.name}</button>`;
  }
}

4.2. index.html

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