使用React Hooks时状态无法更新?求自定义API Hook修复方案
我来帮你排查下代码里的几个关键问题,正是这些问题导致状态无法正确更新:
问题1:then方法的回调使用错误
你当前代码里的这一行存在明显问题:
fetch(url).then(dispatch({ type: "fetching", loading: true }))
then方法需要接收函数作为参数,但你直接执行了dispatch并把它的返回值(undefined)传给了then,这会导致两个问题:
- 还没发起请求就触发了
fetchingaction,失去了loading状态的时效性 - 后续链式调用因为没有正确的Promise延续,逻辑会出现异常
正确的做法是在发起请求之前就dispatch fetching action(因为fetch是异步操作,这样能立刻把loading设为true):
useEffect(() => { // 先触发fetching,设置loading为true dispatch({ type: "fetching", loading: true, url }); axios(url) .then(res => res.data) .then(data => dispatch({ type: "success", loading: false, data, url })) .catch(err => dispatch({ type: "fail", loading: false, err, url })); }, [url]);
问题2:Reducer的状态结构与初始化不匹配
你的初始状态是{ loading: false },但Reducer里试图以url作为key存储每个请求的状态,这会引发两个问题:
- 初始时
state[url]是undefined,展开...currentState会导致异常 - 你在Reducer里解构
const { url, data, err } = action;,但之前dispatch的action里根本没有传入url字段
修正后的Reducer代码:
const Reducer = (state, action) => { const { type, url, data, err, loading } = action; // 给当前url的状态设置默认空对象,避免undefined报错 const currentState = state[url] || {}; switch (type) { case "fetching": return { ...state, [url]: { ...currentState, loading } }; case "success": return { ...state, [url]: { ...currentState, loading, data } }; case "fail": return { ...state, [url]: { ...currentState, loading, err } }; default: return state; } };
问题3:useReducer的初始状态错误
因为我们是按url来区分多个请求的状态,所以初始状态应该是一个空对象,而不是单一的{ loading: false }:
const [state, dispatch] = useReducer(reducer, {});
修正后的完整代码
import { useEffect, useReducer } from "react"; import axios from "axios"; const useApiHook = (url, reducer) => { const [state, dispatch] = useReducer(reducer, {}); useEffect(() => { // 发起请求前先设置loading为true dispatch({ type: "fetching", loading: true, url }); axios(url) .then(res => res.data) .then(data => dispatch({ type: "success", loading: false, data, url })) .catch(err => dispatch({ type: "fail", loading: false, err, url })); }, [url, reducer]); // 直接返回当前url对应的状态,组件使用更便捷 return state[url] || { loading: false }; }; const Reducer = (state, action) => { const { type, url, data, err, loading } = action; const currentState = state[url] || {}; switch (type) { case "fetching": return { ...state, [url]: { ...currentState, loading } }; case "success": return { ...state, [url]: { ...currentState, loading, data } }; case "fail": return { ...state, [url]: { ...currentState, loading, err } }; default: return state; } }; const Comp = () => { const url = "https://randomuser.me/api/"; const sample = useApiHook(url, Reducer); return ( <div> {sample.loading ? <p>Loading...</p> : <pre>{JSON.stringify(sample.data, null, 2)}</pre>} {sample.err && <p>Error: {sample.err.message}</p>} </div> ); };
额外优化说明
- 把
import fetch from "axios"改成import axios from "axios",避免命名混淆(axios的默认导出是axios实例,不是fetch函数) - 给
useEffect的依赖数组加上reducer,避免闭包导致的逻辑异常 - Hook返回值直接返回当前url对应的状态,组件使用时无需再处理整个state对象
内容的提问来源于stack exchange,提问作者John_ny
相关产品推荐
相关产品推荐

