React 项目进阶(二):用 useRepos 解锁状态管理新姿势!

78 阅读3分钟

React 项目开发中,组件通信、状态管理和接口请求常常令人头大。

特别是你刚开始接触 axios、全局状态管理(useContext + useReducer)和懒加载组件时,很容易陷入「哪里发请求?哪里存数据?组件怎么更新?」的迷雾。

这篇文章,我们以一个 GitHub Repos 项目为例,一步步拆解自定义 Hook —— useRepos 的设计和用法。讲清楚它到底解决了什么问题隐藏了哪些业务逻辑,以及你项目里该不该这么用


🌱 场景回顾

你有一个展示 GitHub 用户仓库的组件:

<Route path="/users/:id/repos" element={<RepoList />} />

点进用户详情页后,需要根据 id 获取用户的仓库信息。

于是你在 RepoList 组件中写了:

const { id } = useParams();
const { repos, loading, error } = useRepos(id);

哎?不是用 axios.get 请求吗?为啥没看到?


🧩 useRepos 究竟做了什么?

✅ 组件里只派发一个 action:

useEffect(() => {
  dispatch({ type: 'GET_REPOS', payload: id });
}, [id]);

这段逻辑意味着:“我需要获取 id 对应的仓库信息,你系统自己看着办”。

然后组件只负责用 state.repos 展示内容,并监听 loading / error 决定是否展示“加载中”或“出错啦”。

也就是说:组件不写一行 axios、不操心副作用、不管理状态。

请求逻辑去哪了?

真正的请求逻辑,被统一封装在全局状态管理中,也许像这样:

const enhancedDispatch = (action) => {
  switch (action.type) {
    case 'GET_REPOS':
      dispatch({ type: 'LOADING_REPOS' });
      getRepos(action.payload)
        .then(data => dispatch({ type: 'GET_REPOS_SUCCESS', payload: data }))
        .catch(err => dispatch({ type: 'GET_REPOS_ERROR', payload: err.message }));
      break;
    default:
      dispatch(action);
  }
}

这就是项目中的“中间派发逻辑”,对组件完全透明。


🧪 RepoList 是怎么消费 useRepos 的?

if (loading) return <>loading...</>
if (error) return <>Error: {error}</>

return (
  <>
    <h2>Repositories for {id}</h2>
    {repos.map(repo => (
      <Link key={repo.id} to={`/users/${id}/repos/${repo.name}`}>
        {repo.name}
      </Link>
    ))}
  </>
);

逻辑是不是清晰了:

  • 拿不到 idnavigate('/') 重定向回首页。
  • 正在加载?显示 loading。
  • 报错了?显示 error。
  • 成功了?用数据渲染。

你眼前的 repos 其实是全局状态 state.repos,由 useContext 提供,全局共享。


🏗️ 为什么要这样设计 useRepos?

1. 关注点分离:组件逻辑 vs 数据逻辑

  • useRepos 只是告诉系统“我要数据”。
  • 系统(全局状态管理)自己决定怎么拿数据、怎么处理 loading/error。

组件专注渲染,而不是“又要写 UI,又要发请求”。

2. 逻辑复用:多个组件可复用 useRepos

比如未来还有个 ReposSidebar 也想显示当前用户仓库数目,只要:

const { repos } = useRepos(id);

无需再写 axios!这才是“业务组件”和“数据逻辑”的分离。

3. 便于测试 & 维护

你可以单独测试 getRepos、reducer 和 useRepos,而不用涉及 UI 层。


❓没有中间派发可以用 useRepos 吗?

当然可以,只不过 useRepos 就得自己写请求:

export const useRepos = (id) => {
  const [repos, setRepos] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!id) return;
    setLoading(true);
    getRepos(id)
      .then(setRepos)
      .catch(err => setError(err.message))
      .finally(() => setLoading(false));
  }, [id]);

  return { repos, loading, error };
};

但这样每个组件的状态管理就碎片化了,维护和拓展不友好。

所以你会看到很多项目更倾向于统一 dispatch,通过全局 reducer 管理状态。


🧠 总结:useRepos 做到了什么?

  • ✅ 抽离请求逻辑:组件干净
  • ✅ 状态集中管理:逻辑统一
  • ✅ 接口复用:组件自由组合
  • ✅ 业务封装:低耦合高复用

这就是为什么越来越多项目都会用 useXxx 这样的自定义 Hook 去封装接口数据获取逻辑。

你的组件不需要关心请求、loading、报错这些细节,只要“订阅”状态。


🎉 预告:

下一篇,我们将会结合项目路由懒加载 + 动态嵌套路由,带你一步步理解 react-router 的高级玩法。

记得点赞 + 收藏,我们下一篇见!