React Hook 高级模式:从触发器模式到实战应用
前言
在 React 开发中,我们经常遇到这样的场景:父组件需要控制子组件重新加载数据,但又不想重新挂载整个组件。本文将介绍一种优雅的解决方案------触发器模式(Trigger Pattern),以及其他 9 种实用的 Hook 模式。
问题场景
假设我们有一个图片上传组件 FileUpload,在编辑页面修改图片后返回列表页,需要刷新图片显示:
javascript
// 列表页
function RecordsList() {
const refresh = () => {
fetchData(); // 刷新列表数据
// 问题:如何让 FileUpload 组件也刷新?
};
return (
<div>
{data.map(item => (
<FileUpload id={item.id} /> // 需要刷新图片
))}
</div>
);
}
常见的错误方案
❌ 方案 1:使用 key 强制重新挂载
javascript
<FileUpload key={`${item.id}-${Date.now()}`} id={item.id} />
问题:
- 组件完全销毁重建,丢失所有内部状态
- 性能差,会触发完整的挂载/卸载生命周期
- 可能导致闪烁或动画中断
❌ 方案 2:通过 ref 调用子组件方法
javascript
const fileUploadRef = useRef();
const refresh = () => {
fileUploadRef.current.refresh(); // 命令式调用
};
<FileUpload ref={fileUploadRef} id={item.id} />
问题:
- 打破了 React 的单向数据流
- 不符合声明式编程理念
- 难以追踪数据流向
❌ 方案 3:使用布尔值切换
javascript
const [shouldRefresh, setShouldRefresh] = useState(false);
const refresh = () => {
setShouldRefresh(true);
setTimeout(() => setShouldRefresh(false), 0);
};
<FileUpload shouldRefresh={shouldRefresh} />
问题:
- 需要复杂的状态重置逻辑
- 连续刷新可能失效(值没有变化)
- 代码不够优雅
✅ 最佳方案:触发器模式
核心思想
用一个简单的数值变化来触发复杂的副作用
实现步骤
1. 创建 Hook
javascript
// src/hooks/useRefreshTrigger.js
import { useState, useCallback } from 'react';
export const useRefreshTrigger = () => {
const [trigger, setTrigger] = useState(0);
const fire = useCallback(() => {
setTrigger(prev => prev + 1);
}, []);
return [trigger, fire];
};
2. 在父组件中使用
javascript
// 列表页
import { useRefreshTrigger } from '@/hooks/useRefreshTrigger';
function RecordsList() {
const [refreshTrigger, triggerRefresh] = useRefreshTrigger();
const refresh = () => {
fetchData();
triggerRefresh(); // 触发刷新
};
return (
<div>
{data.map(item => (
<FileUpload
id={item.id}
refreshTrigger={refreshTrigger} // 传递触发器
/>
))}
</div>
);
}
3. 在子组件中响应
javascript
// FileUpload 组件
function FileUpload({ id, refreshTrigger }) {
const [pictures, setPictures] = useState([]);
useEffect(() => {
if (id) {
// 当 id 或 refreshTrigger 变化时,重新加载图片
fetchImages(id).then(setPictures);
}
}, [id, refreshTrigger]); // ← 关键:依赖 refreshTrigger
return (
<div>
{pictures.map(pic => <img src={pic.url} />)}
</div>
);
}
工作原理
scss
用户操作 → triggerRefresh() → trigger: 0 → 1 → 2 → ...
↓
useEffect 检测到依赖变化
↓
重新执行副作用
优势总结
✅ 声明式 - 符合 React 理念,数据流清晰
✅ 轻量级 - 只触发 effect,不重新挂载组件
✅ 可预测 - 父组件控制,子组件响应
✅ 可复用 - Hook 可在任何场景使用
✅ 性能好 - 不会丢失组件状态
其他 9 种实用 Hook 模式
1. Toggle Pattern - 开关模式
管理布尔状态的切换,比 useState 更方便:
javascript
export const useToggle = (initialValue = false) => {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue(v => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return [value, { toggle, setTrue, setFalse }];
};
// 使用
function Modal() {
const [isOpen, { toggle, setTrue, setFalse }] = useToggle(false);
return (
<>
<button onClick={setTrue}>打开</button>
<Dialog visible={isOpen} onClose={setFalse} />
</>
);
}
适用场景: 模态框、折叠面板、显示/隐藏
2. Previous Value Pattern - 记录上一次的值
获取状态的前一个值,用于对比:
javascript
export const 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>
<p>变化: {count - (prevCount || 0)}</p>
</div>
);
}
适用场景: 对比新旧值、撤销操作、动画过渡
3. Debounce Pattern - 防抖模式
延迟执行,避免频繁触发:
javascript
export const useDebounce = (value, delay = 500) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
};
// 使用
function SearchBox() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
searchAPI(debouncedSearchTerm); // 只在停止输入 500ms 后搜索
}
}, [debouncedSearchTerm]);
return <input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />;
}
适用场景: 搜索输入、窗口 resize、滚动事件
4. Async State Pattern - 异步状态管理
统一管理异步请求的加载、成功、失败状态:
javascript
export const useAsync = (asyncFunction) => {
const [state, setState] = useState({
loading: false,
data: null,
error: null,
});
const execute = useCallback(async (...params) => {
setState({ loading: true, data: null, error: null });
try {
const data = await asyncFunction(...params);
setState({ loading: false, data, error: null });
return data;
} catch (error) {
setState({ loading: false, data: null, error });
throw error;
}
}, [asyncFunction]);
return { ...state, execute };
};
// 使用
function UserProfile({ userId }) {
const { loading, data, error, execute } = useAsync(fetchUserData);
useEffect(() => {
execute(userId);
}, [userId]);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return <div>用户名: {data.name}</div>;
}
适用场景: API 调用、数据加载
5. Local Storage Pattern - 持久化状态
自动同步状态到 localStorage:
javascript
export const useLocalStorage = (key, initialValue) => {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = useCallback((value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
}, [key, storedValue]);
return [storedValue, setValue];
};
// 使用
function ThemeSelector() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
当前主题: {theme}
</button>
);
}
适用场景: 用户偏好设置、表单草稿、主题切换
6. Interval Pattern - 定时器模式
安全地使用 setInterval:
javascript
export const useInterval = (callback, delay) => {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay !== null) {
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}
}, [delay]);
};
// 使用
function Countdown() {
const [count, setCount] = useState(60);
useInterval(() => {
if (count > 0) setCount(count - 1);
}, 1000);
return <div>倒计时: {count}秒</div>;
}
适用场景: 倒计时、轮询、实时更新
7. Mount Status Pattern - 组件挂载状态
避免在组件卸载后更新状态(防止内存泄漏):
javascript
export const useIsMounted = () => {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return useCallback(() => isMounted.current, []);
};
// 使用
function SafeAsyncComponent() {
const [data, setData] = useState(null);
const isMounted = useIsMounted();
useEffect(() => {
fetchData().then(result => {
if (isMounted()) { // 只在组件还挂载时才更新
setData(result);
}
});
}, []);
return <div>{data}</div>;
}
适用场景: 异步操作、延迟更新
8. Composite Pattern - 组合模式
将多个相关的 Hook 组合成一个业务 Hook:
javascript
export const useSearchableList = (fetchFunction) => {
const [searchTerm, setSearchTerm] = useState('');
const [refreshTrigger, triggerRefresh] = useRefreshTrigger();
const debouncedSearchTerm = useDebounce(searchTerm, 300);
const { loading, data, error, execute } = useAsync(fetchFunction);
useEffect(() => {
execute(debouncedSearchTerm);
}, [debouncedSearchTerm, refreshTrigger]);
return {
searchTerm,
setSearchTerm,
loading,
data,
error,
refresh: triggerRefresh,
};
};
// 使用
function ProductList() {
const { searchTerm, setSearchTerm, loading, data, refresh } =
useSearchableList(searchProducts);
return (
<div>
<input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
<button onClick={refresh}>刷新</button>
{loading ? '加载中...' : data?.map(item => <div key={item.id}>{item.name}</div>)}
</div>
);
}
适用场景: 表单管理、分页列表、搜索功能
9. Event Listener Pattern - 事件监听模式
安全地添加和移除事件监听器:
javascript
export const useEventListener = (eventName, handler, element = window) => {
const savedHandler = useRef();
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
const isSupported = element && element.addEventListener;
if (!isSupported) return;
const eventListener = (event) => savedHandler.current(event);
element.addEventListener(eventName, eventListener);
return () => {
element.removeEventListener(eventName, eventListener);
};
}, [eventName, element]);
};
// 使用
function KeyboardShortcuts() {
useEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
console.log('保存快捷键触发');
}
});
return <div>按 Ctrl+S 保存</div>;
}
适用场景: 键盘事件、窗口事件、自定义事件
Hook 设计原则
1. 单一职责原则
每个 Hook 只做一件事,保持简单和专注。
2. 可组合性
小 Hook 可以组合成大 Hook,就像乐高积木。
3. 声明式
通过 props 和返回值传递数据,避免命令式调用。
4. 可测试性
逻辑独立,易于单元测试。
5. 可复用性
不依赖具体业务,通用性强。
命名规范
Hook 命名
- 格式:
use+ 功能描述(驼峰命名) - 示例:
useRefreshTrigger,useToggle,useDebounce
返回值规范
-
单个值:直接返回
javascriptconst debouncedValue = useDebounce(value); -
两个值:返回数组(类似 useState)
javascriptconst [trigger, fire] = useRefreshTrigger(); const [isOpen, { toggle }] = useToggle(); -
多个值:返回对象
javascriptconst { loading, data, error, execute } = useAsync(fetchData);
实战案例:完整的列表刷新方案
结合多个 Hook 模式,实现一个完整的列表页:
javascript
import { useRefreshTrigger } from '@/hooks/useRefreshTrigger';
import { useDebounce } from '@/hooks/useDebounce';
import { useAsync } from '@/hooks/useAsync';
function QualityInspectionRecords({ route, navigation }) {
// 1. 刷新触发器
const [refreshTrigger, triggerRefresh] = useRefreshTrigger();
// 2. 搜索防抖
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 300);
// 3. 异步数据加载
const { loading, data, execute } = useAsync(fetchRecords);
// 4. 加载数据
useEffect(() => {
execute(route.params?.outsourcingId, debouncedSearchTerm);
}, [route.params?.outsourcingId, debouncedSearchTerm, refreshTrigger]);
// 5. 刷新函数
const refresh = useCallback(() => {
triggerRefresh();
}, [triggerRefresh]);
// 6. 渲染
return (
<View>
<SearchBar value={searchTerm} onChange={setSearchTerm} />
{loading ? (
<Loading />
) : (
<FlatList
data={data}
renderItem={({ item }) => (
<View>
<FileUpload
id={item.id}
refreshTrigger={refreshTrigger} // 传递触发器
/>
<Button
onPress={() => navigation.navigate('Edit', {
item,
onBack: refresh, // 编辑完成后刷新
})}
>
编辑
</Button>
</View>
)}
/>
)}
</View>
);
}
总结
Hook 模式的核心是关注点分离 和逻辑复用。通过将常见的状态逻辑封装成 Hook,我们可以:
- ✅ 减少重复代码
- ✅ 提高代码可读性
- ✅ 简化组件逻辑
- ✅ 提升可维护性
- ✅ 便于单元测试
触发器模式只是其中一种,掌握这些模式后,你会发现 React 开发变得更加优雅和高效。
参考资源
- React Hooks 官方文档
- usehooks.com - Hook 示例集合
- ahooks - 阿里开源的 Hook 库
作者: [你的名字]
日期: 2026-01-10
标签: React, Hooks, 设计模式, 最佳实践