首先,webpack会接住react相关的loader对源代码进行编译,将jsx语法转成js。比如下面的例子。不过我们不会写这些编译后的对象,太不直观了。

const element = <h1 id='h1' className='h1'><span>哈哈哈</span></h1>

会被转成

const element = React.createElement(
"h1",
 {
      id: "h1",
      className: "h1"
    },
 React.createElement("span", null, "\u54C8\u54C8\u54C8")
); 
ReactDOM.render(element, document.getElementById('root'));

这是函数组件

import React from './VirtualDom/React';
import ReactDOM from './VirtualDom/ReactDom';

function Element2 () {
  return React.createElement("h1", {
    id: "h1",
    style: {
      color: 'red'
    },
    lassName: "h1"
  }, React.createElement("span", null, "\u54C8\u54C8\u54C8"), "我是函数组件,返回一个用来渲染的对象", React.createElement("div", null, React.createElement("ul", null, React.createElement("li", null, "1"), React.createElement("li", null, "2"), React.createElement("li", null, "3"), React.createElement("li", null, "4"))));
}
const Element22 = React.createElement(Element2, {name:'我', age: 100})

ReactDOM.render(Element33, document.getElementById('root'));

这是类组件

import React from './VirtualDom/React';
import ReactDOM from './VirtualDom/ReactDom';

class Element3 extends React.Component {
  render () {
    console.log(this.props)
    return React.createElement("h1", {
      id: "h1",
      style: {
        color: 'red'
      },
      lassName: "h1"
    }, React.createElement("span", null, "\u54C8\u54C8\u54C8"), "我是类组件,通过render返回一个用来渲染节点的对象", React.createElement("div", null, React.createElement("ul", null, React.createElement("li", null, "1"), React.createElement("li", null, "2"), React.createElement("li", null, "3"), React.createElement("li", null, "4"))));
  }
}
const Element33 = React.createElement(Element3, {name:'我', age: 100})

ReactDOM.render(Element33, document.getElementById('root'));

其实可以看到,基础的就以下几个方法
react.js

export default {
  Component,
  createElement
}

react-dom.js

// React.js
export default {
  render
}

我们将React.createElement方法执行生成的虚拟dom节点打印出来可以看到,就是一个对象,有几个重要的属性

{
props: {},
type:
}

props会保存我们传入的数据,还有children属性作为它的子节点,children属性的值分为三种:基础类型(比如数字、字符串、null)、react节点(就是个对象)、数组。
我们来看下实现。

react.js


const createElement = function (type, options, child) {
  let props = {}
  if (options && typeof options === 'object') {
    // for (let key in options) { // for in 会遍历到可枚举的属性,Object.keys不会。
    //   props[key] = options[key]
    // }
    // 这个存在的问题就是没有深拷贝,会影响原来的属性。
    Object.keys(options).forEach(key => {
      props[key] = options[key]
    })
  }

  if (child && arguments.length === 3) {
    props.children = child
  }

  if (arguments.length > 3) {
    props.children = Array.from(arguments).slice(2)
  }
  
  return {
    props,
    type
  }
}


class Component {
  static isReactComponent = true
  constructor (props) {
    this.props = props
  }
}

export default {
  createElement,
  Component
}

react-dom.js

const render = (virtualDom, parentNode) => {
  let childNode
  const props = virtualDom.props
  const type = virtualDom.type
  if (type && type.isReactComponent) {
    return render(new type(props).render(), parentNode)
  }

  if (typeof type === 'function') {
    return render(type(props), parentNode)
  }

  if (typeof virtualDom === 'string' || typeof virtualDom === 'number') {
    childNode = document.createTextNode(virtualDom)
  }

  if (typeof virtualDom.type === 'string') {
    childNode = document.createElement(virtualDom.type)
  }
 
  if (virtualDom && virtualDom.props && typeof virtualDom.props === 'object') {
    const props = virtualDom.props
    Object.keys(props).forEach((key) => {
      if (key === 'style') {
        const styleObj = props[key]
        const cssText = Object.keys(styleObj).map(styleName => {
          return `${styleName.replace(/[A-Z]/g, (childStr) => {
            return '-' + childStr.toLowerCase()
          })}:${styleObj[styleName]}`
        }).join(';');
        childNode.setAttribute(key, cssText) // 或者childNode.style.cssText = cssText
      } else if (key === 'children') {
        const children = Array.isArray(props.children) ? props.children : [props.children]
        children.forEach(child => {
          render(child, childNode)
        })
      } else if (key === 'className') {
        childNode.className = props.className
      } else {
        childNode.setAttribute(key, props[key])
      }
    })
  }
  parentNode.appendChild(childNode)
}

export default {
  render
}

做的事情其实就是将数据渲染到节点上面。符合react的介绍:构建用户界面的 JavaScript 库。

注意点

  1. 处理style属性的时候记得将驼峰转换成横杠,str.replace的用法
  2. 为什么会将其他属性挂载在dom节点上呢?感觉没什么意义。真实项目中貌似没有看到,区别在哪里?