咱們在使用 react 開發前端應用時,不可避免地要進行數據請求,而在數據請求的先後都要執行不少的處理,例如javascript
當請求比較多時,每次都要重複這樣的操做。這裏咱們能夠利用 react 提供的 hook,本身來封裝一個useRequest
。html
umi 框架中已經有實現了一個 useRequest 方法,useRequest-umi-hook,他這裏實現的功能不少,咱們只實現一個基本的功能,其餘更多的功能,您能夠本身拓展。前端
在 useRequest 中,咱們基於 axios 來封裝,不過這裏咱們不會對外暴露太多的配置,首先咱們來明確 useRequest 的輸入和輸出。java
要輸入的數據:react
要返回的數據:ios
咱們在肯定輸入和輸出的格式後,就能夠具體來實現了。json
首先定義好輸入和輸出的數據:axios
const useRequest = (url, data, config) => {
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
return {
loading,
result,
error,
};
};
export default useRequest;
複製代碼
而後就能夠添加數據請求了:markdown
const useRequest = (url, data, config) => {
const [loading, setLoading] = useState(true);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const request = async () => {
setLoading(true);
try {
const result = await axios({
url,
params: data,
method: 'get',
});
if (result && result.status >= 200 && result.status <= 304) {
setResult(result.data);
} else {
setError(new Error('get data error in useRequest'));
}
} catch (reason) {
setError(reason);
}
setLoading(false);
};
useEffect(() => {
request();
}, []);
return {
loading,
result,
error,
};
};
複製代碼
在咱們不考慮第三個配置 config 的狀況下,這個useRequest
就已經可使用了。框架
const App = () => {
const { loading, result, error } = useRequest(url);
return (
<div> <p>loading: {loading}</p> <p>{JSON.stringify(result)}</p> </div>
);
};
複製代碼
咱們在請求接口過程當中,可能接口還沒沒有返回到數據,組件就已經被銷燬了,所以咱們還要添加上取消請求的操做,避免操做已經不存在的組件。關於 axios 如何取消請求,您能夠查看以前寫的一篇文章:axios 源碼系列之如何取消請求。
const request = useCallback(() => {
setLoading(true);
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios({
url,
params: data,
method: 'get',
cancelToken: source.token, // 將token注入到請求中
})
.then((result) => {
setResult(result.data);
setLoading(false);
})
.catch((thrown) => {
// 只有在非取消的請求時,才調用setError和setLoading
// 不然會報組件已被卸載還會調用方法的錯誤
if (!axios.isCancel(thrown)) {
setError(thrown);
setLoading(false);
}
});
return source;
}, [url, data]);
useEffect(() => {
const source = request();
return () => source.cancel('Operation canceled by the user.');
}, [request]);
複製代碼
而後,接着咱們就要考慮把配置加上了,在上面的調用中,只要調用了 useRequest,就會立刻觸發。但若在符合某種狀況下才觸發怎辦呢(例如用戶點擊後才產生接口請求)? 這裏咱們就須要再加上一個配置。
{
"manual": false, // 是否須要手動觸發,若爲false則馬上產生請求,若爲true
"ready": false // 當manual爲true時生效,爲true時才產生請求
}
複製代碼
這裏咱們就要考慮 useRequest 中觸發 reqeust 請求的條件了:
const useRequest = () => {
// 其餘代碼保持不變,並暫時忽略
useEffect(() => {
if (!config || !config.manual || (config.manual && config.ready)) {
const source = request();
return () => source.cancel('Operation canceled by the user.');
}
}, [config]);
};
複製代碼
使用方式:
const App = () => {
const [ready, setReady] = useState(false);
const { loading, result, error } = useRequest(url, null, { manual: true, ready });
return (
<div> <p>loading: {loading}</p> <p>{JSON.stringify(result)}</p> <button onClick={() => setReady(true)}>產生請求</button> </div>
);
};
複製代碼
固然,實現手動觸發的方式有不少,我在這裏是用一個config.ready
來觸發,在 umi 框架中,是對外返回了一個 run 函數,而後執行 run 函數再來觸發這個 useRequest 的執行。
const useRequest = () => {
// 其餘代碼保持不變,並暫時忽略
const [ready, setReady] = useState(false);
const run = (r: boolean) => {
setReady(r);
};
useEffect(() => {
if (!config || !config.manual || (config.manual && ready)) {
if (loading) {
return;
}
setLoading(true);
const source = request();
return () => {
setLoading(false);
setReady(false);
source.cancel('Operation canceled by the user.');
};
}
}, [config, ready]);
};
複製代碼
咱們在這裏實現的useRequest
,也只是實現幾個基本功能!對於更大的業務來講,可能需求點更多,例如輪訓,防抖等,這些功能就須要本身去添加啦!