如何正确的在useEffect里请求数据

您所在的位置:网站首页 axios请求数据渲染页面 如何正确的在useEffect里请求数据

如何正确的在useEffect里请求数据

2023-08-08 19:37| 来源: 网络整理| 查看: 265

一直以来,我将useEffect看作是生命周期.直到前段时间在项目中使用useEffect出现了重复请求的问题,翻阅了相关文章让我对useEffect有了新的认识.

请求数据大概分为两种情况:第一种只在页面初始化请求数据;第二种是根据外部的变化来请求数据(比如props或者state的变化).

页面初始化请求数据

effect的第二个参数为[],表示没有依赖项,effect只会在初次渲染执行.

function SearchResults() { const [data, setData] = useState({ hits: [] }); useEffect(() => { const fetchData async () => { const result = await axios( 'https://hn.algolia.com/api/v1/search?query=react', ); setData(result.data); } fetchData(); }, []); // ... 复制代码

你可能会想将getFetchUrl 移到effect外部,复用逻辑

function SearchResults() { const [data, setData] = useState({ hits: [] }); funcyion fetchData(){ async () => { const result = await axios( 'https://hn.algolia.com/api/v1/search?query=react' ); setData(result.data); } } useEffect(() => { fetchData(); }, []); 复制代码

此时effect第二个参数设置为[]时会出现提示:遗漏了fetchData依赖。在这种情况下应该忽略对函数的依赖吗?effect不应该对它的依赖撒谎。

于是我们指定effect依赖

function SearchResults() { const [data, setData] = useState({ hits: [] }); funcyion fetchData(){ async () => { const result = await axios( 'https://hn.algolia.com/api/v1/search?query=react' ); setData(result.data); } } useEffect(() => { fetchData(); }, [fetchData]); 复制代码

此时,一切看起来很完美,那如果添加一个计时器会发生什么呢? 以下🌰仅仅用来测试!!!

🌰:出现了无限重复请求

function SearchResults() { const [data, setData] = useState({ hits: [] }); const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => {setCount(count + 1);}, 1000); return () => clearInterval(id); }, [count]); funcyion fetchData(){ async () => { const result = await axios( 'https://hn.algolia.com/api/v1/search?query=react' ); setData(result.data); } } useEffect(() => { fetchData(); }, [fetchData]); 复制代码

count发生了变化,引起组件渲染,effect会在每次渲染后执行一次,然后在effect中更新了count状态引起渲染并再次触发effect。

你应该会问请求数据这个依赖一直没变为什么会引起effect执行请求数据的操作

一个常见的误解是,“函数从来不会改变”。这显然不是事实。实际上,在组件内定义的函数每一次渲染都在变。

当props或者state发生变化,组件会重新渲染,每一次渲染都有属于它自己的所有,包括函数,所以每次组件渲染 effect认为它所依赖的函数发现变化就会去执行对应的操作。

我们可以使用 useCallback hook包装函数

function SearchResults() { const fetchData = useCallback( async ()=>{ const result = await axios( 'https://hn.algolia.com/api/v1/search?query=react' ); setData(result.data); },[]) useEffect(() => { fetchData(); }, [fetchData]); } 复制代码

useCallback本质上是添加了一层依赖检查。它以另一种方式解决了问题 - 我们使函数本身只在需要的时候才改变,而不是去掉对函数的依赖。

根据外部的变化来请求数据 function SearchResults() { const [data, setData] = useState({ hits: [] }); const [query, setQuery] = useState('react'); useEffect(() => { const fetchData async () => { const result = await axios( 'https://hn.algolia.com/api/v1/search?query='+query ); setData(result.data); } fetchData(); }, [query]); // ... 复制代码

使用useCallback 将getFetchUrl 移到effects外部

function SearchResults() { const fetchData = useCallback(async ()=>{ const result = await axios( 'https://hn.algolia.com/api/v1/search?query='+query ); setData(result.data); },[query]) // 指定依赖 useEffect(() => { fetchData(); }, [fetchData]); } 复制代码

fetchData使用到了query就需要在useCallback定义依赖,query变化才执行fetchData操作.

一个完整的🌰

以下将从手动触发请求获取数据、错误处理、加载显示、如何实现可重用的数据获取自定义hooks这几个方面来实现

普通写法 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( 'https://hn.algolia.com/api/v1/search?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 ( setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`); event.preventDefault(); } > setQuery(event.target.value)} /> Search {isError && Something went wrong ...} {isLoading ? ( Loading ... ) : ( {data.hits.map(item => ( {item.title} ))} )} ); } export default App; 复制代码 自定义hooks

将属于数据获取的所有内容(属于输入字段的查询状态除外,但包括加载指示器和错误处理)放入自定义hooks中,组件无需知道相关的数据逻辑。

const useDataApi = (initialUrl, initialData) => { const [data, setData] = useState(initialData); const [url, setUrl] = useState(initialUrl); 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]; }; function App() { const [query, setQuery] = useState('redux'); const [{ data, isLoading, isError }, doFetch] = useDataApi( 'https://hn.algolia.com/api/v1/search?query=redux', { hits: [] }, ); return ( { doFetch( `http://hn.algolia.com/api/v1/search?query=${query}`, ); event.preventDefault(); }} > setQuery(event.target.value)} /> Search {isError && Something went wrong ...} {isLoading ? ( Loading ... ) : ( {data.hits.map(item => ( {item.title} ))} )} ); } export default App; 复制代码

这是demo这就是使用自定义hooks获取数据的过程。hooks本身对 API 一无所知。它从外部接收所有参数,只管理必要的状态,例如数据、加载和错误状态。它暴露结果和操作给组件使用。

但是useDataApi还未做到状态与操作的解耦,接下来我们使用useReducer来做进一步优化

状态与操作解耦

Reducer Hook 返回一个状态对象和一个改变状态对象的函数。该函数——称为调度函数——接受一个具有类型和可选负载的动作以及状态对象的初始值。

import React, { Fragment, useState, useEffect, useReducer, } from 'react'; import axios from 'axios'; // 操作 const dataFetchReducer = (state, action) => { switch (action.type) { case 'FETCH_INIT': return { ...state, isLoading: true, isError: false }; case 'FETCH_SUCCESS': return { ...state, isLoading: false, isError: false, data: action.payload, }; case 'FETCH_FAILURE': return { ...state, isLoading: false, isError: true, }; default: throw new Error(); } }; const useDataApi = (initialUrl, initialData) => { const [url, setUrl] = useState(initialUrl); const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, isError: false, data: initialData, }); useEffect(() => { const fetchData = async () => { dispatch({ type: 'FETCH_INIT' }); try { const result = await axios(url); dispatch({ type: 'FETCH_SUCCESS', payload: result.data }); } catch (error) { dispatch({ type: 'FETCH_FAIL' }); } }; fetchData(); }, [url]); return [state, setUrl]; }; 复制代码

现在effect的依赖只剩下url了。相比于直接在effect里面读取所有状态,它dispatch了一个action来描述发生了什么。这使得我们的effect和isLoading、isError、data状态解耦。effect不再关心怎么更新状态,它只负责告诉我们发生了什么。更新的逻辑全都交由dataFetchReducer去统一处理:这里是完整demo

总结 建议将仅被effect使用的函数放到effect里面 如果函数作为effect依赖,那么需要在定义函数的地方用useCallback包一层,你也可以使用useMemo来处理 不要盲目的忽视依赖而使用[],你可以通过useReducer 和 useCallback来优化操作或者移除某些effect依赖


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3