Preact

技术栈
前端框架
reactlightweight3kbcompatibleperformance

概览

Preact 技术栈概览

Preact 是 React 的3KB 轻量替代品,API 几乎与 React 完全兼容。由 Jason Miller 创建,适合对包体积敏感的嵌入式场景和性能优化项目。

Preact 是什么?

  • React API 的精简实现(仅 3KB gzipped)
  • 通过 preact/compat 兼容 React 生态
  • 更快的虚拟 DOM 差异算法
  • 内置 Class 组件和 Hooks

解决什么问题?

  • 移动端 H5 需要极小的 JS 包体积
  • 嵌入式 WebView 场景(如微信小程序 WebView)
  • 不需要 React 全家桶的轻量项目
  • 毕设中需要 React 语法但追求加载速度

关键特性:

  • 3KB 极致体积
  • preact/compat 无缝迁移 React 组件
  • 无合成事件系统(直接用原生事件)
  • 支持 Preact CLI / Vite 构建
  • 官方提供 preact-router

安装

环境准备

  • 操作系统:macOS / Linux / Windows
  • Node.js:>= 16.x
  • 包管理器:npm / yarn / pnpm
  • 编辑器:VS Code(推荐 Preact 或 React 语法支持插件)

安装命令

使用 Vite 模板

npm create vite@latest my-preact-app -- --template preact
cd my-preact-app
npm install

使用 Preact CLI(传统方式)

npm install -g preact-cli
preact create default my-preact-app
cd my-preact-app
npm run dev

手动安装

mkdir my-preact-app &;& cd my-preact-app
npm init -y
npm install preact
npm install -D vite @preact/preset-vite

vite.config.js

import { defineConfig } from "vite";
import preact from "@preact/preset-vite";

export default defineConfig({
  plugins: [preact()],
});

从 React 迁移(alias 方式)

npm install preact
npm install -D @preact/preset-vite

vite.config.js 中加 alias:

// 将 react 映射到 preact/compat,无需修改组件代码
preact({
  aliases: [
    { find: "react", replacement: "preact/compat" },
    { find: "react-dom", replacement: "preact/compat" },
  ],
})

常见安装问题

1. React 组件库不兼容

  • 现象:第三方库报错 createElement is not a function
  • 解决:使用 preact/compat alias,或确认库是否支持 Preact

2. Hooks 行为差异

  • 解决:Preact 的 Hook 实现与 React 基本一致,但 useIduseSyncExternalStore 等较新的 Hook 可能需要 compat 层

3. TypeScript JSX 类型报错

  • 解决tsconfig.json 中设置 "jsx": "react-jsx""jsxImportSource": "preact"

示例

Preact 计数器与随机名言

目标

用 Preact Hooks 实现计数器 + 随机名言获取,展示信号式状态管理和 useEffect 副作用。

完整代码

src/main.jsx

import { render } from 'preact';
import { App } from './app';

render(<App />, document.getElementById('app'));

src/app.jsx

import { useState, useEffect } from 'preact/hooks';

const quotes = [
  '代码是写给人看的,顺便让机器运行。',
  'Talk is cheap. Show me the code.',
  '简单是可靠的先决条件。',
  '任何傻瓜都能写出计算机能理解的代码,唯有优秀程序员才能写出人能理解的代码。',
];

