触发器模式工作流程图解
完整数据流
scss
┌─────────────────────────────────────────────────────────────────┐
│ 用户操作流程 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────┐
│ 用户点击"编辑"按钮 │
└────────────────────────┘
│
▼
┌────────────────────────┐
│ 进入编辑页面 EditQa │
│ 修改图片并保存 │
└────────────────────────┘
│
▼
┌────────────────────────┐
│ 调用 onBack() 回调 │
└────────────────────────┘
│
┌────────────────────────────────┴────────────────────────────────┐
│ 父组件 (列表页) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ const [refreshTrigger, triggerRefresh] = useRefreshTrigger(); │
│ │
│ const refresh = () => { │
│ query({ ... }); // ← 1. 刷新列表数据 │
│ triggerRefresh(); // ← 2. 触发图片刷新 │
│ }; │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ triggerRefresh() 执行过程: │ │
│ │ │ │
│ │ setTrigger(prev => prev + 1) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ refreshTrigger: 0 → 1 → 2 → 3 ... │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ <FileUpload │
│ id={problem.id} │
│ refreshTrigger={refreshTrigger} // ← 传递触发器值 │
│ /> │
│ │
└──────────────────────────┬───────────────────────────────────────┘
│ props 传递
▼
┌─────────────────────────────────────────────────────────────────┐
│ 子组件 (FileUpload) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ function FileUpload({ id, refreshTrigger }) { │
│ const [pictures, setPictures] = useState([]); │
│ │
│ useEffect(() => { │
│ if (id) { │
│ getFileInfos({ query: { fkId: id } }) │
│ .then(res => setPictures(res)); │
│ } │
│ }, [id, refreshTrigger]); // ← 依赖 refreshTrigger │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ React 检测依赖变化: │ │
│ │ │ │
│ │ refreshTrigger 从 0 变成 1 │ │
│ │ ↓ │ │
│ │ useEffect 重新执行 │ │
│ │ ↓ │ │
│ │ 发起新的图片请求 │ │
│ │ ↓ │ │
│ │ setPictures(新数据) │ │
│ │ ↓ │ │
│ │ 组件重新渲染,显示新图片 │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ return <img src={pictures[0].url} />; │
│ } │
│ │
└─────────────────────────────────────────────────────────────────┘
关键点解析
1. 为什么用数字而不是布尔值?
javascript
// ❌ 使用布尔值的问题
const [refresh, setRefresh] = useState(false);
const triggerRefresh = () => {
setRefresh(true);
setTimeout(() => setRefresh(false), 0); // 需要重置
};
// 连续两次调用会失效:
triggerRefresh(); // true
triggerRefresh(); // 还是 true,没有变化!
// ✅ 使用数字的优势
const [trigger, setTrigger] = useState(0);
const triggerRefresh = () => {
setTrigger(prev => prev + 1);
};
// 每次调用都会变化:
triggerRefresh(); // 0 → 1
triggerRefresh(); // 1 → 2
triggerRefresh(); // 2 → 3
2. 为什么不用 key 强制重新挂载?
javascript
// ❌ 使用 key 的问题
<FileUpload key={`${id}-${timestamp}`} id={id} />
// 组件生命周期:
// 1. 卸载旧组件 (componentWillUnmount)
// 2. 销毁所有状态和 DOM
// 3. 挂载新组件 (componentDidMount)
// 4. 重新初始化所有状态
// 5. 重新渲染 DOM
// 性能开销大,可能导致:
// - 闪烁
// - 动画中断
// - 滚动位置丢失
// - 输入焦点丢失
// ✅ 使用 refreshTrigger 的优势
<FileUpload id={id} refreshTrigger={trigger} />
// 组件生命周期:
// 1. 组件保持挂载状态
// 2. 只执行 useEffect 回调
// 3. 只更新需要更新的数据
// 4. 保留其他状态和 DOM
// 性能好,用户体验流畅
3. 为什么要封装成 Hook?
javascript
// ❌ 不封装的问题
function Component1() {
const [trigger, setTrigger] = useState(0);
const fire = () => setTrigger(prev => prev + 1);
// ...
}
function Component2() {
const [trigger, setTrigger] = useState(0);
const fire = () => setTrigger(prev => prev + 1);
// ... 重复代码
}
// ✅ 封装成 Hook 的优势
function Component1() {
const [trigger, fire] = useRefreshTrigger();
// 简洁、可复用
}
function Component2() {
const [trigger, fire] = useRefreshTrigger();
// 同样简洁
}
对比其他方案
| 方案 | 性能 | 易用性 | 可维护性 | 推荐度 |
|---|---|---|---|---|
| 触发器模式 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 强烈推荐 |
| key 强制重挂载 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⚠️ 不推荐 |
| ref 命令式调用 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⚠️ 不推荐 |
| 布尔值切换 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ | ⚠️ 不推荐 |
实际应用场景
场景 1:列表刷新
javascript
const [trigger, fire] = useRefreshTrigger();
<FlatList
data={data}
renderItem={({ item }) => (
<ItemCard refreshTrigger={trigger} />
)}
/>
<Button onPress={fire}>刷新全部</Button>
场景 2:表单重置
javascript
const [trigger, fire] = useRefreshTrigger();
<Form resetTrigger={trigger}>
<Input />
<Select />
</Form>
<Button onPress={fire}>重置表单</Button>
场景 3:图表重绘
javascript
const [trigger, fire] = useRefreshTrigger();
<Chart
data={chartData}
redrawTrigger={trigger}
/>
<Button onPress={fire}>刷新图表</Button>
场景 4:WebSocket 重连
javascript
const [trigger, fire] = useRefreshTrigger();
useEffect(() => {
const ws = new WebSocket(url);
// ... 连接逻辑
return () => ws.close();
}, [trigger]);
<Button onPress={fire}>重新连接</Button>
总结
触发器模式的本质是:
通过一个简单的、可预测的值变化,来触发复杂的、可控的副作用
这种模式:
- ✅ 符合 React 的声明式理念
- ✅ 保持单向数据流
- ✅ 性能优秀
- ✅ 易于理解和维护
- ✅ 可复用性强
掌握这个模式,你就掌握了 React 状态管理的精髓!