如何使用 React hooks 获取 api 接口数据

来源:http://www.chinese-glasses.com 作者:Web前端 人气:71 发布时间:2020-03-24
摘要:时间: 2019-11-09阅读: 116标签: 源码1 引言 时间: 2019-08-22阅读: 169标签: 接口 取数是前端业务的重要部分,也经历过几次演化: 在本教程中,我想向你展示如何使用 state 和 effect 钩子在Re

时间: 2019-11-09阅读: 116标签: 源码1 引言

时间: 2019-08-22阅读: 169标签: 接口

取数是前端业务的重要部分,也经历过几次演化:

在本教程中,我想向你展示如何使用 state 和 effect 钩子在React中获取数据。 你还将实现自定义的 hooks 来获取数据,可以在应用程序的任何位置重用,也可以作为独立节点包在npm上发布。

fetch的兼容性已经足够好,足以替换包括$.post在内的各种取数封装。原生用得久了,发现拓展性更好、支持 ssr 的同构取数方案也挺好,比如isomorphic-fetch、axios。对于数据驱动场景还是不够,数据流逐渐将取数封装起来,同时针对数据驱动状态变化管理进行了dataisLoadingerror封装。Hooks 的出现让组件更 Reactive,我们发现取数还是优雅回到了组件里,swr就是一个教科书般的例子。

如果你对 React 的新功能一无所知,可以查看React hooks的相关 api 介绍。如果你想查看完整的如何使用 React Hooks 获取数据的项目代码,可以查看github 的仓库

swr在 2019.10.29 号提交,仅仅 12 天就攒了 4000+ star,平均一天收获 300+ star!本周精读就来剖析这个库的功能与源码,了解这个 React Hooks 的取数库的 Why How 与 What。

如果你只是想用 React Hooks 进行数据的获取,直接npm i use-data-api并根据文档进行操作。如果你使用他,别忘记给我个star 哦~

2 概述

注意:将来,React Hooks 不适用于 React 中获取数据。一个名为Suspense的功能将负责它。以下演练是了解React中有关 state 和 Effect hooks 的更多信息的好方法。

首先介绍 swr 的功能。

使用 React hooks 获取数据

为了和官方文档有所区别,笔者以探索式思路介绍这个它,但例子都取自官方文档。

如果您不熟悉React中的数据提取,请查看我在React文章中提取的大量数据。 它将引导您完成使用React类组件的数据获取,如何使用Render Prop 组件和高阶组件来复用这些数据,以及它如何处理错误以及 loading 的。

2.1 为什么用 Hooks 取数

 import React, { useState } from 'react'; function App() { const [data, setData] = useState({ hits: [] }); return ( ul {data.hits.map(item = ( li key={item.objectID} a href={item.url}{item.title}/a /li ))} /ul ); } export default App;

首先回答一个根本问题:为什么用 Hooks 替代 fetch 或数据流取数?

App 组件显示了一个项目列表(hits=Hacker News 文章)。状态和状态更新函数来自useState 的 hook。他是来负责管理我们这个 data 的状态的。userState 中的第一个值是data 的初始值。其实就是个解构赋值。

因为Hooks 可以触达 UI 生命周期,取数本质上是 UI 展示或交互的一个环节。用 Hooks 取数的形式如下:

这里我们使用 axios 来获取数据,当然,你也可以使用别的开源库。

import useSWR from "swr";function Profile() { const { data, error } = useSWR("/api/user", fetcher); if (error) return divfailed to load/div; if (!data) return divloading.../div; return divhello {data.name}!/div;}
import React, { useState, useEffect } from 'react';import axios from 'axios';function App() { const [data, setData] = useState({ hits: [] }); useEffect(async () = { const result = await axios( '?query=redux', ); setData(result.data); }); return ( ul {data.hits.map(item = ( li key={item.objectID} a href={item.url}{item.title}/a /li ))} /ul );}export default App;

首先看到的是,以同步写法描述了异步逻辑,这是因为渲染被执行了两次。

这里我们使用 useEffect 的 effect hook 来获取数据。并且使用 useState 中的 setData 来更新组件状态。

useSWR接收三个参数,第一个参数是取数key,这个key会作为第二个参数fetcher的第一个参数传入,普通场景下为 URL,第三个参数是配置项。