export function App() {
  const [count, setCount] = useState(0);
  const [quote, setQuote] = useState(quotes[0]);

  // 切换名言
  const randomQuote = () => {
    let idx;
    do { idx = Math.floor(Math.random() * quotes.length); }
    while (quotes[idx] === quote && quotes.length > 1);
    setQuote(quotes[idx]);
  };

  // 副作用:标题随 count 变化
  useEffect(() => {
    document.title = `计数: ${count}`;
  }, [count]);

  return (
    <main style={{ fontFamily: 'system-ui', maxWidth: 400, margin: '60px auto', textAlign: 'center' }}>
      <h1>⚛️ Preact Demo</h1>

      <div style={{ margin: '30px 0' }}>
        <p style={{ color: '#666' }}>计数器</p>
        <p style={{ fontSize: '3em', fontWeight: 'bold', margin: '10px 0' }}>{count}</p>
        <div style={{ display: 'flex', gap: 10, justifyContent: 'center' }}>
          <button
            onClick={() => setCount(c => c - 1)}
            style={btnStyle('#ef4444')}
          >-1</button>
          <button
            onClick={() => setCount(0)}
            style={btnStyle('#6b7280')}
          >重置</button>
          <button
            onClick={() => setCount(c => c + 1)}
            style={btnStyle('#22c55e')}
          >+1</button>
        </div>
      </div>

      <div style={{ background: '#f0f9ff', padding: 20, borderRadius: 12, marginTop: 30 }}>
        <p style={{ fontStyle: 'italic', fontSize: '1.1em' }}>"{quote}"</p>
        <button onClick={randomQuote} style={btnStyle('#3b82f6')}>
          🎲 换一句
        </button>
      </div>

      <p style={{ marginTop: 30, color: '#999', fontSize: '0.85em' }}>
        运行于 Preact · 仅 3KB
      </p>
    </main>
  );
}

function btnStyle(color) {
  return {
    background: color,
    color: 'white',
    border: 'none',
    padding: '8px 16px',
    borderRadius: 6,
    cursor: 'pointer',
    fontSize: '1em',
  };
}

运行步骤

npm create vite@latest preact-demo -- --template preact
cd preact-demo
# 替换 src/app.jsx 内容
npm install
npm run dev

预期输出

  • 页面展示计数器和名言卡片
  • 点击 +/- 按钮计数变化
  • 页面标题同步更新为 "计数: N"
  • 点击"换一句"随机切换名言

Preact Hello World — React 兼容的轻量应用

目标

展示 Preact 与 React 几乎相同的 API,同时强调其 3KB 的体量优势。包含 Hooks、组件和兼容模式示例。

完整代码

// src/main.jsx
import { render } from "preact";
import { App } from "./App";
import "./style.css";

render(<App />, document.getElementById("app"));
// src/App.jsx
import { useState, useEffect, useCallback, useMemo, useRef } from "preact/hooks";

export function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("朋友");
  const [theme, setTheme] = useState("light");
  const inputRef = useRef(null);

  // useEffect — 与 React 完全一致
  useEffect(() => {
    document.title = `Preact App - ${count} 次点击`;
  }, [count]);

  // useMemo — 自动优化
  const doubleCount = useMemo(() => count * 2, [count]);

  // useCallback — 缓存回调
  const focusInput = useCallback(() => {
    inputRef.current?.focus();
  }, []);

  return (
    <div className={`app ${theme}`}>
      <h1>⚛️ Preact Demo</h1>
      <p className="subtitle">
        仅 <strong>3KB</strong> 的 React 替代方案
      </p>

      {/* 双向绑定 */}
      <div className="input-group">
        <input
          ref={inputRef}
          value={name}
          onInput={(e) => setName(e.currentTarget.value)}
          placeholder="你的名字..."
        />
        <button onClick={focusInput}>聚焦输入框</button>
      </div>

      <p className="greeting">
        你好,<strong>{name}</strong>!
      </p>

      {/* 计数器 */}
      <div className="counter">
        <h2>{count}</h2>
        <p>2倍数:{doubleCount}</p>
        <div className="buttons">
          <button onClick={() => setCount((c) => c + 1)}>+1</button>
          <button onClick={() => setCount((c) => c - 1)}>-1</button>
          <button onClick={() => setCount(0)}>重置</button>
        </div>
      </div>

      {/* 主题切换 */}
      <button
        className="theme-toggle"
        onClick={() => setTheme((t) => (t === "light" ? "dark" : "light"))}
      >
        切换主题({theme})
      </button>

      {/* 条件渲染 */}
      {count >= 10 && (
        <div className="achievement">🏆 达到 10 次点击!</div>
      )}
    </div>
  );
}
/* src/style.css */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, 'Microsoft YaHei', sans-serif; }

.app {
  max-width: 480px; margin: 40px auto; padding: 0 16px;
  text-align: center; border-radius: 16px; padding: 32px;
  transition: background 0.3s, color 0.3s;
}

