react简单实现

75 阅读2分钟

react组件的渲染

1,生成虚拟dom对象:

function createElement(type, config, children) {//children永远都是数组
    let ref, key;
    if (config) {
        delete config.__source;
        delete config.__self;
        ref = config.ref;//后面会讲用来引用这个真实DOM元素
        key = config.key;//后面会讲是用来进行DOM-DIFF优化的,是用来唯一标识某个子元素的
        delete config.ref;
        delete config.key;
    }
    let props = { ...config };
    if (arguments.length > 3) {//如果参数的长度大于3,说明有多个儿子
        props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
    } else if (arguments.length === 3) {
        props.children = wrapToVdom(children);//字符串 数字 React元素
    }
    return {//这就是虚拟DOM,也就是React元素
        $$typeof: REACT_ELEMENT,//指的是元素的类型 现在没有什么作用
        type,//dom标签的类型 h1 h2 span div
        ref,
        key,
        props//className style children
    }

}

通过createElement封装一个虚拟dom,虚拟dom,有type,prop等属性

2,将虚拟dom更新到真实dom上:

function render(vdom, container) {
  mount(vdom, container);
}

//把虚拟dom转换为真实dom并且更新到对应的父节点上。
function mount(vdom, parentDOM) {
  //把虚拟DOM变成真实DOM
  let newDOM = createDOM(vdom);
  //把真实DOM追加到容器上
  if (newDOM) parentDOM.appendChild(newDOM);
  if (newDOM && newDOM.componentDidMount) newDOM.componentDidMount();
}

//根据虚拟dom创建真实的dom,并将虚拟dom的属性更新到真实dom上。
function createDOM(vdom) {
  let { type, props, ref } = vdom;
  let dom; //真实DOM
  if (type && type.$$typeof === REACT_MEMO) {
    return mountMemoComponent(vdom);
  } else if (type && type.$$typeof === REACT_PROVIDER) {
    return mountProviderComponent(vdom);
  } else if (type && type.$$typeof === REACT_CONTEXT) {
    return mountContextComponent(vdom);
  } else if (type && type.$$typeof === REACT_FORWARD_REF) {
    return mountForwardComponent(vdom);
  } else if (type === REACT_TEXT) {
    //创建文件节点
    dom = document.createTextNode(props.content);
  } else if (typeof type === 'function') {
    if (type.isReactComponent) {
      //说明这是一个类组件
      return mountClassComponent(vdom);
    } else {
      return mountFunctionComponent(vdom);
    }
  } else if (typeof type === 'string') {
    //创建DOM节点 span div p
    dom = document.createElement(type);
  }
  if (props) {
    //更新DOM的属性 后面我们会实现组件和页面的更新。
    updateProps(dom, {}, props);
    let children = props.children;
    //如果说children是一个React元素,也就是说也是个虚拟DOM
    if (typeof children === 'object' && children.type) {
      //把这个儿子这个虚拟DOM挂载到父节点DOM上
      props.children.mountIndex = 0;
      mount(children, dom);
    } else if (Array.isArray(children)) {
      reconcileChildren(children, dom);
    }
  }
  vdom.dom = dom; //在虚拟DOM挂载或者说放置一个dom属性指向此虚拟DOM对应的真实DOM
  if (ref) ref.current = dom;
  return dom;
}

function reconcileChildren(children, parentDOM) {
  children.forEach((childVdom, index) => {
    childVdom.mountIndex = index;
    mount(childVdom, parentDOM);
  });
}

不同类型的虚拟dom渲染为真实dom:

function mountMemoComponent(vdom) {
  let { type, props } = vdom; //type={$$typeof: REACT_MEMO,compare,type:FunctionCounter}
  let renderVdom = type.type(props);
  vdom.prevProps = props; //记录一下老的属性对象,方便更新的时候进行对比
  vdom.oldRenderVdom = renderVdom;
  if (!renderVdom) return null;
  return createDOM(renderVdom);
}
function mountProviderComponent(vdom) {
  let { type, props } = vdom; //type={$$typeof: REACT_PROVIDER, _context: context}
  let context = type._context; //{ $$typeof: REACT_CONTEXT, _currentValue: undefined }
  context._currentValue = props.value; //把属性中的value值赋给context._currentValue
  let renderVdom = props.children; //Provider而言它要渲染的其实 是它的儿子
  vdom.oldRenderVdom = renderVdom; //这一步是为了后面更新用的
  if (!renderVdom) return null;
  return createDOM(renderVdom);
}
function mountContextComponent(vdom) {
  let { type, props } = vdom; //type = {$$typeof: REACT_CONTEXT,_context: context}
  let context = type._context;
  let renderVdom = props.children(context._currentValue);
  vdom.oldRenderVdom = renderVdom;
  if (!renderVdom) return null;
  return createDOM(renderVdom);
}
function mountForwardComponent(vdom) {
  let { type, props, ref } = vdom;
  let renderVdom = type.render(props, ref);
  if (!renderVdom) return null;
  return createDOM(renderVdom);
}

