创建自己的 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

本文完,感谢阅读 🌹

相关推荐
kyrie282 分钟前
Redux 完整基础操作(原生 Redux,不结合 React-Redux)
前端
因_崔斯汀2 分钟前
Vue 模板编译:HTML 是怎么变成 JS 的?
前端·vue.js
UXbot15 分钟前
帮助企业低门槛开展AI应用开发的平台推荐
前端·低代码·ui·交互·产品经理·原型模式·web app
橘子星42 分钟前
基于 Vite 的多模态生图前端工程实践
前端·javascript·人工智能
想要成为糕糕手42 分钟前
从零到一:CSS 3D 旋转立方体完全指南
前端·css·canvas
疯狂的魔鬼43 分钟前
多角色督办任务详情页:从权限矩阵到组件拆分的完整实现
前端·vue.js·架构
恋猫de小郭44 分钟前
Android 17 正式版发布,全新 AI 和各种破坏性更新
android·前端·flutter
半个落月1 小时前
从零搭建 AI 生图前端|Vite 工程化 + 通义千问 API 实战,附 API Key 安全方案
前端·人工智能
退休倒计时1 小时前
【每日一题】LeetCode 146. LRU 缓存 TypeScript
算法·leetcode·缓存·typescript
codexu_4612291871 小时前
NoteGen 里一条记录如何变成 Markdown
前端·笔记·rust·tauri