react

83 阅读2分钟

前提

React 17版本之前,都是利用createElement函数生成虚拟dom对象

jsx语法由babel解析后,然后通过createElement得到一个虚拟dom对象例如

babel解析是在webpack打包编译的时候解析的,然后将解析的代码在浏览器执行createElement函数的时候生成虚拟dom对象

<div className="test">zzzz<span>dddd</span></div>

---> babel解析  ----------------->

React.createElement("div", {
   className: "test"
}, "zzzz", /*#__PURE__*/React.createElement("span", null, "dddd"));


----> React.createElement转为虚拟dom对象

const vdom = {
   props: {
     className: 'test',
     children: "zzzzs",
     // child可能是字符串也可能是数组
     // children: ["zzzzs",{
     //   type: 'span',
     //   props: {
            // 属性
     //   }
     // }]
   },
   type: 'div'
}

React 17

实现react.CreateElement


function createElement (type, config, children) {
  if(config) {
    delete config.__self
    delete config.__source
  }
  const props = {...config};
  // 有多个子节点的时候
  if (arguments.length > 3) {
    // children 是一个数组
    children = Array.prototype.slice.call(arguments, 2)
  }

  props.children = children;

  return {
    type,
    props,
  }
}


const React = {
  createElement,
}

export default React;


react

react-dom

有一个render方法,将虚拟dom转为真实dom,然后挂载到容器上

render方法实现

根据虚拟dom创建真实dom

把虚拟dom的属性挂在到真实dom上

把虚拟dom的子节点也变成真实dom挂在到自己的dom上

将dom挂载到容器上


// 传入一个虚拟dom对象.和一个容器进行渲染
function render(vdom, container) {
  // 将虚拟dom转为真实dom,进行渲染
  const dom = createDom(vdom)
  container.appendChild(dom)
}

// 将虚拟dom转为真实dom
function createDom(vdom) {
  // 如果是数字或者字符串,直接返回一个真实到文本节点
  if(typeof vdom === 'string' || typeof vdom === 'number') {
    return document.createTextNode(vdom)
  }

  const { type, props } = vdom;

  // 创建真实dom
  const dom = document.createElement(type);

  // 把虚拟dom属性更新到真实dom上,
  updateProps(dom, props)
  // 把虚拟dom的儿子也变成真实dom挂在到自己的dom上。
  if (typeof props.children === 'string' || typeof props.children === 'number') {
    dom.textContent = props.children
  } else if (typeof props.children === 'object' && props.children.type) {
    // 只是有一个子元素,并且儿子是虚拟dom,把儿子变成真实dom插入到dom上
    render(props.children, dom)
  } else if (Array.isArray(props.children)) {
    // 如果children是一个数组的时候
    updateArryChild(props.children, dom)
  } else {
    document.textContent =  props.children ? props.children.toString() : ""
  }

   // 把真实dom作为一个dom属性放到虚拟dom上,为以后更新准备
  //  vdom.dom = dom;

  return dom;

}

// 将虚拟dom的props属性挂在真实dom 上
/**
 * 
 * @param {*} dom 真实dom
 * @param {*} props  虚拟dom的属性
 */
function updateProps(dom, props) {
  for (let key in props) {
    if (key === 'children') continue;
    if (key === 'style') {
      let styleObj = props.style;
      for (let attr in styleObj) {
        dom.style[attr] = styleObj[attr]
      }
    } else {
      dom[key] = props[key]
    }
  }
}

/**
 * 
 * @param {*} childVdom 虚拟节点的儿子 child是一个数组的时候
 * @param {*} parantdom 父节点
 */
function updateArryChild(childVdom, parantdom) {
  for (let i = 0; i < childVdom.length; i++) {
    let childVdoms = childVdom[i]
    // 将虚拟节点挂在父节点上
    render(childVdoms, parantdom)
  }
}






const ReactDOM = {
  render,
}

export default ReactDOM;