🧠 一、问题本质(必须理解)
你当前写法:
export default connect(({ global }) => ({
global,
}))(Component)
带来的真实数据流:
global 任意字段变化
→ connect 重新计算 props
→ 组件 re-render
→ useEffect / 生命周期重新执行
→ 接口再次请求 ❌
🔥 你的具体事故复盘
首页文件夹组件:
useEffect → 请求接口
弹窗组件:
修改 global.visible
结果:
global 变化 → 文件夹组件 re-render
→ useEffect 再执行 → 接口重复请求 ❌
👉 这不是 bug,是设计问题
🚫 二、禁止写法(必须禁止)
❌ 1. 注入整个 global
connect(({ global }) => ({
global,
}))
👉 ❗ 高危:任何字段变化都会触发更新
❌ 2. 在 useEffect 依赖 global
useEffect(() => {
fetchData()
}, [global]) // ❌
👉 ❗ 等价于:
global 任意变化 → 重新请求
❌ 3. global 作为 UI 状态容器
global: {
modalVisible: true,
loading: false,
}
👉 ❗ UI 状态污染全局,影响所有组件
✅ 三、标准写法(必须执行)
✅ 1. 精确订阅(最核心规则)
connect(({ global }) => ({
user: global.user,
}))
👉 原则:
只取你用到的字段,绝不多拿
✅ 2. useEffect 依赖精确字段
useEffect(() => {
fetchData()
}, [user.id]) // ✅
✅ 3. UI 状态必须本地化
❌:
global.modalVisible
✅:
const [visible, setVisible] = useState(false)
✅ 4. 接口请求只绑定“业务依赖”
useEffect(() => {
fetchFolderList()
}, []) // 或 [folderId]
👉 ❗ 不要依赖 global
🧱 四、推荐架构拆分(非常重要)
❌ 错误结构
global(超级大对象)
├── user
├── theme
├── modalVisible
├── folderData
├── loading
👉 问题:
- 高耦合
- 一改全动
✅ 正确结构(拆 model)
userModel
uiModel
folderModel
示例:
userModel
state: {
user: {}
}
uiModel(弹窗)
state: {
modalVisible: false
}
folderModel
state: {
list: []
}
👉 好处:
modalVisible 变化 ≠ folderModel 变化
⚙️ 五、接口请求规范(防重复请求)
✅ 方案 1:useEffect + 精确依赖
useEffect(() => {
fetchData()
}, [id])
✅ 方案 2:只执行一次
useEffect(() => {
fetchData()
}, [])
❗ 防御式写法(推荐)
const fetchedRef = useRef(false)
useEffect(() => {
if (fetchedRef.current) return
fetchedRef.current = true
fetchData()
}, [])
✅ 方案 3(更推荐):用 TanStack Query
useQuery({
queryKey: ['folderList'],
queryFn: fetchFolderList,
})
👉 优势:
- 自动缓存
- 防重复请求
- 与 UI 状态解耦
⚠️ 六、进阶优化(避免 re-render)
✅ 1. 使用 selector + 浅比较
import { shallowEqual } from 'react-redux'
connect(({ global }) => ({
user: global.user,
}), null, null, {
pure: true,
})
✅ 2. 使用 memo
export default React.memo(Component)
✅ 3. 避免返回新对象
❌:
connect(({ global }) => ({
user: { ...global.user }
}))
🔍 七、排查 checklist(你可以马上用)
🔎 搜代码:
connect(({ global }) => ({
global
}))
👉 有的话 → 必改 ❗
🔎 搜:
useEffect(.*, [global])
👉 有的话 → 必改 ❗
🔎 看:
- 是否 UI 状态写在 global
- 是否接口依赖 global
📌 八、最终原则(记住这3条就够了)
🎯 原则 1
global 是“共享数据”,不是“所有数据”
🎯 原则 2
connect 必须精确订阅(按字段)
🎯 原则 3
UI 状态不要放 global
🚀 九、针对你这个问题的“最优解”
你的场景:
文件夹组件(请求接口)
弹窗组件(控制 visible)
✅ 改造方案
1️⃣ 弹窗状态本地化
const [visible, setVisible] = useState(false)
2️⃣ 文件夹组件
useEffect(() => {
fetchFolderList()
}, []) // 不依赖 global
3️⃣ 不再 connect global
connect(null)(Component)
🎯 一句话总结
在 Dva 中,错误不是“用了 global”,而是“过度订阅 global”
👉 强烈建议:
逐步从 Dva → hooks + React Query + Zustand