//类组件的虚拟dom渲染为真实dom
function mountClassComponent(vdom) {
  let { type: ClassComponent, props, ref } = vdom;
  //把类组件的属性传递给了类组件的构造函数,
  let classInstance = new ClassComponent(props); //创建类组件的实例
  if (ClassComponent.contextType) {
    //把value值赋给classInstance的context属性
    classInstance.context = ClassComponent.contextType._currentValue;
  }
  vdom.classInstance = classInstance; //在虚拟DOm上挂载一个属性,指向类组件的实例
  if (ref) ref.current = classInstance;
  if (classInstance.componentWillMount) {
    classInstance.componentWillMount();
  }
  //可能是原生组件的虚拟DOM,也可能是类组件的的虚拟DOM,也可能是函数组件的虚拟DOM
  let renderVdom = classInstance.render();
  //在第一次挂载类组件的时候让类实例上添加一个oldRenderVdom=renderVdom
  vdom.oldRenderVdom = classInstance.oldRenderVdom = renderVdom;
  if (!renderVdom) return null;
  let dom = createDOM(renderVdom); //TODO
  if (classInstance.componentDidMount) {
    dom.componentDidMount = classInstance.componentDidMount.bind(classInstance);
  }
  return dom;
}

//函数组件的虚拟dom渲染为真实dom
function mountFunctionComponent(vdom) {
  let { type: functionComponent, props } = vdom;
  let renderVdom = functionComponent(props); //获取组件将要渲染的虚拟DOM
  vdom.oldRenderVdom = renderVdom;
  if (!renderVdom) return null;
  return createDOM(renderVdom);
}

更新真实dom的属性:

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 (/^on[A-Z].*/.test(key)) {
      ///说明是一个事件处理函数
      //dom.onclick = 函数
      //dom[key.toLowerCase()]=newProps[key];
      //以后我们不再把事件函数绑定到对应DOM上的,而是会进行事件委托,全部委托到document上
      addEvent(dom, key.toLowerCase(), newProps[key]);
    } else {
      dom[key] = newProps[key];
    }
  }
  for (let key in oldProps) {
    //如果说一个属性老的属性对象里有,新的属性没有,就需要删除
    if (!newProps.hasOwnProperty(key)) {
      dom[key] = null;
    }
  }
}

react组件的setState更新渲染:

React.Component实现:

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; //获取老的虚拟DOM
    let oldDOM = findDOM(oldRenderVdom); //获取老的真实DOM
    if (this.constructor.contextType) {
      this.context = this.constructor.contextType._currentValue;
    }
    if (this.constructor.getDerivedStateFromProps) {
      let newState = this.constructor.getDerivedStateFromProps(
        this.props,
        this.state
      );
      if (newState) {
        this.state = { ...this.state, ...newState };
      }
    }
    let newRenderVdom = this.render();
    let snapshot =
      this.getSnapshotBeforeUpdate && this.getSnapshotBeforeUpdate();
    //此处的逻辑其实就是DOM-DIFF的逻辑
    compareTwoVdom(oldDOM.parentNode, oldRenderVdom, newRenderVdom);
    this.oldRenderVdom = newRenderVdom;
    if (this.componentDidUpdate) {
      this.componentDidUpdate(this.props, this.state, snapshot);
    }
  }
}

调用组件实例的setState会触发组件的更新,会调用Updater的addState进行对应的操作。forceUpdate是组件根据当前的属性state和prop进行强制更新。 Updater实现:

class Updater {
  constructor(classInstance) {
    this.classInstance = classInstance;
    this.pendingStates = [];
  }
  addState(partialState) {
    //将所有的新状态放到一个待处理状态数组中。
    this.pendingStates.push(partialState); 
    this.emitUpdate();
  }
  //判断当前是否是批处理模式,然后根据具体模式去决定是否更新。
  emitUpdate(nextProps) {
    this.nextProps = nextProps;
    if (updateQueue.isBatchingUpdate) {
      //说明当前处于批量列新模式
      updateQueue.updaters.add(this); //会将update实例放进来
    } else {
      this.updateComponent(); //立即更新
    }
  }
  updateComponent() {
    let { nextProps, classInstance, pendingStates } = this;
    //如果属性更新的了,或者说状态更新了都会进行更新
    if (nextProps || pendingStates.length > 0) {
      shouldUpdate(classInstance, nextProps, this.getState());
    }
  }
  //基于老状态和pendingStates获取新状态
  getState() {
    let { classInstance, pendingStates } = this;
    let { state } = classInstance; //老状态
    pendingStates.forEach((nextState) => {
      if (typeof nextState === 'function') {
        nextState = nextState(state);
      }
      state = { ...state, ...nextState };
    });
    pendingStates.length = 0;
    return state;
  }
}

