mini-react起步

57 阅读2分钟

第一节:实现最简单 mini-react

本节课使用js创建一个div元素,通过改造创建div元素属性、内容的方式来体验现在主流前端框架的核心思想,虚拟dom(vdom)

使用js创建元素

很简单的实现了:

const app = document.createElement("div");
app.id = "app";

document.querySelector("#root").append(app);

const textNode = document.createTextNode("");
textNode.nodeValue = "app";

app.append(textNode);

然后我们开始改造。

改造1. 使用对象来描述元素 el

const textEl = {
  type: "TEXT_ELEMENT",
  props: {
    nodeValue: "app",
    children: [],
  },
};
​
const el = {
  type: "div",
  props: {
    id: "app",
    children: [textEl],
  },
};

然后使用对象中的属性代替直接赋值

const app = document.createElement(el.type);
app.id = el.props.id;

document.querySelector("#root").append(app);
const textNode = document.createTextNode("");
textNode.nodeValue = textEl.props.nodeValue;

app.append(textNode);

改造2. 使用函数创建元素和创建文字节点

把创建textElel封装成函数方便调用

function createTextNode(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children,
    },
  };
}

const textEl = createTextNode("app");
const el = createElement("div", { id: "app" }, textEl);

页面正常显示 app。 createElement 函数是 mini-react 的核心。它接收元素的类型属性子元素作为参数。对于子元素,若它们是字符串或数字,函数会通过 createTextNode 创建一个文本类型的虚拟 DOM 节点;若是其他类型,则直接作为子节点。

改造3. 创建 render 函数来统一处理

function render(el, container) {
  // 根据不同类型创建不同的dom
  const dom =
    el.type === "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(el.type);
​
  // 添加属性
  Object.keys(el.props).forEach((key) => {
    if (key !== "children") {
      dom[key] = el.props[key];
    }
  });

  // 处理子元素
  const children = el.props.children;
  children.forEach((child) => {
    render(child, dom);
  });

  // 添加到容器中
  container.append(dom);
}
// 使用render
const App = createElement("div", { id: "app" }, "app", "---hi mini-react");
console.log(App);
render(App, document.querySelector("#root"));

到此我们使用对象来描述创建dom的过程

改造4. 对齐 React 的 api 构建核心方法

分离成两个核心文件React.js和ReactDom.js

打开一个最简单的react项目,我们知道React导出的有一个render方法,还有React.dom的createRoot方法,我们据此实现如下:

React.js

// core/React.js

// 实现部分省略
const React = {
  render,
}
export default React

ReactDom.js

// core/ReactDom.js
import React from './React.js'
const ReactDOM = {
  createRoot(container) {
    return {
      render(el) {
        React.render(el, container)
      }
    }
  }
}
export default ReactDOM

改造5. 引入 jsx

JSX 的本质是提供一种更接近 HTML 的语法来描述界面,它通过 createElement 函数转换为 JavaScript 对象。这种转换把组件的每个部分——包括类型、属性和子元素——都转换成了可以被 JavaScript 处理的格式。

// 引入jsx前
const app = React.createElement('div', { id: 'app' }, 'app')
// 引入jsx后
const app = <div id="app">app</div>

使用vite创建一个空项目,然后改造js为jsx文件,运行项目我们会发现报错了:

Uncaught ReferenceError: React is not defined

这是因为对于拓展名为.jsx/.tsx的文件,vite会默认使用React.createElement转化成vdom,但是我们React.js文件没有引入且没有导出createElement方法。修改core/React.js导出方法,然后在使用jsx语法的文件中引入react对象后,项目正常运行。

第一节完。