但是如上代码运行的时候,你会发现一个特别烦人的循环问题。effect hook 的触发不仅仅是在组件第一次加载的时候,还有在每一次更新的时候也会触发。由于我们在获取到数据后就进行设置了组件状态,然后又触发了 effect hook。所以就会出现死循环。很显然,这是一个 bug!我们只想在组件第一次加载的时候获取数据,这也就是为什么你可以提供一个空数组作为useEffect的第二个参数以避免在组件更新的时候也触它。当然,这样的话,也就是在组件加载的时候触发。

Hooks 的威力还不仅如此,上面短短几行代码还自带如下特性:

 import React, { useState, useEffect } from 'react'; import axios from 'axios'; function App() { const [data, setData] = useState({ hits: [] }); useEffect(async () = { const result = await axios( '?query=redux', ); setData(result.data); }, []); return ( ul {data.hits.map(item = ( li key={item.objectID} a href={item.url}{item.title}/a /li ))} /ul ); } export default App;

可自动刷新。组件被销毁再渲染时优先启用本地缓存。在列表页中浏览器回退可以自动记忆滚动条位置。tabs 切换时,被 focus 的 tab 会重新取数。

第二个参数可以用来定义 hook 所依赖的所有变量(在这个数组中),如果其中一个变量发生变化,则就会触发这个 hook 的运行。如果传递的是一个空数组,则仅仅在第一次加载的时候运行。

当然,自动刷新或重新取数也不一定是我们想要的,swr允许自定义配置。

是不是感觉 ,干了shouldComponentUpdate的事情

2.2 配置

这里还有一个陷阱。在这个代码里面,我们使用async/await去获取第三方的 API 的接口数据,根据文档,每一个async都会返回一个 promise:async函数声明定义了一个异步函数,它返回一个 AsyncFunction 对象。异步函数是通过事件循环异步操作的函数,使用隐式的 Promise 返回结果然而,effect hook 不应该返回任何内容,或者清除功能。这也就是为啥你看到这个警告:07:41:22.910 index.js:1452 Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () = …) are not supported, but you can call an async function inside an effect.. ``

上面提到,useSWR还有第三个参数作为配置项。

这就是为什么我们不能在useEffect中使用async的原因。但是我们可以通过如下方法解决:

独立配置

 import React, { useState, useEffect } from 'react'; import axios from 'axios'; function App() { const [data, setData] = useState({ hits: [] }); useEffect(() = { const fetchData = async () = { const result = await axios( '?query=redux', ); setData(result.data); }; fetchData(); }, []); return ( ul {data.hits.map(item = ( li key={item.objectID} a href={item.url}{item.title}/a /li ))} /ul ); } export default App;

通过第三个参数为每个useSWR独立配置:

如上就是通过 React hooks 来获取 API 数据。但是,如果你对错误处理、loading、如何触发从表单中获取数据或者如何实现可重用的数据获取的钩子。请继续阅读。

useSWR("/api/user", fetcher, {revalidateOnFocus:false});

如何自动或者手动的触发 hook? (How to trigger a hook programmatically/manually?)

配置项可以参考文档。

目前我们已经通过组件第一次加载的时候获取了接口数据。但是,如何能够通过输入的字段来告诉 api 接口我对那个主题感兴趣呢?(就是怎么给接口传数据。这里原文说的有点啰嗦(还有 redux 关键字来混淆视听),我直接上代码吧)...

可以配置的有:suspense 模式、focus 重新取数、重新取数间隔/是否开启、失败是否重新取数、timeout、取数成功/失败/重试时的回调函数等等。

 ... function App() { const [data, setData] = useState({ hits: [] }); const [query, setQuery] = useState('redux'); useEffect(() = { const fetchData = async () = { const result = await axios( `?query=${query}`, ); setData(result.data); }; fetchData(); }, []); return ( ... ); } export default App;

第二个参数如果是 object 类型,则效果为配置项,第二个 fetcher 只是为了方便才提供的,在 object 配置项里也可以配置 fetcher。

这里我跳过一段,原文实在说的太细了。

全局配置

缺少一件:当你尝试输入字段键入内容的时候,他是不会再去触发请求的。因为你提供的是一个空数组作为useEffect的第二个参数是一个空数组,所以effect hook 的触发不依赖任何变量,因此只在组件第一次加载的时候触发。所以这里我们希望当 query 这个字段一改变的时候就触发搜索

SWRConfig可以批量修改配置:

