setState实现原理

119 阅读3分钟

react实现原理

合成事件原理

事件合成的好处?

  1. 批量异步更新

在事件、生命周期开始的时候 updateQueue.isBatchingUpdate = true setState执行时只追加更新内容,但不进行更新 在事件、生命周期结束的时候 updateQueue.isBatchingUpdate = false 结束时将更新器全部进行批量更新

React17,所有的事件都委托到document

React17,所有的事件都委托到容器上 这是因为之前如果页面中有多个React应用,事件绑定容易冲突;

<div id="container"></div>

<div id="container1"></div> // ReactDOM.render(<h1>, container1)
<div id="container2"></div> // ReactDOM.render(<h2>, container2)
  1. 对浏览器进行兼容性处理

把不同浏览器的API不一致的,把不同的事件对象做成一个标准化的事件对象,提供标准的API供用户使用

例如

function stopPropagation(event){
  if(!event){ // IE下
    window.event.cancelBubble = true;
  }
  if(event.stopPropagation){ // w3c标准
    event.stopPropagation()
  }
}

原理

event.js

import { updateQueue } from './Component'

export function addEvent (dom, eventType, eventHandler) {
  let store;
  if (dom._store) {
    store = dom._store;
  } else {
    dom._store = store = {};
  }
  // store.onclick = handleClick;
  store[eventType] = eventHandler;
  // document.onclick = dispatchEvent;
  if (!document[eventType]) {
    document[eventType] = dispatchEvent
  }
}

/**
 * 不管点什么按钮,触发什么事件,最终都会执行dispatchEvent方法
 * 在合成事件的处理函数中,状态的更新是批量的
 * @param {*} event 原生的事件对象,不同的浏览器可能不一样
 */
function dispatchEvent (event) {
  // type: 'click' type: 'click'
  const { target, type } = event;
  const eventType = `on${type}`;
  // 先把批量更新的标识改为true
  updateQueue.isBatchingUpdate = true;
  let syntheticEvent = createSyntheticEvent(event);
  let currentTarget = target;
  // 模拟冒泡
  while (currentTarget) {
    // 获取事件源DOM对象上的store属性
    const { _store } = currentTarget;
    const eventHandler = _store && _store[eventType];
    if (eventHandler) {
      syntheticEvent.target = target;
      syntheticEvent.currentTarget = currentTarget;
      eventHandler.call(currentTarget, syntheticEvent);
    }
    currentTarget = currentTarget.parentNode;
  }
  updateQueue.isBatchingUpdate = false;
  updateQueue.batchUpdate(); // 真正的更新
}

function createSyntheticEvent (nativeEvent) {
  const syntheticEvent = { nativeEvent };
  for (const key in nativeEvent) {
    syntheticEvent[key] = nativeEvent[key];
  }
  // 此处会有一些兼容性处理
  return syntheticEvent;
}
import { addEvent } from './event'
/**
 * 把新的属性更新到真实DOM上
 * @param {*} dom 真实DOM
 * @param {*} oldProps 旧的属性对象
 * @param {*} newProps 新的属性对象
 */
function updateProps (dom, oldProps, newProps) {
  for (let key in newProps) {
    if (key === 'children') {
      continue; // 此处忽略子节点的处理
    } else if (key === 'style') {
      let styleObj = newProps[key];
      for (let attr in styleObj) {
        dom.style[attr] = styleObj[attr];
      }
    } else if (key.startsWith('on')) {
      addEvent(dom, key.toLocaleLowerCase(), newProps[key]); // => 合成事件
    } else {
      dom[key] = newProps[key]; // className
    }

  }
}

setState实现原理

setState什么时候是同步的,什么时候是异步的?

  • 在React能管辖的地方就是批量异步的。比如事件处理函数,比如生命周期函数
  • 在React管不到的地方,就是同步的。比如setTimeout setInterval 原生的事件处理函数

实现原理

import { compareTwoVdom, findDOM } from './react-dom'

// 更新队列
export let updateQueue = {
  isBatchingUpdate: false, // 默认不批量更新,同步的
  updaters: [], // 更新器的数组
  batchUpdate () { // 批量更新
    for (const updater of updateQueue.updaters) {
      updater.updateComponent()
    }
    updateQueue.updaters.length = 0;
    updateQueue.isBatchingUpdate = false;
  }
}
class Updater {
  constructor(classInstance) {
    this.classInstance = classInstance;
    this.pendingStates = []; // 等待生效的数组
  }
  addState (partialState) {
    this.pendingStates.push(partialState)
    this.emitUpdate(); // 触发更新
  }
  emitUpdate () {
    // 有可能是批量异步更新,也有可能是同步更新
    if (updateQueue.isBatchingUpdate) { // 批量异步更新
      updateQueue.updaters.push(this) // 不刷新视图了,把当前的updater放到更新队列中
    } else { // 同步更新
      this.updateComponent();
    }
  }
  updateComponent () {
    const { classInstance, pendingStates } = this;
    if (pendingStates.length > 0) {
      sholdUpdate(classInstance, this.getState());
    }
  }
  getState () {
    const { classInstance, pendingStates } = this;
    let { state } = classInstance;
    pendingStates.forEach((partialState) => {
      if (typeof partialState === 'function') {
        partialState = partialState(state);
      }
      state = { ...state, ...partialState }
    })
    pendingStates.length = 0;
    return state;
  }
}

function sholdUpdate (classInstance, nextState) {
  classInstance.state = nextState;
  classInstance.forceUpdate();
}
class Component {
  static isReactComponent = true
  constructor(props) {
    this.props = props;
    this.state = {};
    this.updater = new Updater(this);
  }
  setState (partialState) {
    this.updater.addState(partialState);
  }
  forceUpdate () {
    let oldRenderVdom = this.oldRenderVdom;
    // let oldDOM = oldRenderVdom.dom;
    let oldDOM = findDOM(oldRenderVdom);
    // 基于新的属性和状态,计算新的虚拟DOM
    let newRenderVdom = this.render()
    compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);

    this.oldRenderVdom = newRenderVdom;
  }
}

export default Component

例子

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { number: 0, age: 10 }
  }
  handleClick = (event) => {
    // setState参数是新的状态对象,这个新状态对象会合并到老状态对象上。
    // 老状态没有的属性会添加,老状态有的属性会被覆盖

    /* this.setState({ number: this.state.number + 1 })
    console.log(this.state.number); // => 0
    this.setState({ number: this.state.number + 1 })
    console.log(this.state.number); // => 0

    Promise.resolve().then(() => {
      this.setState({ number: this.state.number + 1 })
      console.log(this.state.number); // => 2
      this.setState({ number: this.state.number + 1 })
      console.log(this.state.number); // => 3
    })
     */

    this.setState({ number: this.state.number + 1 })
    console.log(this.state.number); // => 0
    this.setState({ number: this.state.number + 1 })
    console.log(this.state.number); // => 0

    setTimeout(() => {
      this.setState({ number: this.state.number + 1 })
      console.log(this.state.number); // => 2
      this.setState({ number: this.state.number + 1 })
      console.log(this.state.number); // => 3
    }, 1000);
    

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

    /* 
    // 如果直接修改state的话,this.state确实改变了,但视图不会更新
    this.state.number += 1; 
    */
    

    // Cannot add property title, object is not extensible
    // this.props.title = '新标题';
  }
  render () {
    return (
      <div>
        <p>{this.props.title}</p>
        <p>number:{this.state.number}</p>
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}