createElement与render

144 阅读3分钟

create-react-app

  • 脚手架需要node14及以上版本
  • npm i create-react-app -g
  • create-react-app 项目名

createElement

  • jsx是一种格式,表现为函数返回dom
  • 我们日常会使用jsx来写react代码,但是react本身是无法识别jsx的,所以会用babel将jsx转换为React.createElement,然后react在浏览器里面跑的时候会执行React.createElement函数,返回react元素
  • jsx
  <h1 className="title" style={{ color: "red" }}>哈哈</h1>
  • 转为React.createElement函数
//如果有多个子集的话传递方式为React.createElement(标签名, {}, dom1, dom2....)
React.createElement('h1',
{
    className: 'title',
    style: {color: 'red'}
},'哈哈')
  • 在浏览器中的React元素
  • 实际就是React.createElement函数的执行结果
createElement有新老两版
  • 老版本:写项目的时候,通常会发现,明明当前文件没用到React,但不引入React却会报错
    • 因为它编译完为React.createElement,用到了React的createElement方法,那么没有React就会导致方法找不到
  • 开启老版本的方法:
    • npm i cross-env --save
    • 在start原本命令前里添加cross-env DISABLE_NEW_JSX_TRANSFORM=true
React.createElemet('h1', null, '哈哈')
  • 新版本:内部自动引入jsx包,转换方法为jsx,用法一致,好处是不用哪里都引入React了
  • 17以后就是新版本
import jsx from 'jsx';
jsx('h1', null, '哈哈')
模拟一下react数据结构
//element.js 变量js
//react元素
export const REACT_ELEMENT = Symbol("react.element");


//react.js
import { REACT_ELEMENT } from "./element";
function createElement(type, config, children) {
  //children可能有多个,
  let ref; //用来获取真实Dom元素
  let key; //进行DomDiff的
  if (config) {
    delete config.__source;
    delete config.__self;
    ref = config.ref;
    key = config.key;
    delete config.ref;
    delete config.key;
  }
  let props = { ...config };
  if (arguments.length > 3) {
    //如果length大于3,代表有多个儿子
    props.children = Array.prototype.slice.call(arguments, 2);
  } else {
    //如果只有一个儿子或没有,那么children是对象或字符串,或undefined
    props.children = children;
  }
  return {
    $$typeof: REACT_ELEMENT, //表示是一个react元素
    type, //虚拟dom类型
    ref,
    key,
    props, //属性对象
  };
}
const React = {
  createElement,
};
export default React;

  • 现在返回大致是一样的了,但是我们做domDiff的时候还得判断当前的dom是字符串或数字或对象,有点麻烦,我们直接把字符串和数组转为对象(源码里没有这样处理,这里只是省事儿)
//utils.js
export const REACT_TEXT = Symbol("REACT_TEXT");
export function wrapToVdom(element) {
  return typeof element === "string" || typeof element === "number"
    ? { type: REACT_TEXT, props: element }
    : element;
}

//react.js
....
  //把每个children处理了一层,如果是数字或文字,变为对象
  if (arguments.length > 3) {
    props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
  } else {
    props.children = wrapToVdom(children);
  }
....

ReactDom简单实现

render

  • 我们先实现render,保证渲染的正常
    • 事件的绑定等暂没实现
//react-dom.js
import { REACT_TEXT } from "./utils";

/**
 * 把虚拟dom变成真实dom,插入到容器中
 * @param {*} vdom 虚拟dom
 * @param {*} container 容器
 */
function render(vdom, container) {
  mount(vdom, container);
}
/**
 * 被render包了一层,mount语义化好点
 * @param {*} vdom
 * @param {*} container
 */
function mount(vdom, container) {
  let newDom = createDom(vdom);
  container.appendChild(newDom);
}
/**
 * 创建真实dom
 * @param {*} vdom
 * @returns dom
 */
function createDom(vdom) {
  let { type, props } = vdom;
  let dom;
  //如果是REACT_TEXT代表是文本
  if (type === REACT_TEXT) {
    dom = document.createTextNode(props); //创建文本节点
  } else {
    dom = document.createElement(type);
  }
  if (props) {
    //更新新旧值,进行diff
    updateProps(dom, {}, props);
    //独生子为{},那么在这处理
    if (typeof props.children === "object" && props.children.type) {
      mount(props.children, dom);
    } else if (Array.isArray(props.children)) {
      //如果是数组代表有多个
      reconcileChildren(props.children, dom);
    }
  }
  return dom;
}
/**
 * 将多个children渲染到一个父节点里
 * @param {Array} children
 * @param {*} prarentDom
 */
function reconcileChildren(children, prarentDom) {
  for (let i = 0; i < children.length; i++) {
    mount(children[i], prarentDom);
  }
}
/**
 * 新旧值比对,进行diff
 * @param {*} dom
 * @param {*} oldProps
 * @param {*} newProps
 */
function updateProps(dom, oldProps = {}, newProps = {}) {
  for (let key in newProps) {
    if (key === "children") {
      //如果是children,不处理
      continue;
    } else if (key === "style") {
      //如果是样式,加样式
      let styleObj = newProps["style"];
      for (let attr in styleObj) {
        dom.style[attr] = styleObj[attr];
      }
    } else {
      //className....
      dom[key] = newProps[key];
    }
  }
  for (let key in oldProps) {
    //如果新值上不存在,代表不要了,那么我们把它删掉
    if (!newProps.hasOwnProperty(key)) {
      oldProps[key] = null;
    }
  }
}
const ReactDom = { render };
export default ReactDom;

使用我们的react与react-dom
  • 正常渲染
import React from "./react";
import ReactDOM from "./react-dom";
let element = (
  <h1
    className="title"
    style={{ color: "red" }}
  >
    哈哈
    <span>我是span</span>
  </h1>
);
ReactDOM.render(element, document.getElementById("root"));