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

本文完,感谢阅读 🌹

相关推荐
Vicky&James2 分钟前
WPF到Web的无缝过渡:英雄联盟客户端项目OpenSilver迁移实战
前端·wpf
m0_748233646 分钟前
RabbitMQ 进阶
android·前端·后端
不想有bug的小菜鸟14 分钟前
vue3使用iframe全屏展示pdf效果
前端·pdf
m0_7482386315 分钟前
Spring Boot项目接收前端参数的11种方式
前端·spring boot·后端
u01005596016 分钟前
前端代理,解决跨域问题讲解
前端
quitv21 分钟前
react脚手架配置别名
前端·javascript·react.js
m0_5287238130 分钟前
前端如何进行性能优化
前端·性能优化
化作繁星31 分钟前
在 Vue 3 中,如何缓存和复用动态组件
前端·vue.js·缓存
一粒沙-1 小时前
iOS 将GIF图分享至微信
前端·ios
graywen1 小时前
从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)
前端