手写React-组件更新

194 阅读3分钟
整体思路
  1. diff 方法中判断要更新的virtual dom是否是组件。
  2. 若是组件,继续判断该组件与更新前的组件是否为同一个组件。
    • 若不是,则直接渲染新组件render返回的视图。
    • 若是同一个组件,则进行组件更新操作。
  3. 组件更新的过程,其实就是获取最新的props,然后通过组件的render得到最新的virtual dom,最后通过 diff 找出与原有的差异,并更新到视图上。
  4. 组件更新的过程中,组件实例需要在不同阶段调用生命周期函数。
代码实现
  1. diff 方法中增加处理virtual dom是组件的逻辑分支,交由diffComponent 统一处理。diffComponent 的四个参数分别为:
    • vdom: 组件本身的virtual dom,通过它可获得最新的props。
    • oldComponent: 要更新的组件的实例对象,通过它可
      • 调用组件的生命周期函数
      • 更新组件的props
      • 调用render获取最新的视图virtual dom
    • oldDOM: 要更新的DOM对象。可通过它获取原来的virtual dom,与新的virtual dom比对diff后,实现DOM的最小化更新。
    • container: 容器,在渲染的是一个新组件的情况下,直接将新组件视图内容渲染到容器内
export default function diff(vdom, container, oldDOM) {
  const oldVDom = oldDOM && oldDOM._vdom;
  // 老
  const oldComponent = oldVDom?.component;

  if (!oldDom) {
    // 处理挂载阶段...
  } else if (oldVDom && vdom.type === oldVDom.type) {
    // 处理非组件virtual dom(相同type)...
  } else if (vdom.type !== oldVDom.type && !isComponent(vdom)) {
    // 处理非组件virtual dom(不同type)...
  } else if (isComponent(vdom)) {
    // 处理组件virtual dom
    diffComponent(vdom, oldComponent, oldDOM, container)
  }
}
export default function diffComponent (vdom, oldComponent, oldDOM, container) {
  // ...
}
  1. diffComponent 中通过 isSameComponent 判断要更新的组件与原有组件是否是同一个。 若不是同个组件,也就是说,渲染的是一个全新组件,那么直接渲染该组件即可。
/**
 * 判断新旧组件是否相同
 * @param {*} vdom 新的virtual dom(其type属性,指向组件类)
 * @param {*} oldComponent 老的组件实例
 * @returns 
 */
function isSameComponent (vdom, oldComponent) {
  // virtual dom.type === 老组件的构造器
  return oldComponent && oldComponent.constructor  === vdom.type;
}

export default function diffComponent (vdom, oldComponent, oldDOM, container) {
  if (isSameComponent(vdom, oldComponent)) {
    // 在原组件基础上的更新...
  } else {
    // 渲染一个全新组件
    mountElement(vdom, container, oldDOM)
  }
}

需要补充的是,在视图更新时,原来的DOM是需要在 mountNativeElement 中手动移除的

export default function mountNativeElement(vdom, container, oldDOM) {
  if (oldDOM) {
    unMountNode(oldDOM);
  }

  // 使用DOM方法挂载元素...
}
  1. 若是原组件的更新,则在 diffComponent 内调用 updateComponent 方法进行处理。 同时,组件实例需要具备调用生命周期函数的能力,可以预先定义在父类Component上,子类可以选择覆盖与否。
export default function diffComponent (vdom, oldComponent, oldDOM, container) {
  if (isSameComponent(vdom, oldComponent)) {
    updateComponent(virtualDOM, oldComponent, oldDOM, container)
  } 
	// ...
}
export default class Component {

  // 生命周期函数
  componentWillMount() {}
  componentDidMount() {}
  componentWillReceiveProps(nextProps) {}
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps != this.props || nextState != this.state;
  }
  componentWillUpdate(nextProps, nextState) {}
  componentDidUpdate(prevProps, preState) {}
  componentWillUnmount() {}

	// 其他方法...
}
  1. updateComponent 中,主要进行:
    • 生命周期函数的调用
    • 获取最新的render virtual dom并通过调用 diff 进行更新操作。
export default function updateComponent (vdom, oldComponent, oldDOM, container) {
  const nextProps = vdom.props;
  const nextState = oldComponent.state;
	const prevProps = oldComponent.props;
  const shouldComponentUpdate = oldComponent.shouldComponentUpdate(nextProps, nextState);

  oldComponent.componentWillReceiveProps(nextProps);
  if (shouldComponentUpdate) {
    oldComponent.componentWillUpdate(nextProps, nextState);
    oldComponent.updateProps(nextProps);
    const virtualDOM = oldComponent.render();
    virtualDOM.component = oldComponent;
    diff(virtualDOM, container, oldDOM);
    oldComponent.componentDidUpdate(prevProps);
  }
}

Component 需要新增一个 updateComponent 方法

export default class Component {	
  updateProps(props) {
    this.props = props
  }
	
	// ...
}