.app.light { background: #fff; color: #1a1a2e; }
.app.dark { background: #1a1a2e; color: #eee; }

.subtitle { color: #888; margin: 8px 0 24px; }

.input-group {
  display: flex; gap: 8px; margin-bottom: 16px;
}
.input-group input {
  flex: 1; padding: 10px; border: 2px solid #e2e8f0;
  border-radius: 8px; font-size: 1rem;
}
.input-group button {
  padding: 10px 16px; border: none; border-radius: 8px;
  background: #6366f1; color: white; cursor: pointer;
}

.counter h2 { font-size: 3rem; margin: 8px 0; }
.buttons button {
  padding: 10px 20px; border: none; border-radius: 8px;
  font-size: 1rem; cursor: pointer; margin: 4px;
  background: #10b981; color: white;
  transition: transform 0.15s;
}
.buttons button:hover { transform: translateY(-1px); }

.theme-toggle {
  margin-top: 20px; padding: 8px 20px;
  border: 1px solid #ccc; border-radius: 8px;
  background: transparent; cursor: pointer; font-size: 0.9rem;
}

.achievement {
  margin-top: 16px; padding: 12px;
  background: #fef3c7; border-radius: 8px; font-weight: 600;
}
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Preact App</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="./src/main.jsx" type="module"></script>
  </body>
</html>
// package.json(关键字段)
{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  },
  "dependencies": {
    "preact": "^10.19.0"
  },
  "devDependencies": {
    "@preact/preset-vite": "^2.7.0",
    "vite": "^5.0.0"
  }
}

运行步骤

npm install
npm run dev

预期输出

  • 界面与 React 几乎无法区分
  • 打包后 gzip 体积约 4KB(Preact 3KB + 组件代码)
  • 计数器、双向绑定、主题切换、条件渲染均正常工作

教程

Preact 入门教程:从 React 到 Preact 无缝迁移

一、Preact 是什么?

Preact 是 Jason Miller 创建的 React 替代品,核心目标:用 3KB 的体积实现 React 的绝大部分 API。它保持与 React 几乎相同的编程模型(组件、Hooks、JSX),但去掉了合成事件系统、Fiber 调度器等大型内部机制,直接使用原生 DOM 事件和微任务调度。

为什么选择 Preact?

场景 推荐
移动端 H5 / 嵌入式 WebView ✅ 体积敏感场景
Widget / 微前端组件 ✅ 独立部署,3KB 几乎可忽略
从 React 渐进迁移 ✅ compat 模式无缝兼容
学习 React 前的过渡 ✅ API 相同,概念更简单

二、从 React 迁移

方案 A:直接使用 preact/compat

最简单的方式——改两行 import + alias:

npm install preact
// vite.config.js
import preact from "@preact/preset-vite";

export default {
  plugins: [preact({
    aliases: [
      { find: "react", replacement: "preact/compat" },
      { find: "react-dom", replacement: "preact/compat" },
      { find: "react/jsx-runtime", replacement: "preact/jsx-runtime" },
    ],
  })],
};

不改任何组件代码,React 项目直接运行在 Preact 上,体积减少约 100KB。

方案 B:逐文件替换 import

- import { useState } from "react";
+ import { useState } from "preact/hooks";

- import { render } from "react-dom";
+ import { render } from "preact";

三、API 兼容性

完全兼容

  • useStateuseEffectuseRefuseContextuseReduceruseMemouseCallback
  • createContextFragmentmemocreatePortal
  • 所有 HTML 属性和事件处理

需要 compat 层

  • Suspense / lazy:Preact 原生支持但行为略有差异,compat 层提供完全匹配
  • useIduseSyncExternalStoreuseInsertionEffect:仅 compat 层
  • createRoot API(React 18):仅 compat 层

Preact 独有特性

import { render } from "preact";

// render 可以直接在 body 上(React 不允许)
render(<App />, document.body);

四、Preact Signals(可选状态管理)

Preact 团队也开发了 Signals,与 Solid.js 类似的细粒度响应式:

npm install @preact/signals
import { signal, computed, effect } from "@preact/signals";

const count = signal(0);
const double = computed(() => count.value * 2);

// 组件中直接使用,无需 useState
function Counter() {
  return (
    <div>
      <p>{count.value} / {double.value}</p>
      <button onClick={() => count.value++}>+1</button>
    </div>
  );
}

effect(() => {
  console.log("count 变了:", count.value);
});

五、打包优化

对比体积(gzip)

React + ReactDOM Preact Preact + compat
大小 ~42KB ~3KB ~4.5KB
相比 React 100% 7% 11%

实际项目节省

一个中等规模的 React 项目迁移到 Preact 后,vendor bundle 通常从 130KB 降至 90KB 左右(包含依赖库)。

六、注意事项

  1. 合成事件差异:Preact 使用原生 DOM 事件,event.currentTarget 行为一致,但某些边缘情况(如 onChange 触发时机)有细微差异
  2. React DevTools:Preact 有独立 DevTools 扩展,也可通过 compat 兼容 React DevTools
  3. findDOMNode 不支持,请使用 ref
  4. React 18 的并发特性useTransitionuseDeferredValue)Preact 原生不支持

七、思考题

  1. 为什么 Preact 能只用 3KB 做到 React 大部分功能?它省略了什么?
  2. 什么类型的项目不适合用 Preact 替代 React?
  3. Preact Signals 和 useState 在性能上有什么本质区别?

Preact 轻量级毕设方案入门

背景

Preact 是 React 的 3KB 替代品。如果你的毕设需要 React 的组件化开发体验,但又担心打包体积太大(尤其是移动端 H5 场景),Preact 是完美的折中方案。API 几乎相同,生态兼容,体积仅 React 的 1/10。


核心概念

Preact vs React

特性 React Preact
体积 ~40KB ~3KB
虚拟 DOM 有(更快)
Hooks
合成事件 无(原生事件)
Class 组件
createElement 有(自动转换)

关键差异

// React:合成事件
<input onChange={(e) => setValue(e.target.value)} />

// Preact:原生事件(也可以用 onInput)
<input onInput={(e) => setValue(e.target.value)} />

分步操作

第一步:创建项目

npm create vite@latest my-app -- --template preact
cd my-app
npm install
npm run dev

第二步:理解项目结构

my-app/
├── index.html
├── src/
│   ├── main.jsx      # 入口:render(<App/>, ...)
│   ├── app.jsx       # 根组件
│   ├── app.css
│   └── components/   # 自定义组件
└── vite.config.js

第三步:编写组件

import { useState } from 'preact/hooks';

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

第四步:兼容 React 生态

npm install preact-compat
// vite.config.js
export default {
  resolve: {
    alias: {
      'react': 'preact/compat',
      'react-dom': 'preact/compat',
    },
  },
};

毕设推荐场景

移动端 H5

Preact 3KB 体积 = 秒级加载,适合扫码即用的毕设展示。

嵌入到现有页面

不需要整个 SPA,Preact 可以渲染到指定 DOM:

render(<Widget />, document.getElementById('widget-root'));

组件库开发

用 Preact 开发可复用组件,体积小、依赖少。


思考题

  1. Preact 去除合成事件系统的优劣分别是什么?
  2. 在 Vite 中配置 alias 后,import from 'react' 实际加载的是什么?
  3. 什么时候应该用 Preact 而不是 React?

小结

Preact = React 语法 + 3KB 体积。如果你的毕设需要组件化开发但对体积敏感(移动端、嵌入式),Preact 是最佳选择。它也能作为理解 React 原理的极佳学习材料。

参考资料

  1. [1] Preact Team. Preact Documentation. 2024. https://preactjs.com
  2. [2] Jason Miller. WTF is Preact?. 2017.
  3. [3] LogRocket Blog. React vs Preact: A Detailed Comparison. 2023.
  4. [4] Preact 团队. Preact 官方文档. 2024. https://preactjs.com/guide/v10/getting-started
  5. [5] Preact 团队. Preact 从React迁移指南. 2024. https://preactjs.com/guide/v10/differences-to-react
  6. [6] Preact 团队. @preact/signals 文档. 2024. https://preactjs.com/guide/v10/signals