前言

越来越多的项目 使用 react hooks 的方式来开发应用, 需要调用 api 请求数据, 一个网络请求的自定义 hook 显得尤为的重要

很久没用到的 redux

我接触的项目中, 大多数引入了 react-redux, redux 之类的状态管理库, 用来做 数据 与 UI 分离, 自己感觉是为了用而用, 太重又没必要,
当然, 也可能自己做的都是小项目, 体会不到好处, 既然体会不到, 为啥我要用, 以前请求个数据, 数据流要绕一大圈

首先是 view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@connect(
({data})=> ({data})
({xx}) => ({ getData: xx.getData() })
)
class Component extends React.Component {

render() {
{this.props.data}
}

componentDidMount() {
this.props.getData()
}
}

Action

1
2
3
4
5
6
function getData() {
dispatch({
type: "fuck_type",
payload: 'xx'
})
}

Reducer

1
2
3
4
5
6
7
8
9
10
11
function reducer(state) {
switch (key) {
case fuck_type:
return {
...state,
data: action.payload
}
default:
return state
}
}

这是一个最基本的 redux 代码, 还没有算上 redux-saga, 和 isLoading, isError 之类的 action,
有时候我们写代码都忘记初心了…, 那就是 我只是想请求一个数据, 现实是不管用不用 store, 我都和他 redux 的世界里走一走

单纯的发一个请求吧, 求求你了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import MyFetcher from  './index.ts'

function App() {
const [data, setData] = useState<Array<any>>([])
useEffect(() => {
(async ()=> {
const data = await MyFetcher.get('/')
setData(data)
})()
}, [])

return (
<div>{data}</div>
)
}

一个网络请求基本场景

  • 需要一个是否在请求的标识, 用来 loading 状态
  • 需要一个请求完成后的数据, 用来渲染
  • 需要一个是否错误的标识,或错误信息, 用来做错误的逻辑
  • 最好有一个支持重新请求的功能
  • 最好在组件卸载的时候, 能够自动取消当前的请求

基本实现

我们这里基于 axios 来实现, 取名 useData

首先定义三个状态机 loading , data , error

1
2
3
4
5
function useData<T extends any[] = [], E = null>() {
const [data, setData] = useState<T>(null)
const [loading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<E>(null)
}

然后声明一个 请求函数,

  • 在请求开始 设置 loadingtrue
  • 请求成功, 成功 setData
  • 请求失败, 设置 setError
  • 完成, 设置 loadingfalse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import axios from 'axios'

const instance = axios.create({
baseURL: '/',
timeout: 1000,
});

function useData<T extends any[] = [], E = null>(url: string) {
const [data, setData] = useState<T>(null)
const [loading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<E>(null)

const fetchData = useCallback((apiUrl: string) => {
async function getData() {
setData(await instance.get(apiUrl))
}
setLoading(true);
getData()
.catch(setError)
.finally(() => {
setLoading(false);
});
}, [apiUrl]);

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

return {
loading,
data,
error,
refresh: fetchData,
};
}

url 作为 useEffect 的依赖项, 这样就可以在 url 改变的时候, 自动重新拉取数据, 非常方便,
最后对 fetchData 引用, 实现 refresh 重新加载的功能

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function App() {
const {loading, data, refresh} = useData('/api')

return (
<>
{
loading
? '加载中...'
: data
}
<div onClick={refresh}>重新拉取数据</div>
</>
)
}

可以很明显的看出, React Hooks 很好的解决了 逻辑复用的问题,
我们通过这样一个 自定义 hook, 实现了一个可以重新拉取数据, 加载状态, 错误状态, 显示数据的公用hook,
然后还要一个问题, useData 内部写死了 axiosget 请求, 显然是不合理,
为了支持自定义的网络请求库, 我们可以添加第二个参数, 来实现自己的 fetcher

自定义 fetcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
+ function useData<T extends any[] = [], E = null>(url: string, fetcher: (url: string) => Promise<T>) {
const [data, setData] = useState<T>(null)
const [loading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<E>(null)

const fetchData = useCallback((apiUrl: string) => {
async function getData() {
+ setData(await fetcher(apiUrl))
}
setLoading(true);
getData()
.catch(setError)
.finally(() => {
setLoading(false);
});
}, [apiUrl]);

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

return {
loading,
data,
error,
refresh: fetchData,
};
}

这样, 就可以传入自己的网络请求实现, 比如 fetch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function App() {
const {loading, data, refresh} = useData('/api', (url) => fetch(url))

return (
<>
{
loading
? '加载中...'
: data
}
<div onClick={refresh}>重新拉取数据</div>
</>
)
}

当然这只是一个简单的封装, 事实上, 社区已经有一个很火的 hooks 请求库 swr, 非常实用, 推荐给大家哟 :)