📘 Dva 使用规范(避免 global 误用 & 重复请求)

5 阅读2分钟

🧠 一、问题本质(必须理解)

你当前写法:

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