React.createElement() React.Component() ReactDom.render() 源码解析

399 阅读3分钟

Vitual Dom

  1. 概念 用 JavaScript 对象表示 DOM 信息和结构,当状态变更更的时候,重新渲染这个 JavaScript 的对象结构。这个JavaScript 对象称为virtual dom。

  2. 原因 DOM操作很慢,轻微的操作都可能导致页面重新排版,非常耗性能。相对于DOM对象,js对象处理起来更快,而且更简单。通过diff算法对比新旧vdom之间的差异,可以批量的、最小化的执行dom操作,从⽽提⾼性能。

3.在react中的使用

react中用JSX语法描述视图,babel-loader将JSX转译为React.createElement(...)形式,该函数将生成vdom来描述真实dom。将来如果状态变化,vdom将作出相应变化,再通过diff算法对比新⽼vdom区别从⽽而做出最终dom操作。

JSX形式:

class HelloMessage extends React.Component {
  render() {
    return (
    <div key="0">
      <div className={hello} key="1" >
        Hello {this.props.name}
      </div>
    </div>
    );
  }
}

ReactDOM.render(
  <HelloMessage name="Taylor" />,
  document.getElementById('hello-example')
);

babel-loader编译后:分别传入标签类型,props,常量,变量,嵌套的children~

class HelloMessage extends React.Component {
  render() {
    return React.createElement(
      "div",
      { key: "0" },
      React.createElement(
        "div",
        { className: hello, key: "1" },
        "Hello ",
        this.props.name
      )
    );
  }
}

ReactDOM.render(React.createElement(HelloMessage, { name: "Taylor" }), document.getElementById('hello-example'));

4. React.createElement()的作用:将传入的元素转换成虚拟DOM

核心API

  1. React.createElement:创建虚拟DOM (1)节点类型:⽂文本节点,HTML标签节点,function组件,class组件,fragment,其他如portal等节点。

在外部index.js文件中定义各种节点类型:

// import React from "react";
// import ReactDOM from "react-dom";
import React from "./kreact/";
import ReactDOM from "./kreact/ReactDOM";
import Component from "./kreact/Component";
import "./index.css";

// jsx=>createElement(生成element,就是我们需要的虚拟dom)=>render(vnode->node, 再把node渲染到container)

function FunctionComponent({name}) {
  return (
    <div className="border function">
      hello, {name}
      <button onClick={() => console.log("omg")}>click</button>
    </div>
  );
}


class ClassComponent extends Component {
  render() {
    const {name} = this.props;
    return <div className="border function">hello, {name}</div>;
  }
}

const jsx = (
  <div className="border">
    <p>这是一个文本</p>
    <a href="https://kaikeba.com/">开课吧</a>
    <div className="border">
      <h5>hello</h5>
    </div>
    <FunctionComponent name="function" />
    <ClassComponent name="class" />
    <>
      <h5>文本1</h5>
      <h5>文本2</h5>
    </>

    {[1, 2, 3].map(item => {
      return (
        <div className="border" key={item}>
          <p>{item}</p>
          <p>{item}</p>
        </div>
      );
    })}
  </div>
);

// element, container
// vnode->node , 把node渲染更新到container
ReactDOM.render(jsx, document.getElementById("root"));

在内部index.js中定义createElement

function createElement(type, props, ...children) {
  // 删除原生
  if (props) {
    delete props.__source;
    delete props.__self;
  }

  return {
    type: type,
    props: {
      ...props,
      //!这里的处理与源码稍有不同,源里的话,只有一个元素,children是对象,多于一个的时候,是数组
      children: children.map(child =>
        typeof child === "object" ? child : createTextNode(child)
      )
    }
  };
}

// p标签TEXT类型要单独处理生成树
function createTextNode(text) {
  return {
    type: "TEXT",
    props: {
      children: [],
      nodeValue: text
    }
  };
}
export default {
  createElement
};

  1. React.Component:实现⾃自定义组件,被其他class组件继承
export default class Component {
  static isReactComponent = {};
  constructor(props) {
    this.props = props;
  } 
}
  1. ReactDOM.render:渲染真实DOM,将虚拟dom转换为真实dom,将真实dom挂载到container标签下。

在ReactDom.js定义render函数。

function render(vnode, container) {
  // console.log("vnode", vnode);
  // 虚拟dom转换为真实dom
  const node = createNode(vnode);
  //把node更新到container
  container.appendChild(node);
}

// 根据vnode,创建一个node
function createNode(vnode) {
  const {type, props} = vnode;
  let node;
  // 根据node的类型调用document.createXXX()创建节点,function和class类型要单独处理
  if (typeof type === "function") {
    node = type.isReactComponent ? 
        updateClassComponent(vnode)
      : updateFunctionComponent(vnode);
  } else if (type === "TEXT") {
    node = document.createTextNode("");
  } else if (type) {
    node = document.createElement(type);
  } else {
    node = document.createDocumentFragment();
  }
  // 将属性添加进节点里
  updateNode(node, props);
  // 递归将children生成真实节点
  reconcilerChildren(props.children, node);
  return node;
}

function reconcilerChildren(children, node) {
  for (let i = 0; i < children.length; i++) {
    let child = children[i];
    // 遍历 创建元素
    // 判读children[i]类型
    if (Array.isArray(child)) {
      for (let j = 0; j < child.length; j++) {
        // 递归调用render
        render(child[j], node);
      }
    } else {
      // 递归调用render
      render(children[i], node);
    }
  }
}

// 更新节点上属性,如className、nodeValue等
function updateNode(node, nextVal) {
  Object.keys(nextVal)
    .filter(k => k !== "children")
    .forEach(k => {
      if (k.slice(0, 2) === "on") {
        // 以on开头,就认为是一个事件,源码处理复杂一些,
        let eventName = k.slice(2).toLocaleLowerCase();
        node.addEventListener(eventName, nextVal[k]);
      } else {
        node[k] = nextVal[k];
      }
    });
}

// function组件,返回node
function updateFunctionComponent(vnode) {
  const {type, props} = vnode;
  // 运行函数返回虚拟dom
  const vvnode = type(props);
  const node = createNode(vvnode);
  return node;
}

function updateClassComponent(vnode) {
  const {type, props} = vnode;
  //实例化class
  const cmp = new type(props); 
  // 调用class中的render 返回虚拟dom
  const vvnode = cmp.render(); 
  const node = createNode(vvnode);
  return node;
}
export default {
  render
};