updateQueue存放的是updater实例。 判断更新是否拦截:

function shouldUpdate(classInstance, nextProps, nextState) {
  let willUpdate = true;
  //如果有shouldComponentUpdate方法,并且shouldComponentUpdate执行结果是false的话才会willUpdate=false
  if (
    classInstance.shouldComponentUpdate &&
    !classInstance.shouldComponentUpdate(nextProps, nextState)
  ) {
    willUpdate = false;
  }
  if (willUpdate && classInstance.componentWillUpdate) {
    classInstance.componentWillUpdate();
  }
  if (nextProps) {
    classInstance.props = nextProps;
  }
  classInstance.state = nextState; //不管要不要更新,赋值都 会执行
  if (willUpdate) {
    //如果要更新,就会更新界面,否则 不更新
    classInstance.forceUpdate();
  }
}

setState不是批量更新的情况下:首先将最新的状态存放在一个数组中,然后合并更新的状态和组件的老状态为一个最新的状态,然后根据组件的shouldComponentUpdate的返回值,去决定是否进行组件更新。不管组件是否重新渲染,组件的状态都要更新为最新状态。

合成事件与批量更新


import { updateQueue } from './component';
/**
 * 
 * @param {*} dom 要绑定事件的DOM元素  button
 * @param {*} eventType 事件类型 onclick
 * @param {*} handler 事件处理函数 handleClick
 */
export function addEvent(dom, eventType, handler) {
  //保证dom有store属性,值初始化是一个空对象 store是一个自定义属性
  /*   let store;
    if(dom.store){
      store = dom.store;
    }else{
      store = dom.store = {};
    } */
  let store = dom.store || (dom.store = {})
  //dom.store['onclick']=handleClick
  store[eventType] = handler;//虽然没有给每个子DOM绑定事件,但是事件处理函数还是保存在子DOM上的
  //从17开始,我们不再把事件委托给document.而是委托给容器了 div#root
  if (!document[eventType]) {
    document[eventType] = dispatchEvent;
  }
}


//合成事件的统一代理处理函数
function dispatchEvent(event) {
  let { target, type } = event;//target=button type =click
  let eventType = `on${type}`;//onclick
  let syntheticEvent = createSyntheticEvent(event);
  updateQueue.isBatchingUpdate = true;//事件函数执行前先设置批量更新模式为true
  //在此我们要模拟React事件的冒泡
  while (target) {
    let { store } = target;
    let handler = store && store[eventType]
    handler && handler(syntheticEvent);
    //在执行handler的过程中有可能会阻止冒泡
    if (syntheticEvent.isPropagationStopped) {
      break;
    }
    target = target.parentNode;
  }
  updateQueue.isBatchingUpdate = false;
  updateQueue.batchUpdate();
}
/**
 * 为什么React不会原生事件对象直接传给事件处理函数
 * 1.为了兼容性 像jquery一样,抹平浏览器的差异
 * @param {*} nativeEvent 
 * @returns 
 */
function createSyntheticEvent(nativeEvent) {
  let syntheticEvent = {};
  for (let key in nativeEvent) {//把原生事件上的属性拷贝到合成事件对象上去
    syntheticEvent[key] = nativeEvent[key];
  }
  syntheticEvent.nativeEvent = nativeEvent;
  syntheticEvent.isDefaultPrevented = false;
  syntheticEvent.isPropagationStopped = false;
  syntheticEvent.preventDefault = preventDefault;
  syntheticEvent.stopPropagation = stopPropagation;
  return syntheticEvent;
}

function preventDefault() {
  this.defaultPrevented = true;
  const event = this.nativeEvent;
  if (event.preventDefault) {
    event.preventDefault();
  } else {//IE
    event.returnValue = false;
  }
  this.isDefaultPrevented = true;
}

function stopPropagation() {
  const event = this.nativeEvent;
  if (event.stopPropagation) {
    event.stopPropagation();
  } else {//IE
    event.cancelBubble = true;
  }
  this.isPropagationStopped = true;
}

dispatchEvent是将原生事件进行封装成合成事件对象,然后传给对应的事件处理函数,这样我们拿到的事件对象就是合成事件对象,在合成事件触发前后,可以设置批量模式,并进行批量更新。