发布于 2026-01-06 1 阅读
0

正在获取数据并创建自定义钩子。🪝 正在获取数据并创建自定义钩子。DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

获取数据并创建自定义钩子。🪝

获取数据并创建自定义钩子

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

本文旨在介绍如何使用 React 和自定义 hook 来发出 HTTP GET 类型的请求。

🚨 注意:本文要求您了解 React 的基础知识(基本 hooks 和 fetch 请求)。

任何形式的反馈都非常欢迎,谢谢!希望您喜欢这篇文章。🤗

 

目录。

📌使用的技术

📌创建项目

📌第一步

📌进行第一次 fetch

📌在屏幕上显示 API 数据

📌创建自定义钩子

📌改进useFetch钩子

📌添加更多组件和重构

📍 Header.tsx

📍 Loading.tsx

📍 ErrorMessage.tsx

📍 Card.tsx

📍 LayoutCards.tsx


🚨 使用的技术。

▶️ React JS(版本 18)

▶️ Vite JS

▶️ TypeScript

▶️瑞克和莫蒂 API

▶️原生CSS(样式表可在本文末尾的仓库中找到

 

〽️ 创建项目。

npm init vite@latest
Enter fullscreen mode Exit fullscreen mode

在这种情况下,我们将它命名为:(fetching-data-custom-hook可选)。

我们将选择 React,然后选择 TypeScript。

然后我们执行以下命令来导航到刚刚创建的目录。

cd fetching-data-custom-hook
Enter fullscreen mode Exit fullscreen mode

然后我们安装依赖项:

npm install
Enter fullscreen mode Exit fullscreen mode

然后我们在代码编辑器(我这里用的是 VS Code)中打开项目。

code .
Enter fullscreen mode Exit fullscreen mode

 

〽️ 第一步。

在src/App.tsx文件夹中,我们删除该文件的所有内容,并放置一个显示标题和副标题的功能组件。

const App = () => {
  return (
            <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
  )
}
export default App;

Enter fullscreen mode Exit fullscreen mode

显示标题和副标题

首先,我们将创建几个接口,以帮助我们自动完成 API 提供的 JSON 响应中的属性。

  • 第一个接口Response包含 results 属性,该属性是一个 Results 数组。
  • 第二个界面仅包含 3 个属性(虽然还有更多,您可以查看APIResult文档),选择角色的 ID、名称和图像。
interface Response {
  results: Result[]
}

interface Result {
  id: number;
  name: string;
  image: string;
}

Enter fullscreen mode Exit fullscreen mode

 

〽️ 制作我们的第一个 Fetch。

  1. 首先,我们添加一个状态,其类型为[状态名称] Result[],默认值为空数组,因为我们尚未发起 API 调用。这将用于存储 API 数据并使其能够显示。
const App = () => {

  const [data, setData] = useState<Result[]>([]);

  return (
        <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
  )
}
export default App;

Enter fullscreen mode Exit fullscreen mode
  1. 为了执行数据获取,我们必须在 `<head>` 中执行useEffect,因为我们需要在我们的组件第一次渲染时执行获取操作。

由于我们只需要它执行一次,所以我们放置一个空数组(即没有任何依赖项)。

const App = () => {
  const [data, setData] = useState<Result[]>([]);

    useEffect(()=> {

    },[]) // arreglo vació

  return (
    <div>
      <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
    </div>
  )
}
export default App;

Enter fullscreen mode Exit fullscreen mode
  1. 在函数体内部useEffect,将会发出 API 调用,由于useEffect不允许我们直接使用异步代码,因此我们将同时通过 Promise 发出调用。
const [data, setData] = useState<Result[]>([]);

useEffect(()=> {
   fetch('<https://rickandmortyapi.com/api/character/?page=8>')
   .then( res => res.json())
   .then( (res: Response) => {})
   .catch(console.log)
},[])

Enter fullscreen mode Exit fullscreen mode
  1. 一旦承诺得到解决,我们将获得与 API 对应的数据,并通过该setData函数将其放入状态中。

这样我们就可以把数据显示在屏幕上了。😌

🚨 如果 API 出现问题,catch 会捕获错误并通过控制台显示,并且状态“ ”的值data将保持为空数组(最后只会显示应用程序的标题和副标题)。

const [data, setData] = useState<Result[]>([]);

useEffect(()=> {
   fetch('<https://rickandmortyapi.com/api/character/?page=8>')
   .then( res => res.json())
   .then( (res: Response) =>  {
      setData(res.results);
   })
   .catch(console.log)
},[])

Enter fullscreen mode Exit fullscreen mode

 

〽️ 在屏幕上显示 API 数据。

在显示 API 数据之前,我们需要进行评估。🤔

🔵 只有当“ ”状态值的长度data大于 0 时,我们才会在屏幕上显示 API 数据。

🔵 如果“ ”状态值的长度data小于或等于 0,则屏幕上不会显示任何数据,只会显示标题和副标题。

const App = () => {
    const [data, setData] = useState<Result[]>([]);

    useEffect(()=> {
       fetch('<https://rickandmortyapi.com/api/character/?page=8>')
       .then( res => res.json())
       .then( (res: Response) =>  {
          setData(res.results);
       })
       .catch(console.log)
    },[])

  return (
    <div>
      <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
      {
        (data.length > 0) && <p>data</p>
      }
    </div>
  )
}
export default App;

Enter fullscreen mode Exit fullscreen mode

现在,一旦我们确认“ ”状态值中确实有数据data,我们将继续显示和处理数据。

使用数组中的 map 函数,我们将遍历状态“ ”的值数组data,并返回一个新的 JSX 组件,在本例中,该组件仅包含图像和文本。

🔴 注意: div 元素内的key属性是 React 在列表中使用的标识符,用于更高效地渲染组件。务必设置此属性。

const App = () => {
    const [data, setData] = useState<Result[]>([]);

    useEffect(()=> {
       fetch('<https://rickandmortyapi.com/api/character/?page=8>')
       .then( res => res.json())
       .then( (res: Response) =>  {
          setData(res.results);
       })
       .catch(console.log)
    },[])

  return (
    <div>
      <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
      {
        (data.length > 0) && data.map( ({ id, image, name }) => (
          <div key={id}>
            <img src={image} alt={image} />
            <p>{name}</p>
          </div>
        ))
      }
    </div>
  )
}
export default App;

Enter fullscreen mode Exit fullscreen mode

这样我们就完成了数据获取和屏幕显示。不过我们还可以做得更好。😎

展示卡
 

〽️ 创建自定义钩子。

在src/hook文件夹中,我们创建一个名为 . 的文件useFetch

我们创建了函数并删除了App.tsx组件的逻辑。

const App = () => {

  return (
    <div>
      <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
      {
        (data.length > 0) && data.map( ({ id, image, name }) => (
          <div key={id}>
            <img src={image} alt={image} />
            <p>{name}</p>
          </div>
        ))
      }
    </div>
  )
}
export default App;

Enter fullscreen mode Exit fullscreen mode

我们将逻辑粘贴到此函数中,最后返回状态“ data”的值。

export const useFetch = () => {
  const [data, setData] = useState<Result[]>([]);

  useEffect(()=> {
     fetch('<https://rickandmortyapi.com/api/character/?page=8>')
     .then( res => res.json())
     .then( (res: Response) =>  {
        setData(res.results);
     })
     .catch(console.log)
  },[]);

  return {
    data
  }
}

Enter fullscreen mode Exit fullscreen mode

最后,我们调用useFetch钩子函数来提取数据。

好了,我们的组件现在更简洁、更易读了。🤓

const App = () => {

  const { data } = useFetch();

  return (
    <div>
      <h1 className="title">Fetching data and create custom Hook</h1>
      <span className="subtitle">using Rick and Morty API</span>
      {
        (data.length > 0) && data.map( ({ id, image, name }) => (
          <div key={id}>
            <img src={image} alt={image} />
            <p>{name}</p>
          </div>
        ))
      }
    </div>
  )
}
export default App;

Enter fullscreen mode Exit fullscreen mode

等等,我们还可以改进这个钩子。🤯
 

〽️改进useFetch钩子。

现在我们要做的就是改进钩子,添加更多属性。

在现有状态的基础上,我们将添加其他属性,并且这个新状态的类型为DataState

interface DataState {
    loading: boolean;
    data: Result[];
    error: string | null;
}

Enter fullscreen mode Exit fullscreen mode

加载状态(布尔值)用于告知我们何时发起 API 调用。默认情况下,该值为 true。

如果返回错误信息(字符串或空值),则会显示错误信息。默认情况下,该值为空。

`data` 参数(类型为 `value` Result[])将显示 API 数据。默认情况下,该值为空数组。

🔴 注意:该庄园的房产已重新命名。

🔵数据➡️数据状态

🔵 setData ➡️ setDataState

export const useFetch = () => {
    const [dataState, setDataState] = useState<DataState>({
      data: [],
      loading: true,
      error: null
  });

  useEffect(()=> {
     fetch('<https://rickandmortyapi.com/api/character/?page=8>')
     .then( res => res.json())
     .then( (res: Response) =>  {
        setData(res.results);
     })
     .catch(console.log)
  },[]);

  return {
    data
  }
}

Enter fullscreen mode Exit fullscreen mode

现在我们将逻辑提取useEffect到一个单独的函数中。这个函数将被命名为handleFetch.

我们将使用useCallback, 来存储此函数,并防止在状态改变时重新创建它。

它还useCallback接收一个依赖项数组,在这种情况下,我们将它留空,因为我们只想生成一次。

const handleFetch = useCallback(
    () => {},
    [],
)

Enter fullscreen mode Exit fullscreen mode

接收参数的函数useCallback可以是异步的,因此我们可以使用async/await

  1. 首先,我们添加一个try/catch 语句来处理错误。
  2. 然后我们创建一个包含 URL 值的常量来发起 API 调用。
  3. 我们使用 fetch 进行 API 调用并发送 URL(await 允许我们等待响应,无论响应是否正确;如果出错,它将直接进入 catch 函数)。
const handleFetch = useCallback(
      async () => {
          try {
                            const url = '<https://rickandmortyapi.com/api/character/?page=18>';
              const response = await fetch(url);
          } catch (error) {}
        },
        [],
    )

Enter fullscreen mode Exit fullscreen mode
  1. 然后我们评估响应,如果出现错误,则激活 catch 并将 API 返回的错误发送出去。
  2. 在 catch 语句中,我们将设置状态。我们调用setDataState函数,并传入一个函数来获取先前的值(prev)。我们返回以下内容。
    1. 我们将之前的属性(...prev)展开,在这种情况下,它只是数据属性的值,最终会是一个空数组。
    2. 加载中,我们将其设置为 false。
    3. 错误时,我们将接收 catch 的参数 error 的值进行转换,以便能够获取消息并将其放入此属性中。
const handleFetch = useCallback(
      async () => {
          try {
                            const url = '<https://rickandmortyapi.com/api/character/?page=18>';
              const response = await fetch(url);

              if(!response.ok) throw new Error(response.statusText);

          } catch (error) {

              setDataState( prev => ({
                  ...prev,
                  loading: false,
                  error: (error as Error).message
              }));
          }
        },
        [],
    )

Enter fullscreen mode Exit fullscreen mode
  1. 如果 API 没有返回错误,我们会获取信息,并以与 catch 中类似的方式设置状态。
  2. 我们调用setDataState函数,并传入一个函数来获取先前的值(prev)。我们返回以下结果。
    1. 我们将之前的属性(...prev)展开,在这种情况下,它只会是错误属性的值,该值最终将为 null。
    2. 加载中,我们将其设置为 false。
    3. data将是访问 dataApi 计数器的 results 属性得到的值。
const handleFetch = useCallback(
      async () => {
          try {
                            const url = '<https://rickandmortyapi.com/api/character/?page=18>';
              const response = await fetch(url);

              if(!response.ok) throw new Error(response.statusText);

              const dataApi: Response = await response.json();

              setDataState( prev => ({
                  ...prev,
                  loading: false,
                  data: dataApi.results
              }));

          } catch (error) {

              setDataState( prev => ({
                  ...prev,
                  loading: false,
                  error: (error as Error).message
              }));
          }
        },
        [],
    )

Enter fullscreen mode Exit fullscreen mode

创建函数后handleFetch,我们返回到函数中useEffect,删除其中的逻辑并添加以下内容。

我们通过访问数据属性来判断状态“ dataState ”的值是否包含长度为0的数据,如果包含,则执行该函数。这样做是为了避免多次调用该函数。

useEffect(() => {
    if (dataState.data.length === 0) handleFetch();
}, []);

Enter fullscreen mode Exit fullscreen mode

钩子看起来是这样的:

🔴 注意:在钩子函数的末尾,我们使用扩展运算符返回“ dataState ”状态的值

🔴 注意:接口已移至src/interfaces目录下的各自文件夹中。

import { useState, useEffect, useCallback } from 'react';
import { DataState, Response } from '../interface';

const url = '<https://rickandmortyapi.com/api/character/?page=18>';

export const useFetch = () => {

    const [dataState, setDataState] = useState<DataState>({
        data: [],
        loading: true,
        error: null
    });

    const handleFetch = useCallback(
        async () => {
            try {
                const response = await fetch(url);

                if(!response.ok) throw new Error(response.statusText);

                const dataApi: Response = await response.json();

                setDataState( prev => ({
                    ...prev,
                    loading: false,
                    data: dataApi.results
                }));

            } catch (error) {

                setDataState( prev => ({
                    ...prev,
                    loading: false,
                    error: (error as Error).message
                }));
            }
        },
        [],
    )

    useEffect(() => {
        if (dataState.data.length === 0) handleFetch();
    }, []);

    return {
        ...dataState
    }
}

Enter fullscreen mode Exit fullscreen mode

在使用此钩子的新属性之前,我们将进行重构并创建更多组件。😳
 

〽️ 添加更多组件和重构。

首先,需要在src 目录下创建components文件夹。

在 components 文件夹内,我们创建以下文件。
 

🟡 Header.tsx

这个组件内部只会包含之前创建的标题和副标题。😉

export const Header = () => {
    return (
        <>
            <h1 className="title">Fetching data and create custom Hook</h1>
            <span className="subtitle">using Rick and Morty API</span>
        </>
    )
}

Enter fullscreen mode Exit fullscreen mode

 

🟡 加载中.tsx

只有当钩子的 loading 属性设置为 true 时,此组件才会显示。⏳

export const Loading = () => {
  return (
    <p className='loading'>Loading...</p>
  )
}

Enter fullscreen mode Exit fullscreen mode

显示加载中
 

🟡 ErrorMessage.tsx

只有当钩子函数的错误属性包含字符串值时,才会显示此组件。🚨

export const ErrorMessage = ({msg}:{msg:string}) => {
  return (
    <div className="error-msg">{msg.toUpperCase()}</div>
  )
}

Enter fullscreen mode Exit fullscreen mode

显示错误
 

🟡 Card.tsx

显示 API 数据,即图像及其文本。🖼️

import { Result } from '../interface';

export const Card = ({ image, name }:Result) => {

    return (
        <div className='card'>
            <img src={image} alt={image} width={100} />
            <p>{name}</p>
        </div>
    )
}

Enter fullscreen mode Exit fullscreen mode

 

🟡 LayoutCards.tsx

此组件用作容器,用于遍历数据属性并显示字母及其信息。🔳

🔴 注意:我们使用 memo 来包裹组件,以避免重新渲染。虽然在这个应用中可能不会注意到这一点,但这只是一个小技巧。只有当“data”属性的值发生变化时,memo 函数才会重新渲染。

import { memo } from "react"
import { Result } from "../interface"
import { Card } from "./"

interface Props { data: Result[] }

export const LayoutCards = memo(({data}:Props) => {
    return (
        <div className="container-cards">
            {
                (data.length > 0) && data.map( character => (
                    <Card {...character} key={character.id}/>
                ))
            }
        </div>
    )
})

Enter fullscreen mode Exit fullscreen mode

这就是我们App.tsx组件的样子。

我们创建一个名为showData 的函数,并对其进行评估:

  • 如果loading属性为真,则返回<Loading/>组件。
  • 如果error属性为真,则返回组件<ErrorMessage/>,并将错误发送给组件。
  • 如果所有条件都不成立,则表示 API 数据已准备就绪,我们将返回组件<LayoutCards/>并将数据发送给它以进行显示。

最后,在组件下方,我们打开括号并调用showData函数。

import { ErrorMessage, Header, Loading, LayoutCards } from './components'
import { useFetch } from './hook';

const App = () => {

  const { data, loading, error } = useFetch();

  const showData =  () => {
    if (loading) return <Loading/>
    if (error) return <ErrorMessage msg={error}/>
    return <LayoutCards data={data} />
  }

  return (
    <>
      <Header/>
      { showData() }
    </>
  )
}
export default App;

Enter fullscreen mode Exit fullscreen mode

🔴 注意:您也可以将showData函数移至 hook 中,并将 hook 文件扩展名更改为.tsx.jsx,这是因为在返回各种组件时会使用 JSX。


感谢你看到这里。🙌

如果你有兴趣,可以看看代码库。⬇️

GitHub 标志 Franklin361 /获取数据自定义钩子

本教程将介绍如何获取数据并创建自定义钩子。

获取数据并创建自定义钩子

本教程将介绍如何获取数据并创建自定义钩子。

演示

教程文章链接➡️






文章来源:https://dev.to/franklin030601/fetching-data-and-creating-a-custom-hook-32m2