创建自己的 React (上)

前言

使用 vite, typescript 从零开始创建自己的 React,命名为 hypereact

起步

通过 vite 创建一个项目,不选择任何框架

sh 复制代码
pnpm create vite

创建 vite.config.js 文件,添加 esbuild.loader 选项,设置为 tsx

js 复制代码
export default defineConfig({
  esbuild: {
    loader: 'tsx',
  },
});

createElement 函数

新建 hypereact.ts,在这个文件实现功能代码。

首先需要一个创建 createElement 函数,在 JSX 转换成 JS 的时候调用它,生成一个具有 typepropschildren 的对象,使用这个对象就能生成真实的 DOM 节点。

这个对象的 type 指的是DOM 节点的类型,props 是节点的属性,children 是一个数组,可以包含多种类型的数据,如果是数字或者字符串这种原始类型的值,就将它设置为 TEXT_ELEMENT 类型。

ts 复制代码
/// hypereact.ts

const TEXT_ELEMENT = 'TEXT_ELEMENT';

export function createElement(type: string, props: any, ...children: any[]) {
  return {
    type,
    ...props,
    children: children.map((child) =>
      typeof child === 'object' ? child : createTextElement(child),
    ),
  };
}

function createTextElement(text: string | number) {
  return {
    type: TEXT_ELEMENT,
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

render 函数

render 函数的作用是将元素渲染到页面上,它接收 elementcontainer 两个参数。

首先需要判断 element 的类型,如果是原始类型,就调用 document.createTextNode 创建文本节点,否则根据 element.type 创建对应的 DOM 节点。

然后将 props 属性添加到 DOM 节点上,对 elementchildren 递归调用 render 函数,从而生成完整的节点树。

最后将生成的 DOM 节点添加到 container 里面。

ts 复制代码
export function render(element, container) {
  const dom: HTMLElement =
    element.type == TEXT_ELEMENT
      ? document.createTextNode('')
      : document.createElement(element.type);
  const isProperty = (key) => key !== 'children';

  Object.keys(element.props)
    .filter(isProperty)
    .forEach((name) => {
      if (dom.setAttribute) {
        dom.setAttribute(name, element.props[name]);
      } else {
        dom[name] = element.props[name];
      }
    });

  element.props.children.forEach((child) => render(child, dom));

  container.appendChild(dom);
}

使用

导出 Hypereact 对象

ts 复制代码
/// hypereact.ts
/// ...

const Hypereact = {
  createElement,
  render,
};

export default Hypereact;

添加 App.tsx 文件,使用 /** @jsx Hypereact.createElement */ 注释,告诉构建工具在翻译 JSX 时使用这个函数。

tsx 复制代码
/// App.tsx
/// import Hypereact from './hypereact';
/// ...

/** @jsx Hypereact.createElement */
const App = (
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src={viteLogo} class="logo" alt="Vite logo" />
    </a>
    <a
      href="https://developer.mozilla.org/en-US/docs/Web/JavaScript"
      target="_blank"
    >
      <img src={javascriptLogo} class="logo vanilla" alt="JavaScript logo" />
    </a>
    <h1>Hello Hypereact!</h1>
    <div class="card">
      <button id="counter" type="button">
        {`count is ${counter}`}
      </button>
    </div>
    <p class="read-the-docs">Click on the Vite logo to learn more</p>
  </div>
);

export default App;

main.js 中使用 render 函数,App.jsx 的内容被渲染到页面上了。

js 复制代码
import './style.css';
import App from './App';
import Hypereact from './hypereact';

const container = document.getElementById('app');
Hypereact.render(App, container);

参考

源码

Build your own React

本文完,感谢阅读 🌹

相关推荐
炫饭第一名2 小时前
速通Canvas指北🦮——基础入门篇
前端·javascript·程序员
王晓枫3 小时前
flutter接入三方库运行报错:Error running pod install
前端·flutter
符方昊3 小时前
React 19 对比 React 16 新特性解析
前端·react.js
ssshooter3 小时前
又被 Safari 差异坑了:textContent 拿到的值居然没换行?
前端
曲折3 小时前
Cesium-气象要素PNG色斑图叠加
前端·cesium
Forever7_3 小时前
Electron 淘汰!新的桌面端框架 更强大、更轻量化
前端·vue.js
不会敲代码13 小时前
前端组件化样式隔离实战:React CSS Modules、styled-components 与 Vue scoped 对比
css·vue.js·react.js
Angelial3 小时前
Vue3 嵌套路由 KeepAlive:动态缓存与反向配置方案
前端·vue.js
jiayu4 小时前
Angular学习笔记24:Angular 响应式表单 FormArray 与 FormGroup 相互嵌套
前端
jiayu4 小时前
Angular6学习笔记13:HTTP(3)
前端