💯 React自定义Hook:封装数据请求

前言

React应用开发中,数据请求是一个极其常见的需求。几乎每个页面都需要处理加载状态错误处理重新加载逻辑。

接下来,本文将详细介绍如何通过自定义Hook将这些重复的逻辑封装起来,实现代码的复用和整洁

正文

一、为什么需要自定义Hook?

React开发中,我们经常会遇到这样的场景多个组件需要相同的功能逻辑,如数据请求、状态管理等。如果每个组件都重复编写这些代码,会导致

  1. 代码冗余,维护困难
  2. 容易产生不一致的实现
  3. 业务逻辑分散,难以追踪

自定义Hook正是为了解决这些问题而生的。它让我们可以将组件逻辑提取到可重用的函数中,保持组件的简洁性。

二、基础实现:未封装的数据请求

让我们先看看未封装前的组件实现:

jsx 复制代码
export default function App() {
    const [courses, setCourses] = useState([]);
    const [keyword, setKeyword] = useState('');
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(false);

    const fetchData = async () => {
        try {
            const { data } = await get('/search', { q: keyword });
            setCourses(data.courses);
        } catch (err) {
            setError(true);
        } finally {
            setLoading(false);
        }
    };

    const onReload = async () => {
        setLoading(true);
        setError(false);
        await fetchData();
    }

    useEffect(() => {
        fetchData();
    }, [keyword]);

    if (loading) {
        return <Loading />;
    }

    if (error) {
        return <NetworkError onReload={onReload} />;
    }

    return (
        <View>
            {/* 页面内容 */}
        </View>
    );
}

这种实现方式有几个明显的问题:

  1. 状态管理代码占据了组件的大部分内容
  2. 数据请求逻辑与UI渲染逻辑混杂
  3. 在其他组件中需要重复相同的模式

三、创建自定义Hook:useFetchData

为了解决上述问题,我们可以创建一个自定义Hook来封装数据请求逻辑。以下是完整的实现:

jsx 复制代码
import { useState, useEffect } from 'react';
import { get } from '../utils/request';

/**
 * 自定义Hook用于获取数据
 * @param {string} url - API请求路径(如'/search')
 * @param {Object} params - 查询参数(如{ q: keyword })
 * @returns {{
 *   data: Object,
 *   loading: boolean,
 *   error: boolean,
 *   onReload: Function,
 *   setData: Function
 * }}
 */
const useFetchData = (url, params = {}) => {
    const [data, setData] = useState({});
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(false);

    /**
     * 请求接口数据
     * @returns {Promise<void>}
     */
    const fetchData = async () => {
        try {
            setLoading(true);
            setError(false);
            const { data: responseData } = await get(url, params);
            setData(responseData);
        } catch (err) {
            setError(true);
        } finally {
            setLoading(false);
        }
    };

    /**
     * 重新加载数据
     * @returns {Promise<void>}
     */
    const onReload = async () => {
        await fetchData();
    };

    useEffect(() => {
        // 将params对象转为字符串,避免因引用变化导致的重复请求
        const paramsStr = JSON.stringify(params);
        fetchData();
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [url, paramsStr]);

    return {
        data,
        loading,
        error,
        onReload,
        setData // 允许外部修改data
    };
};

export default useFetchData;

关键点解析:

  1. 状态管理Hook内部管理了dataloadingerror三种状态,外部组件无需关心这些状态的维护。

  2. 参数处理 :接受urlparams两个参数,params参数会被用于API请求

  3. 依赖处理useEffect的依赖项包括urlparams。由于params是对象,直接作为依赖项会导致每次渲染都触发effect(因为对象引用不同),所以将其转为字符串后再作为依赖项。

  4. 返回对象 :返回了dataloadingerroronReloadsetData,提供了完整的数据请求功能集。

四、优化后的组件实现

使用自定义Hook后,组件的代码变得非常简洁:

jsx 复制代码
import { TextInput, StyleSheet, Text, View } from 'react-native';
import { useState } from 'react';
import Loading from './components/shared/Loading';
import NetworkError from './components/shared/NetworkError';
import useFetchData from './hooks/useFetchData';

export default function App() {
    const [keyword, setKeyword] = useState('');
    const { data, loading, error, onReload } = useFetchData('/search', { q: keyword });
    const { courses = [] } = data;

    if (loading) {
        return <Loading />;
    }

    if (error) {
        return <NetworkError title="网络错误,请重试" onReload={onReload} />;
    }

    return (
        <View style={styles.container}>
            <Text>您搜索的关键词是:{keyword}</Text>
            <TextInput
                style={styles.input}
                placeholder="请输入要搜索的课程"
                value={keyword}
                onChangeText={setKeyword}
            />
            {/* 渲染课程列表 */}
        </View>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        padding: 16,
    },
    input: {
        height: 40,
        borderColor: 'gray',
        borderWidth: 1,
        marginBottom: 16,
        paddingHorizontal: 8,
    },
});

