React1 分钟阅读

React JSX

React.createElement 的返回结果然后交由 ReactDOM 去处理

李李顺2022 · 08 · 16

JSX 渲染

  • 是一种 JS 和 HTML 混合的语法,将组建的结构、数据甚至是样式都聚合在一起的写法

  • JSX 其实只是一种语法糖,最终会通过babeljs转译成React.createElement语法

    js
    // 老版本需要手动引入React
    import React from "react";
    let ele = <h1>ele</h1>;
    // 编译后的结果是 React.createElement(h1, null, 'ele')
    js
    // React17后新版本
    let ele = <h1>ele</h1>;
    // 编译后会自动引入jsx
    // jsx('h1', null, 'ele')
  • React.createElement会返回一个 React 元素

  • React 元素事实上是普通的 JS 对象,用来描述你在屏幕上看到的内容

  • ReactDOM来确保浏览器中的真实 DOM 数据和 React 元素保持一致

JSX

text
<h1 className="title" style={{color:'red'}}>hello</h1>

转译后的代码: 是由 babel 去处理的

text
React.createElement("h1", {
  className: "title",
  style: {
    color: 'red'
  }
}, "hello");

返回的结果
React.createElement 的返回结果然后交由 ReactDOM 去处理

text
{
  $$typeof: Symbol(react.element), // 元素类型
  type:'h1', // 标签
  props:{ // 属性
    className: "title",
    style: {
      color: 'red'
    }
  },
  children:"hello"
}

JSX 的实现

src/index.js

js
import React from "./lib/react";
// import React from 'react';
import ReactDOM from "./lib/react-dom";
let element = (
  <h1 id="test" className="h1" style={{ color: "red" }}>
    hello<span> world</span>{" "}
  </h1>
);
// babel会将其变成React.createElement('h1', null, 'hello')

ReactDOM.render(element, document.getElementById("root"));

lib/react.js

js
import { REACT_ELEMENT } from "./element-types";
import { wrapToVdom } from "./utils";

// 把babel转换后的结果转成ReactDOM
function createElement(type, config, children) {
  let key = config.key;
  let ref = config.ref;
  if (config) {
    delete config.__source;
    delete config._owner;
    delete config.__self;
    delete config.key;
    delete config.ref;
  }
  let props = { ...config };
  if (arguments.length > 3) {
    // 如果参数大于3,说明子节点是数组
    props.children = Array.prototype.slice.call(arguments, 2).map(wrapToVdom);
  } else {
    props.children = wrapToVdom(children);
  }
  return {
    $$typeof: REACT_ELEMENT, // 表示是一个虚拟DOM,或者说是React元素
    key,
    ref,
    type, // 虚拟DOM的类型
    props, // 像事件,id,属性,class,style等都在这里面
  };
}
const React = {
  createElement,
};
export default React;

lib/element-types.js

js
export const REACT_ELEMENT = Symbol("react.element"); // React元素
export const REACT_TEXT = Symbol("react.text"); // 纯文本

lib/utils.js

js
import { REACT_TEXT } from "./element-types";

// 将纯本文包装成一个对象
export const wrapToVdom = (element) => {
  const type = typeof element;
  return ["string", "number"].includes(type)
    ? {
        type: REACT_TEXT,
        props: element,
      }
    : element;
};

lib/react-dom.js

js
import { REACT_ELEMENT, REACT_TEXT } from "./element-types";

/**
 * 要把虚拟DOM转换成真实DOM并且挂载到容器中
 * @param {*} vdom 虚拟DOM
 * @param {*} container 容器
 */
function render(vdom, container) {
  mount(vdom, container);
}
function mount(vdom, container) {
  let newDOM = createDOM(vdom);
  container.appendChild(newDOM);
}
function createDOM(vdom) {
  const { props, type } = vdom;
  let dom; // 真实的DOM节点
  if (type === REACT_TEXT) {
    dom = document.createTextNode(props); // props是一个字符串
  } else {
    dom = document.createElement(type);
  }
  if (props) {
    // 如果props存在,更新属性
    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;
}

function reconcileChildren(children, dom) {
  children.forEach((child) => {
    mount(child, dom);
  });
}

function updateProps(dom, oldProps = {}, newProps) {
  for (let key in newProps) {
    // 先遍历新的属性
    if (key === "children") continue;
    if (key === "style") {
      let styleObj = newProps[key];
      for (let attr in styleObj) {
        dom.style[attr] = styleObj[attr];
      }
    } else {
      // dom.className = 'title'
      dom[key] = newProps[key];
    }
  }
  // 再遍历老属性,如果新属性中没有直接删除掉
  for (let key in oldProps) {
    if (!newProps.hasOwnProperty(key)) {
      dom[key] = null;
    }
  }
}

const ReactDOM = {
  render,
};
export default ReactDOM;

标签 · TAGS

评论 · COMMENTS

评论由 Giscus 承载 · 需要 GitHub 账号登录

站点不保存任何读者账号与凭证

未配置:在 lib/site.config.ts 填入 giscus 的 repo / repoId / category / categoryId