Hooks基础 #
一、Hooks概述 #
1.1 什么是Hooks #
Hooks 是 Preact 提供的函数,允许在函数组件中使用状态和其他特性。
1.2 为什么使用Hooks #
| 优势 | 说明 |
|---|---|
| 简洁 | 函数组件比类组件更简洁 |
| 复用 | 自定义 Hook 实现逻辑复用 |
| 组合 | 相关逻辑放在一起 |
| 无this | 不需要处理 this 绑定问题 |
1.3 Hooks规则 #
jsx
// 规则一:只在最顶层使用Hook
function Component() {
const [count, setCount] = useState(0);
// ❌ 不要在条件语句中使用
if (count > 0) {
const [name, setName] = useState(''); // 错误
}
// ❌ 不要在循环中使用
for (let i = 0; i < 5; i++) {
const [value, setValue] = useState(i); // 错误
}
// ✅ 在顶层使用
const [name, setName] = useState('');
return <div>{count}</div>;
}
// 规则二:只在Preact函数中使用Hook
// ✅ 在函数组件中使用
function Component() {
const [state, setState] = useState(0);
}
// ✅ 在自定义Hook中使用
function useCustomHook() {
const [state, setState] = useState(0);
}
// ❌ 不要在普通函数中使用
function regularFunction() {
const [state, setState] = useState(0); // 错误
}
二、useState #
2.1 基本用法 #
jsx
import { useState } from 'preact/hooks';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
2.2 初始化状态 #
jsx
// 直接值
const [count, setCount] = useState(0);
const [name, setName] = useState('Preact');
const [isActive, setIsActive] = useState(false);
// 对象
const [user, setUser] = useState({ name: '', age: 0 });
// 数组
const [items, setItems] = useState([]);
// 惰性初始化(计算开销大时使用)
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation();
return initialState;
});
2.3 更新状态 #
jsx
function Counter() {
const [count, setCount] = useState(0);
// 方式一:直接设置新值
const increment = () => {
setCount(count + 1);
};
// 方式二:函数式更新(推荐)
const incrementSafe = () => {
setCount(prev => prev + 1);
};
// 批量更新
const incrementMultiple = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// 最终 count 增加 3
};
return (
<div>
<p>{count}</p>
<button onClick={increment}>增加</button>
</div>
);
}
2.4 更新对象和数组 #
jsx
function Form() {
const [user, setUser] = useState({
name: '',
email: '',
address: {
city: '',
street: ''
}
});
// 更新顶层属性
const updateName = (name) => {
setUser(prev => ({ ...prev, name }));
};
// 更新嵌套属性
const updateCity = (city) => {
setUser(prev => ({
...prev,
address: {
...prev.address,
city
}
}));
};
return (
<form>
<input
value={user.name}
onInput={(e) => updateName(e.target.value)}
/>
<input
value={user.address.city}
onInput={(e) => updateCity(e.target.value)}
/>
</form>
);
}
function TodoList() {
const [todos, setTodos] = useState([]);
// 添加
const addTodo = (text) => {
setTodos(prev => [...prev, { id: Date.now(), text }]);
};
// 删除
const removeTodo = (id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
};
// 更新
const updateTodo = (id, text) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, text } : todo
));
};
return (
<div>
<button onClick={() => addTodo('New Task')}>添加</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}
三、useRef #
3.1 基本用法 #
jsx
import { useRef } from 'preact/hooks';
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>聚焦</button>
</div>
);
}
3.2 useRef特点 #
| 特点 | 说明 |
|---|---|
| 持久化 | 组件生命周期内保持不变 |
| 不触发渲染 | 修改 current 不会重新渲染 |
| 任意值 | 可以存储任何类型的值 |
3.3 存储可变值 #
jsx
function Stopwatch() {
const [time, setTime] = useState(0);
const intervalRef = useRef(null);
const start = () => {
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
setTime(prev => prev + 1);
}, 1000);
};
const stop = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
const reset = () => {
stop();
setTime(0);
};
return (
<div>
<p>{time}秒</p>
<button onClick={start}>开始</button>
<button onClick={stop}>停止</button>
<button onClick={reset}>重置</button>
</div>
);
}
3.4 获取前一次的值 #
jsx
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>当前: {count}</p>
<p>之前: {prevCount}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
四、useMemo #
4.1 基本用法 #
jsx
import { useMemo } from 'preact/hooks';
function ExpensiveComponent({ list, filter }) {
const filteredList = useMemo(() => {
console.log('计算中...');
return list.filter(item => item.includes(filter));
}, [list, filter]);
return (
<ul>
{filteredList.map(item => <li key={item}>{item}</li>)}
</ul>
);
}
4.2 使用场景 #
jsx
function ProductList({ products, category }) {
// 缓存计算结果
const filteredProducts = useMemo(() => {
return products.filter(p => p.category === category);
}, [products, category]);
// 缓存排序结果
const sortedProducts = useMemo(() => {
return [...filteredProducts].sort((a, b) => a.price - b.price);
}, [filteredProducts]);
// 缓存复杂计算
const statistics = useMemo(() => {
const total = filteredProducts.reduce((sum, p) => sum + p.price, 0);
const avg = total / filteredProducts.length;
return { total, avg };
}, [filteredProducts]);
return (
<div>
<p>平均价格: {statistics.avg}</p>
{sortedProducts.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}
五、useCallback #
5.1 基本用法 #
jsx
import { useCallback } from 'preact/hooks';
function ParentComponent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
// 缓存回调函数
const handleClick = useCallback(() => {
console.log('点击');
}, []);
// 带依赖的回调
const handleAddItem = useCallback(() => {
setItems(prev => [...prev, `Item ${prev.length + 1}`]);
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>增加Count</button>
<ChildComponent onClick={handleClick} onAdd={handleAddItem} />
</div>
);
}
const ChildComponent = memo(function ChildComponent({ onClick, onAdd }) {
console.log('ChildComponent 渲染');
return (
<div>
<button onClick={onClick}>点击</button>
<button onClick={onAdd}>添加</button>
</div>
);
});
5.2 useMemo vs useCallback #
jsx
// useMemo: 缓存计算结果
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// useCallback: 缓存函数引用
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// useCallback 等价于
const memoizedCallback = useMemo(() => () => {
doSomething(a, b);
}, [a, b]);
六、useReducer #
6.1 基本用法 #
jsx
import { useReducer } from 'preact/hooks';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
case 'set':
return { count: action.payload };
default:
throw new Error('Unknown action');
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
<button onClick={() => dispatch({ type: 'set', payload: 10 })}>
设置为10
</button>
</div>
);
}
6.2 复杂状态管理 #
jsx
const initialState = {
todos: [],
filter: 'all'
};
function todoReducer(state, action) {
switch (action.type) {
case 'add':
return {
...state,
todos: [...state.todos, {
id: Date.now(),
text: action.payload,
completed: false
}]
};
case 'toggle':
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
};
case 'delete':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
case 'set_filter':
return {
...state,
filter: action.payload
};
default:
return state;
}
}
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, initialState);
const addTodo = (text) => {
dispatch({ type: 'add', payload: text });
};
return (
<div>
<TodoInput onAdd={addTodo} />
<TodoList
todos={state.todos}
onToggle={(id) => dispatch({ type: 'toggle', payload: id })}
onDelete={(id) => dispatch({ type: 'delete', payload: id })}
/>
</div>
);
}
6.3 惰性初始化 #
jsx
function init(initialCount) {
return { count: initialCount };
}
function Counter({ initialCount }) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'reset', payload: initialCount })}>
Reset
</button>
</div>
);
}
七、useContext #
7.1 基本用法 #
jsx
import { createContext, useContext } from 'preact';
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <ThemedButton />;
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button class={theme}>Themed Button</button>;
}
7.2 结合useState使用 #
jsx
const UserContext = createContext(null);
function App() {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
<Header />
<Main />
</UserContext.Provider>
);
}
function Header() {
const { user, setUser } = useContext(UserContext);
if (!user) {
return <button onClick={() => setUser({ name: 'Alice' })}>登录</button>;
}
return (
<div>
<span>{user.name}</span>
<button onClick={() => setUser(null)}>退出</button>
</div>
);
}
八、Hooks速查表 #
| Hook | 用途 |
|---|---|
| useState | 状态管理 |
| useEffect | 副作用处理 |
| useContext | 上下文访问 |
| useReducer | 复杂状态管理 |
| useRef | DOM引用/持久值 |
| useMemo | 缓存计算结果 |
| useCallback | 缓存函数引用 |
| useLayoutEffect | 同步DOM操作 |
九、总结 #
| 要点 | 说明 |
|---|---|
| 规则 | 只在顶层调用,只在Preact函数中调用 |
| useState | 管理组件状态 |
| useRef | 访问DOM,存储持久值 |
| useMemo | 缓存计算结果 |
| useCallback | 缓存函数引用 |
| useReducer | 复杂状态逻辑 |
核心原则:
- 遵循 Hooks 调用规则
- 使用函数式更新保证状态正确
- 合理使用 useMemo 和 useCallback 优化性能
- 复杂状态使用 useReducer
最后更新:2026-03-28