...function App() { const [data, setData] = useState({ hits: [] }); const [query, setQuery] = useState('redux'); useEffect(() = { const fetchData = async () = { const result = await axios( `?query=${query}`, ); setData(result.data); }; fetchData(); }, [query]); return ( ... );}export default App;
import useSWR, { SWRConfig } from "swr";function Dashboard() { const { data: events } = useSWR("/api/events"); // ...}function App() { return ( SWRConfig value={{ refreshInterval: 3000 }} Dashboard / /SWRConfig );}

如上,我们只是把query作为第二个参数传递给了 effect hook,这样的话,每当 query 改变的时候就会触发搜索。但是,这样就会出现了另一个问题:每一次的query 的字段变动都会触发搜索。如何提供一个按钮来触发请求呢?

独立配置优先级高于全局配置,在精读部分会介绍实现方式。

function App() { const [data, setData] = useState({ hits: [] }); const [query, setQuery] = useState('redux'); const [search, setSearch] = useState('redux'); useEffect(() = { const fetchData = async () = { const result = await axios( `?query=${search}`, ); setData(result.data); }; fetchData(); }, [search]); return ( Fragment input type="text" value={query} onChange={event = setQuery(event.target.value)} / button type="button" onClick={() = setSearch(query)} Search /button ul {data.hits.map(item = ( li key={item.objectID} a href={item.url}{item.title}/a /li ))} /ul /Fragment );}

最重量级的配置项是fetcher,它决定了取数方式。

搜索的状态设置为组件的初始化状态,组件加载的时候就要触发搜索,类似的查询和搜索状态易造成混淆,为什么不把实际的 URL 设置为状态而不是搜索状态呢?

2.3 自定义取数方式

function App() { const [data, setData] = useState({ hits: [] }); const [query, setQuery] = useState('redux'); const [url, setUrl] = useState( '?query=redux', ); useEffect(() = { const fetchData = async () = { const result = await axios(url); setData(result.data); }; fetchData(); }, [url]); return ( Fragment input type="text" value={query} onChange={event = setQuery(event.target.value)} / button type="button" onClick={() = setUrl(`?query=${query}`) }  Search /button ul {data.hits.map(item = ( li key={item.objectID} a href={item.url}{item.title}/a /li ))} /ul /Fragment );}

自定义取数逻辑其实分几种抽象粒度,比如自定义取数 url,或自定义整个取数函数,而swr采取了相对中间粒度的自定义fetcher:

这是一个使用 effect hook 来获取数据的一个例子,你可以决定 effect hook 所以依赖的状态。一旦你点击或者其他的什么操作 setState 了,那么 effect hook 就会运行。但是这个例子中,只有当你的 url 发生变化了,才会再次去获取数据。

import fetch from "unfetch";const fetcher = url = fetch(url).then(r = r.json());function App() { const { data } = useSWR("/api/data", fetcher); // ...}

在 Effect Hook 中使用 Loading(Loading Indicator with React Hooks)

所以fetcher本身就是一个拓展点,我们不仅能自定义取数函数,自定义业务处理逻辑,甚至可以自定义取数协议:

这里让我们来给程序添加一个 loading(加载器),这里需要另一个 state

import { request } from "graphql-request";const API = "";const fetcher = query = request(API, query);function App() { const { data, error } = useSWR( `{ Movie(title: "Inception") { releaseDate actors { name } } }`, fetcher ); // ...}
import React, { Fragment, useState, useEffect } from 'react';import axios from 'axios';function App() { const [data, setData] = useState({ hits: [] }); const [query, setQuery] = useState('redux'); const [url, setUrl] = useState( '?query=redux', ); const [isLoading, setIsLoading] = useState(false); useEffect(() = { const fetchData = async () = { setIsLoading(true); const result = await axios(url); setData(result.data); setIsLoading(false); }; fetchData(); }, [url]); return ( Fragment input type="text" value={query} onChange={event = setQuery(event.target.value)} / button type="button" onClick={() = setUrl(`?query=${query}`) }  Search /button {isLoading ? ( divLoading .../div ) : ( ul {data.hits.map(item = ( li key={item.objectID} a href={item.url}{item.title}/a /li ))} /ul )} /Fragment );}export default App;

这里回应了第一个参数称为取数 Key 的原因,在 graphql 下它则是一段语法描述。

代码比较简单,不解释了使用 Effect Hook 添加错误处理(Error Handling with React Hooks)

到这里,我们可以自定义取数函数,但却无法控制何时取数,因为 Hooks 写法使取数时机与渲染时机结合在一起。swr的条件取数机制可以解决这个问题。

如何在 Effect Hook 中做一些错误处理呢?错误仅仅是一个 state ,一旦程序出现了 error state,则组件需要去渲染一些feedback 给用户。当我们使用async/await的时候,我们可以使用try/catch,如下:

2.4 条件取数

import React, { Fragment, useState, useEffect } from 'react';import axios from 'axios';function App() { const [data, setData] = useState({ hits: [] }); const [query, setQuery] = useState('redux'); const [url, setUrl] = useState( '?query=redux', ); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); useEffect(() = { const fetchData = async () = { setIsError(false); setIsLoading(true); try { const result = await axios(url); setData(result.data); } catch (error) { setIsError(true); } setIsLoading(false); }; fetchData(); }, [url]); return ( Fragment input type="text" value={query} onChange={event = setQuery(event.target.value)} / button type="button" onClick={() = setUrl(`?query=${query}`) }  Search /button {isError  divSomething went wrong .../div} {isLoading ? ( divLoading .../div ) : ( ul {data.hits.map(item = ( li key={item.objectID} a href={item.url}{item.title}/a /li ))} /ul )} /Fragment );}export default App;

所谓条件取数,即useSWR第一个参数为 null 时则会终止取数,我们可以用三元运算符或函数作为第一个参数,使这个条件动态化:

每一次 effect hook 运行的时候都需要重置一下 error state,这是非常有必要的。因为用户可能想再发生错误的时候想再次尝试一下。

// conditionally fetchconst { data } = useSWR(shouldFetch ? "/api/data" : null, fetcher);// ...or return a falsy valueconst { data } = useSWR(() = (shouldFetch ? "/api/data" : null), fetcher);

说白了,界面给用户反馈更加的友好使用 React 中 Form 表单获取数据(Fetching Data with Forms and React)

上例中,当shouldFetch为 false 时则不会取数。

function App() { ... return ( Fragment form onSubmit={event = { setUrl(`?query=${query}`); event.preventDefault(); }} input type="text" value={query} onChange={event = setQuery(event.target.value)} / button type="submit"Search/button /form {isError  divSomething went wrong .../div} ... /Fragment );}

第一个取数参数推荐为回调函数,这样swr会 catch 住内部异常,比如:

为了防止浏览器的 reload,我们这里加了一个event.preventDefalut(),然后别的操作就是正常表单的操作了

// ... or throw an error when user.id is not definedconst { data, error } = useSWR(() = "/api/data?u/api/user"); const { data: projects } = useSWR(() = "/api/projects?uloading..."; return "You have " + projects.length + " projects";}

自定义获取数据的 hook(Custom Data Fetching Hook)其实就是请求的封装

swr会尽可能并行没有依赖的请求,并按依赖顺序一次发送有依赖关系的取数。

为了能够提取自定义的请求 hook,除了属于输入框的 query 字段,别的包括 loading 加载器、错误处理函数都要包括在内。当然,你需要确保 App Component 所需的所有字段在你自定义的 hook 中都有返回

可以想象,如果手动管理取数,当依赖关系复杂时,为了确保取数的最大可并行,往往需要精心调整取数递归嵌套结构,而在swr的环境下只需顺序书写即可,这是很大的效率提升。优化方式在下面源码解读章节详细说明。

const useHackerNewsApi = () = { const [data, setData] = useState({ hits: [] }); const [url, setUrl] = useState( '?query=redux', ); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); useEffect(() = { const fetchData = async () = { setIsError(false); setIsLoading(true); try { const result = await axios(url); setData(result.data); } catch (error) { setIsError(true); } setIsLoading(false); }; fetchData(); }, [url]); return [{ data, isLoading, isError }, setUrl];}

依赖取数是自动重新触发取数的一种场景,其实swr还支持手动触发重新取数。

现在,我们可以将你的新 hook 继续放到组件中使用

2.6 手动触发取数

function App() { const [query, setQuery] = useState('redux'); const [{ data, isLoading, isError }, doFetch] = useHackerNewsApi(); return ( Fragment form onSubmit={event = { doFetch(`?query=${query}`); event.preventDefault(); }} input type="text" value={query} onChange={event = setQuery(event.target.value)} / button type="submit"Search/button /form ... /Fragment );}

本文由10bet发布于Web前端,转载请注明出处:如何使用 React hooks 获取 api 接口数据

关键词:

上一篇:TypeScript基础类型

下一篇:没有了

频道精选

最火资讯