Preact与TypeScript #
一、TypeScript 配置 #
1.1 安装 TypeScript #
bash
npm install -D typescript @types/node
1.2 tsconfig.json #
json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "preact",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"]
}
1.3 Vite 配置 #
typescript
import { defineConfig } from 'vite';
import preact from '@preact/preset-vite';
export default defineConfig({
plugins: [preact()],
resolve: {
alias: {
'@': '/src'
}
}
});
二、组件类型 #
2.1 函数组件 #
tsx
import { FunctionalComponent } from 'preact';
interface ButtonProps {
text: string;
onClick?: () => void;
disabled?: boolean;
variant?: 'primary' | 'secondary' | 'danger';
}
const Button: FunctionalComponent<ButtonProps> = ({
text,
onClick,
disabled = false,
variant = 'primary'
}) => {
return (
<button
class={`btn btn-${variant}`}
disabled={disabled}
onClick={onClick}
>
{text}
</button>
);
};
export default Button;
2.2 简化写法 #
tsx
interface CardProps {
title: string;
children: preact.ComponentChildren;
}
function Card({ title, children }: CardProps) {
return (
<div class="card">
<h2>{title}</h2>
<div class="card-body">{children}</div>
</div>
);
}
2.3 带泛型的组件 #
tsx
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => preact.VNode;
keyExtractor: (item: T) => string | number;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>
{renderItem(item, index)}
</li>
))}
</ul>
);
}
// 使用
interface User {
id: number;
name: string;
}
<List<User>
items={users}
renderItem={(user) => <span>{user.name}</span>}
keyExtractor={(user) => user.id}
/>
三、Hooks 类型 #
3.1 useState #
tsx
import { useState } from 'preact/hooks';
function Counter() {
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
);
}
3.2 useRef #
tsx
import { useRef } from 'preact/hooks';
function Input() {
const inputRef = useRef<HTMLInputElement>(null);
const focus = () => {
inputRef.current?.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focus}>Focus</button>
</div>
);
}
3.3 useEffect #
tsx
import { useEffect } from 'preact/hooks';
function UserProfile({ userId }: { userId: number }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
let cancelled = false;
async function fetchUser() {
const response = await fetch(`/api/users/${userId}`);
const data: User = await response.json();
if (!cancelled) {
setUser(data);
}
}
fetchUser();
return () => {
cancelled = true;
};
}, [userId]);
return <div>{user?.name}</div>;
}
3.4 自定义 Hook #
tsx
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
async function fetchData() {
try {
setLoading(true);
const response = await fetch(url);
const json: T = await response.json();
if (!cancelled) {
setData(json);
setError(null);
}
} catch (e) {
if (!cancelled) {
setError((e as Error).message);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchData();
return () => {
cancelled = true;
};
}, [url]);
return { data, loading, error };
}
// 使用
interface User {
id: number;
name: string;
email: string;
}
function UserProfile({ userId }: { userId: number }) {
const { data: user, loading, error } = useFetch<User>(`/api/users/${userId}`);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return <div>{user?.name}</div>;
}
四、事件类型 #
4.1 表单事件 #
tsx
function Form() {
const handleSubmit = (e: Event) => {
e.preventDefault();
console.log('Form submitted');
};
const handleInput = (e: Event) => {
const target = e.target as HTMLInputElement;
console.log(target.value);
};
const handleChange = (e: Event) => {
const target = e.target as HTMLSelectElement;
console.log(target.value);
};
return (
<form onSubmit={handleSubmit}>
<input onInput={handleInput} />
<select onChange={handleChange}>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
</form>
);
}
4.2 鼠标事件 #
tsx
function Button() {
const handleClick = (e: MouseEvent) => {
console.log('Clicked at', e.clientX, e.clientY);
};
const handleMouseEnter = (e: MouseEvent) => {
console.log('Mouse entered');
};
return (
<button
onClick={handleClick}
onMouseEnter={handleMouseEnter}
>
Click me
</button>
);
}
4.3 键盘事件 #
tsx
function Input() {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
console.log('Enter pressed');
}
if (e.key === 'Escape') {
console.log('Escape pressed');
}
};
return <input onKeyDown={handleKeyDown} />;
}
五、Context 类型 #
5.1 定义 Context #
tsx
import { createContext } from 'preact';
import { useContext } from 'preact/hooks';
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | null>(null);
function ThemeProvider({ children }: { children: preact.ComponentChildren }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme(t => t === 'light' ? 'dark' : 'light');
};
const value: ThemeContextType = {
theme,
toggleTheme
};
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
function useTheme(): ThemeContextType {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
六、类型声明文件 #
6.1 全局类型 #
typescript
// src/types/global.d.ts
interface User {
id: number;
name: string;
email: string;
avatar?: string;
}
interface Product {
id: number;
name: string;
price: number;
description: string;
image: string;
}
interface ApiResponse<T> {
data: T;
success: boolean;
message?: string;
}
6.2 模块类型 #
typescript
// src/types/api.ts
export interface LoginRequest {
email: string;
password: string;
}
export interface LoginResponse {
token: string;
user: User;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
七、类型工具 #
7.1 组件 Props 类型 #
tsx
// 基础 Props
type BaseProps = {
className?: string;
style?: preact.JSX.CSSProperties;
children?: preact.ComponentChildren;
};
// 扩展 Props
type ButtonProps = BaseProps & {
variant: 'primary' | 'secondary';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
onClick?: (e: MouseEvent) => void;
};
7.2 工具类型 #
tsx
// 可选 Props
type OptionalProps<T> = {
[K in keyof T]?: T[K];
};
// 必选 Props
type RequiredProps<T> = {
[K in keyof T]-?: T[K];
};
// 只读 Props
type ReadonlyProps<T> = {
readonly [K in keyof T]: T[K];
};
八、最佳实践 #
8.1 类型导出 #
tsx
// types/index.ts
export type { User, Product, Order } from './models';
export type { ApiResponse, PaginatedResponse } from './api';
// components/Button/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button';
8.2 严格模式 #
json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
九、总结 #
| 要点 | 说明 |
|---|---|
| FunctionalComponent | 组件类型 |
| useState |
状态类型 |
| useRef |
引用类型 |
| Event | 事件类型 |
| createContext |
Context 类型 |
核心原则:
- 使用严格模式
- 定义清晰的接口
- 导出类型定义
- 避免使用 any
最后更新:2026-03-28