迁移指南 #

一、迁移概述 #

1.1 迁移步骤 #

text
1. 安装 Preact
2. 配置别名
3. 修改导入
4. 处理差异
5. 测试验证

1.2 兼容性 #

React 特性 Preact 支持
Hooks ✅ 完全支持
Context ✅ 完全支持
Refs ✅ 完全支持
Portals ✅ 完全支持
Error Boundaries ✅ 支持
合成事件 ❌ 使用原生事件

二、安装 Preact #

2.1 添加依赖 #

bash
npm install preact

# 如果需要 React 兼容
npm install preact

2.2 移除 React #

bash
npm uninstall react react-dom

三、配置别名 #

3.1 Vite 配置 #

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

export default defineConfig({
  plugins: [preact()],
  resolve: {
    alias: {
      'react': 'preact/compat',
      'react-dom': 'preact/compat',
      'react-dom/test-utils': 'preact/test-utils',
      'react/jsx-runtime': 'preact/jsx-runtime'
    }
  }
});

3.2 Webpack 配置 #

javascript
module.exports = {
  resolve: {
    alias: {
      'react': 'preact/compat',
      'react-dom': 'preact/compat',
      'react-dom/test-utils': 'preact/test-utils'
    }
  }
};

3.3 TypeScript 配置 #

json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact",
    "paths": {
      "react": ["./node_modules/preact/compat"],
      "react-dom": ["./node_modules/preact/compat"]
    }
  }
}

四、代码修改 #

4.1 导入语句 #

jsx
// React
import React from 'react';
import ReactDOM from 'react-dom';
import { useState, useEffect } from 'react';

// Preact(使用别名后无需修改)
import React from 'react';
import ReactDOM from 'react-dom';
import { useState, useEffect } from 'react';

// Preact(直接导入)
import { render } from 'preact';
import { useState, useEffect } from 'preact/hooks';
import { Component } from 'preact';

4.2 渲染入口 #

jsx
// React
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

// Preact
import { render } from 'preact';

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

// Preact(使用 compat)
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(<App />);

4.3 类组件 #

jsx
// React
import React from 'react';

class MyComponent extends React.Component {
  render() {
    return <div>Hello</div>;
  }
}

// Preact
import { Component } from 'preact';

class MyComponent extends Component {
  render() {
    return <div>Hello</div>;
  }
}

4.4 事件处理 #

jsx
// React 合成事件
function handleClick(e) {
  e.persist(); // 如果需要异步访问
  setTimeout(() => {
    console.log(e.target.value);
  }, 1000);
}

// Preact 原生事件
function handleClick(e) {
  // 无需 persist
  setTimeout(() => {
    console.log(e.target.value);
  }, 1000);
}

4.5 属性名称 #

jsx
// React
<div className="container">
  <label htmlFor="input">Label</label>
</div>

// Preact(两种都支持)
<div className="container">
  <label htmlFor="input">Label</label>
</div>

// Preact(推荐)
<div class="container">
  <label for="input">Label</label>
</div>

五、处理差异 #

5.1 事件系统 #

jsx
// React 合成事件特性
function ReactComponent() {
  const handleClick = (e) => {
    // 合成事件
    console.log(e.nativeEvent); // 原生事件
    e.isPropagationStopped();   // 方法
    e.isDefaultPrevented();     // 方法
  };
}

// Preact 原生事件
function PreactComponent() {
  const handleClick = (e) => {
    // 原生事件
    console.log(e); // 直接是原生事件
    // 无 isPropagationStopped 等方法
  };
}

5.2 生命周期差异 #

jsx
// React
class Component extends React.Component {
  getSnapshotBeforeUpdate(prevProps, prevState) {
    return { scrollTop: this.listRef.current.scrollTop };
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot !== null) {
      this.listRef.current.scrollTop = snapshot.scrollTop;
    }
  }
}

// Preact(不支持 getSnapshotBeforeUpdate)
// 使用 useLayoutEffect 替代
function Component() {
  const listRef = useRef(null);
  const prevScrollTop = useRef(0);

  useLayoutEffect(() => {
    prevScrollTop.current = listRef.current.scrollTop;
  });

  useEffect(() => {
    // 更新后操作
  });
}

5.3 错误边界 #

jsx
// React
class ErrorBoundary extends React.Component {
  componentDidCatch(error, errorInfo) {
    console.log(errorInfo.componentStack); // 组件堆栈
  }
}

// Preact
class ErrorBoundary extends Component {
  componentDidCatch(error) {
    // 无 errorInfo 参数
    console.log(error);
  }
}

// Preact Hook 方式
function ErrorBoundary({ children }) {
  const [error, resetError] = useErrorBoundary();
  
  if (error) {
    return <button onClick={resetError}>Retry</button>;
  }
  
  return children;
}

六、第三方库兼容 #

6.1 React Router #

jsx
// 使用 preact/compat 别名后可直接使用
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}

6.2 Redux #

jsx
// 使用 preact/compat 别名后可直接使用
import { Provider, useSelector, useDispatch } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({ reducer: rootReducer });

function App() {
  return (
    <Provider store={store}>
      <Main />
    </Provider>
  );
}

6.3 不兼容的库 #

jsx
// 某些依赖 React 内部 API 的库可能不兼容
// 解决方案:
// 1. 寻找 Preact 替代品
// 2. 使用 preact/compat 并测试
// 3. 联系库作者

七、测试迁移 #

7.1 测试配置 #

javascript
// vitest.config.js
import { defineConfig } from 'vitest/config';
import preact from '@preact/preset-vite';

export default defineConfig({
  plugins: [preact()],
  test: {
    environment: 'jsdom',
    setupFiles: ['./test/setup.ts']
  }
});

7.2 测试代码 #

tsx
// React Testing Library
import { render, screen } from '@testing-library/react';

// Preact Testing Library
import { render, screen } from '@testing-library/preact';

// 测试代码相同
test('renders button', () => {
  render(<Button>Click me</Button>);
  expect(screen.getByRole('button')).toBeInTheDocument();
});

八、常见问题 #

8.1 类型错误 #

typescript
// 添加类型声明
declare module 'preact' {
  namespace JSX {
    interface IntrinsicElements {
      // 自定义元素
    }
  }
}

8.2 样式问题 #

jsx
// 检查 CSS 模块导入
import styles from './Button.module.css';

// 确保 class 正确应用
<button class={styles.button}>Click</button>

8.3 性能问题 #

jsx
// 使用 memo 优化
import { memo } from 'preact/compat';

const MemoComponent = memo(function Component({ data }) {
  return <div>{data}</div>;
});

九、迁移检查清单 #

text
□ 安装 Preact 依赖
□ 配置构建工具别名
□ 修改入口文件渲染方式
□ 检查事件处理代码
□ 更新属性名称(可选)
□ 测试第三方库兼容性
□ 更新测试配置
□ 运行完整测试
□ 检查性能表现
□ 更新文档

十、总结 #

步骤 说明
安装 安装 preact
配置 设置别名
修改 调整代码
测试 验证功能

核心要点:

  • 使用 preact/compat 实现兼容
  • 注意事件系统差异
  • 测试第三方库兼容性
  • 逐步迁移验证
最后更新:2026-03-28