常规请求
import React, { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [data, setData] = useState({ hits: [] });
// useEffect(async () => {
// const result = await axios(
// 'https://hn.algolia.com/api/v1/search?query=redux',
// );
// setData(result.data);
// }, []);
useEffect(() => {
(async () => {
const result = await axios(
"https://hn.algolia.com/api/v1/search?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;
7-13行中,会
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..
因为useEffect返回nothing或者clean up function,而async返回的是一个promise,如果对async或者useEffect不熟悉的参见 文章 useEffect 和 异步
第21行,如果去掉[],会发现无限循环的请求接口,这是因为请求接口之后,重新setData,导致重新渲染,重新执行该effect,如此循环……
传入一个空数组,只能在mount的时候请求一次,如果想表单某个值改变的时候出发请求,该怎么实现呢?
触发请求
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`https://hn.algolia.com/api/v1/search?query=${query}`,
);
setData(result.data);
};
fetchData();
}, [query]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</Fragment>
);
}
export default App;
如上,每当input输入值改变的时候,都会对应的触发请求,通过维护一个query的值作为useEffect的依赖。
每次输入哪怕是只改变一个字母,都会频繁请求,那么我们通过一个按钮来控制请求,
import React, { Fragment, useState, useEffect } from "react";
import axios from "axios";
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(
`https://hn.algolia.com/api/v1/search?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>
);
}
export default App;
维护两个state,且状态同步,有点让人疑惑
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"
);
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(`http://hn.algolia.com/api/v1/search?query=${query}`)
}
>
Search
</button>
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</Fragment>
);
}
export default App;
我们尝试封装URL,每当点击search的时候,会改变url的query,url变化,就会触发effect的执行.
Loading 和 Error Indicator
loading和error indicator
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 [loading, setLoading] = useState(false);
const [err, setError] = useState(false);
useEffect(() => {
setLoading(true);
try {
const fetchData = async () => {
const result = await axios(url);
setData(result.data);
setLoading(false);
};
fetchData();
} catch (err) {
setError(true);
}
}, [url]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button
type="button"
onClick={() =>
setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
}
>
Search
</button>
{err && <div>Something goes wrong ... </div>}
{loading ? (
<div>Loading...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default App;
自定义Hook
封装自定义Hooks
import React, { Fragment, useState, useEffect } from "react";
import axios from "axios";
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) {
console.log(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 (
<Fragment>
<form
onSubmit={event => {
doFetch(`http://hn.algolia.com/api/v1/search?query=${query}`);
event.preventDefault();
}}
>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
{isError && <div>Something went wrong ...</div>}
{isLoading ? (
<div>Loading...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default App;
这就是自定义hook,自定义hook通常以use开头,返回数组。
hook接收参数,内部管理状态,返回data、loading、error。
useReducer中请求数据
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_FAILURE" });
}
};
fetchData();
}, [url]);
return [state, 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 (
<Fragment>
<form
onSubmit={event => {
doFetch(`http://hn.algolia.com/api/v1/search?query=${query}`);
event.preventDefault();
}}
>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
{isError && <div>Something went wrong ...</div>}
{isLoading ? (
<div>Loading...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default App;
取消请求
请求api的时候,如果组件unmount,也会触发setState 详见组件unmount取消setState
在hooks中如何避免组件unmount的时候setState呢?
const useDataApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: initialData,
});
useEffect(() => {
let didCancel = false;
const fetchData = async () => {
dispatch({ type: 'FETCH_INIT' });
try {
const result = await axios(url);
if (!didCancel) {
dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
}
} catch (error) {
if (!didCancel) {
dispatch({ type: 'FETCH_FAILURE' });
}
}
};
fetchData();
return () => {
didCancel = true;
};
}, [url]);
return [state, setUrl];
};
重点第11行、19行、23行、31-33行。
通过didCancel标志来控制。
Reference:
https://www.robinwieruch.de/react-hooks-fetch-data