优势分析:

  1. 代码简洁:组件只关注UI渲染,业务逻辑被完全抽离。

  2. 复用性强:任何需要数据请求的组件都可以使用这个Hook。

  3. 一致性:所有组件都遵循相同的数据请求模式,便于维护。

  4. 可测试性:Hook可以单独测试,组件测试更加简单。

五、用法

1. 依赖项处理

在useEffect中,我们使用了JSON.stringify(params)来避免因对象引用变化导致的重复请求。这是因为:

jsx 复制代码
// 这样会导致问题
useEffect(() => {
    fetchData();
}, [url, params]); // params对象每次都是新的引用

// 正确的做法
useEffect(() => {
    const paramsStr = JSON.stringify(params);
    fetchData();
}, [url, paramsStr]); // 依赖字符串而非对象

2. 取消请求

在实际应用中,我们还需要考虑组件卸载时取消未完成的请求:

jsx 复制代码
const useFetchData = (url, params = {}) => {
    // ...其他代码...
    
    useEffect(() => {
        let isMounted = true;
        const paramsStr = JSON.stringify(params);
        
        const fetchData = async () => {
            try {
                setLoading(true);
                setError(false);
                const { data: responseData } = await get(url, params);
                if (isMounted) {
                    setData(responseData);
                }
            } catch (err) {
                if (isMounted) {
                    setError(true);
                }
            } finally {
                if (isMounted) {
                    setLoading(false);
                }
            }
        };

        fetchData();

        return () => {
            isMounted = false;
            // 这里也可以使用axios的CancelToken或其他取消机制
        };
    }, [url, paramsStr]);

    // ...其他代码...
};

3. 类型安全(使用TypeScript)

如果使用TypeScript,我们可以为Hook添加类型定义:

typescript 复制代码
interface UseFetchDataResult<T> {
    data: T;
    loading: boolean;
    error: boolean;
    onReload: () => Promise<void>;
    setData: React.Dispatch<React.SetStateAction<T>>;
}

function useFetchData<T = any>(url: string, params?: object): UseFetchDataResult<T>;

总结

自定义HookReact中强大的代码复用机制。通过将数据请求逻辑封装到useFetchData Hook中,我们获得了以下好处:

  1. 关注点分离UI组件只负责渲染,业务逻辑由Hook处理。
  2. 代码复用:多个组件可以共享相同的数据请求逻辑。
  3. 一致性:所有组件遵循相同的数据处理模式。
  4. 可维护性:业务逻辑集中管理,修改时只需改动一处。

理解并掌握自定义Hook的使用,当然,扩展这个Hook,比如添加缓存、请求节流、错误重试等功能。自己去封装一些hook,提升我们的React开发效率和代码质量。

相关推荐
寅时码36 分钟前
我开源了一款 Canvas “瑞士军刀”,十几种“特效与工具”开箱即用
前端·开源·canvas
CF14年老兵38 分钟前
🚀 React 面试 20 题精选:基础 + 实战 + 代码解析
前端·react.js·redux
CF14年老兵39 分钟前
2025 年每个开发人员都应该知道的 6 个 VS Code AI 工具
前端·后端·trae
十五_在努力43 分钟前
参透 JavaScript —— 彻底理解 new 操作符及手写实现
前端·javascript
拾光拾趣录1 小时前
🔥99%人答不全的安全链!第5问必翻车?💥
前端·面试
IH_LZH1 小时前
kotlin小记(1)
android·java·前端·kotlin
lwlcode1 小时前
前端大数据渲染性能优化 - 分时函数的封装
前端·javascript
Java技术小馆1 小时前
MCP是怎么和大模型交互
前端·面试·架构
玲小珑1 小时前
Next.js 教程系列(二十二)代码分割与打包优化
前端·next.js
coding随想1 小时前
HTML5插入标记的秘密:如何高效操控DOM而不踩坑?
前端·html