迁移指南 #
一、迁移概述 #
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