原文链接:https://www.robinwieruch.de/react-hooks-fetch-data
作者简介:https://overreacted.io/zh-hans/my-decade-in-review/
如果我们要请求一个接口获得数据,并要遍历到当前页面中渲染出来,可能会这么写
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(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
};
fetchData();
}, []);
return (
// 数据展示区
{
data.hits}
);
}
export default App;
如果我们需要修改地址中的查询参数,用一个输入框和一个提交按钮实现。点击按钮时改变url,从而使副作用拉取新的数据,代码如下
export default function BestFetchData() {
const [data, setData] = useState(0)
const [query, setQuery] = useState('')
const [url, setUrl] = useState('http://wthrcdn.etouch.cn/weather_mini?city=青岛')
useEffect(() => {
const fetchData = async () => {
const ret = await axios(url)
setData(ret.data.data)
}
fetchData()
}, [url])
return (
<div>
<h3>可输入内容再搜索</h3>
<input value={
query} onChange={
e => setQuery(e.target.value)} />
<button type='button' onClick={
() => setUrl('http://wthrcdn.etouch.cn/weather_mini?city=' + query)}>搜索</button>
{
data && <>
// 数据展示区
</>}
</div>
)
}
下一步,我们加入loading和错误捕获功能,并通过表单提交事件来实现搜索
export default function () {
const [data, setData] = useState('')
const [query, setQuery] = useState('')
const [url, setUrl] = useState('http://wthrcdn.etouch.cn/weather_mini?city=青岛')
const [isLoading, setIsLoading] = useState(false)
const [isError, setIsError] = useState(false)
useEffect(() => {
const fetchData = async () => {
setIsLoading(true)
setIsError(false)
try{
const ret = await axios(url)
setData(ret.data.data)
}catch(e){
setIsError(true)
}
setIsLoading(false)
}
fetchData()
}, [url])
return (
<div>
<h3>loading+错误捕捉+表单提交</h3>
<p>输入内容后可直接按回车</p>
<form onSubmit={
(e) =>{
e.preventDefault(); setUrl('http://wthrcdn.etouch.cnn/weather_mini?city2==' + query)}}>
<input value={
query} onChange={
e => setQuery(e.target.value)} />
<button type='submit'>搜索</button>
</form>
{
isError&&<div style={
{
background:'pink',padding:'1rem',margin:'1rem'}}>出错啦~~</div>}
{
data && (isLoading
? <div> loading...</div>
: <>
// 数据展示区
</>)}
</div>
)
}
可以使用自定义hooks来把请求相关逻辑进行封装,以便复用。useFetchData 只在乎逻辑处理而不关心数据。
const useFetchData = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl)
const [data, setData] = useState(initialData)
const [isLoading, setIsLoading] = useState(false)
const [isError, setIsError] = useState(false)
useEffect(() => {
const fetchData = async () => {
setIsLoading(true)
setIsError(false)
try {
const ret = await axios(url)
setData(ret.data.data)
} catch (e) {
setIsError(true)
}
setIsLoading(false)
}
fetchData()
}, [url])
return [{
data, isLoading, isError }, setUrl]
}
export default function () {
const [query, setQuery] = useState('')
const [{
data, isLoading, isError }, doFetch] = useFetchData('http://wthrcdn.etouch.cn/weather_mini?city=青岛', null)
return (
<div>
<h3>抽取自定义hooks为公用</h3>
<p>输入内容后可直接按回车</p>
<form onSubmit={
(e) => {
e.preventDefault(); doFetch('http://wthrcdn.etouch.cn/weather_mini?city=' + query) }}>
<input value={
query} onChange={
e => setQuery(e.target.value)} />
<button type='submit'>搜索</button>
</form>
{
isError && <div style={
{
background: 'pink', padding: '1rem', margin: '1rem' }}>出错啦~~</div>}
{
data && (isLoading
? <div> loading...</div>
: <>
// 数据展示区
</>)}
</div>
)
}
可以使用reducer把自定义hook里面比较独立的逻辑再次封装
const dataFetchReducer = (state, action) => {
switch (action.type) {
case 'FETCH_INIT':
return {
...state, isLoading: true, isError: false }
case 'FETCH_SCUESS':
return {
...state, isError: false, isLoading: false, data: action.payload }
case 'FETCH_FAILURE':
return {
...state, isLoading: false, isError: true }
default:
throw new error();
}
}
const useDataApi = (initialUrl, initialData) => {
const [state, dispatch] = useReducer(dataFetchReducer, {
data: initialData,
isLoading: false,
isError: false
})
const {
data, isLoading, isError } = state
const [url, setUrl] = useState(initialUrl)
useEffect(() => {
const fetchData = async () => {
dispatch({
type: 'FETCH_INIT' })
try {
const ret = await axios(url)
dispatch({
type: 'FETCH_SCUESS', payload: ret.data.data })
} catch (e) {
dispatch({
type: 'FETCH_FAILURE' })
}
}
fetchData()
}, [url])
return [state, setUrl]
}
export default function () {
const [query, setQuery] = useState('')
const commonUrl = 'http://wthrcdn.etouch.cn/weather_mini?city='
const [{
data, isLoading, isError }, doFetch] = useDataApi(commonUrl + '青岛', null)
return (
<div>
<h3>使用reducer</h3>
<p>输入内容后可直接按回车</p>
<form onSubmit={
(e) => {
e.preventDefault()
doFetch(commonUrl + query)
}}>
<input value={
query} onChange={
e => setQuery(e.target.value)} />
<button type='submit'>搜索</button>
</form>
{
isError && <div style={
{
background: 'pink', padding: '1rem', margin: '1rem' }}>出错啦~~</div>}
{
data && (isLoading
? <div> loading...</div>
: <>
// 数据展示区
</>)}
</div>
)
}
结束