以下为译文:
教程中涉及useState, useEffect, useReducer的使用
-
基础的 Hook
- useState
- useEffect
- useContext
-
额外的 Hook
- useReducer
- useCallback
- useMemo
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
原文地址(www.robinwieruch.de/react-hooks…
在本教程中,我想向您展示如何使用Hooks中获取数据。我们将使用广为人知的Hacker News API从科技世界获取热门文章。您还将实现用于数据获取的Custom Hook,该Hook可在应用程序中的任何位置复用或作为独立节点程序包发布在npm上。
如果您对这个新的React功能一无所知,请查看这里对React Hooks的介绍(www.robinwieruch.de/react-hooks)。如果你想要查看文章中涉及的代码,可以从这个git仓库中获取(github.com/the-road-to…)
如果您只想准备好使用React Hook获取数据,可以执行npm install use-data-api并查看文档。 (如果使用它,别忘了给它加星)
下面的组件用于展示Hacker News的文章列表,使用useState这个钩子来更新组件的state,初始的hits是空数组,这里使用axios这个库来获取数据。
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);
});
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
上述代码中,useEffect的第二个参数可用于定义Hook所监听的所有变量(在此数组中分配)。 如果变量之一更改,则hook再次运行。 如果带有变量的数组为空,则在更新组件时挂钩根本不会运行,因为它不必监听任何变量。 并且上面的代码会在控制台中报warning:
Warning: An effect function must not return anything besides a function, which is used for clean-up.
It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately:
useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or state
根据文档,每个带有async注释的函数都返回一个promise:“ async函数的声明定义了一个异步函数,该函数返回一个AsyncFunction对象。异步函数是通过事件循环异步操作的函数,使用隐式Promise返回结果。”。warning中给出了具体的解决办法,也就是将userEffect第一个参数改写为一个不带有任何返回方法
修改后的代码如下:
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 (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
那么在使用useEffect时又如何通过事件来触发数据请求呢?
在前面的例子中,组件挂载时就会获取数据。但是如果我们要执行输入检索该怎么办呢?前面的例子中'redux'时默认查询(?query=redux),那么如果要查询'react'相关内容的话,该怎么做。下一个例子中会新增一个input元素,搜索input的输入内容。
import React, { useState, useEffect, Fragment } from 'react';
import axios from 'axios';
import _ from 'lodash';
function EffectHooks() {
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();
}, []);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<ul>
{_.map(data.hits, item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</Fragment>
);
}
export default EffectHooks;
上面的代码中缺失一个重要的部分,导致在input中输入内容时,并没有触发数据请求。原因是useEffect的第二个参数是一个空数组,并没有监听任何变量,所以只会在组件挂载时请求数据,如果要支持input查询获取数据,需要按照下面的写法。
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`https://hn.algolia.com/api/v1/search?query=${query}`,
);
setData(result.data);
};
fetchData();
}, [query]);
即在useEffect的第二个参数中增加对query变量的监听。现在可以做到在input中输入数据即可触发数据请求,但是现在的情况是每次输入都会触发请求,那么如何添加一个search按钮,手动触发请求。
带有loading状态的react钩子
为上面的例子代码会请求增加一个loading状态
import React, { useState, useEffect, Fragment } from 'react';
import axios from 'axios';
import _ from 'lodash';
function EffectHooks() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [search, setSearch] = useState('');
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
const result = await axios(
`https://hn.algolia.com/api/v1/search?query=${search}`,
);
setData(result.data);
setIsLoading(false);
};
fetchData();
}, [search]);
return (
<Fragment>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="button" onClick={() => setSearch(query)}>
Search
</button>
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{_.map(data.hits, item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default EffectHooks;
使用Form表单获取数据
前面的例子中只有输入框和搜索按钮,如果要加入更多的form表单元素,并使用回车键触发搜索,来通过下面的代码一步步完成。
import React, { useState, useEffect, Fragment } from 'react';
import axios from 'axios';
import _ from 'lodash';
function EffectHooks() {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [search, setSearch] = useState('');
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
const result = await axios(
`https://hn.algolia.com/api/v1/search?query=${search}`,
);
setData(result.data);
setIsLoading(false);
};
fetchData();
}, [search]);
return (
<Fragment>
<form
onSubmit={event => {
setSearch(query);
event.preventDefault();
}}
>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{_.map(data.hits, item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default EffectHooks;
上面的代码实现了表单搜索,回车键触发搜索。
对获取的数据进行自定义处理
在组件之外新增一个处理数据请求的方法,将请求相关的state处理移到这个方法中进行处理,并确保该方法返回组件所必需的所有变量。
并且对钩子方法的初始状态进行设置
import React, { useState, useEffect, Fragment } from 'react';
import axios from 'axios';
import _ from 'lodash';
const useDataApi = (initialData, initialSearch) => {
const [data, setData] = useState({ hits: [] });
const [query, setQuery] = useState('redux');
const [search, setSearch] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(
`https://hn.algolia.com/api/v1/search?query=${search}`,
);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [search]);
return [{ data, query, isLoading, isError }, setSearch];
};
function EffectHooks() {
const [query, setQuery] = useState('redux');
const [{ data, isLoading, isError }, setSearch] = useDataApi(
{ hits: [] },
query,
);
return (
<Fragment>
<form
onSubmit={event => {
setSearch(query);
event.preventDefault();
}}
>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{_.map(data.hits, item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default EffectHooks;
用于数据请求的reducer钩子
到目前为止,我们已经使用各种状态挂钩来管理数据的获取状态,加载和错误状态。那么如何用reducer钩子对与数据获取相关的状态管理进行统一管理。
reducer钩子返回一个状态对象和一个更改状态对象的函数。这个函数被成为dispatch函数,处理一个action,拥有type属性和optional payload,通过下面的代码来分析该钩子如何工作
import React, { useState, useEffect, Fragment, useReducer } from 'react';
import axios from 'axios';
import _ from 'lodash';
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 = (initialData, initialSearch) => {
const [search, setSearch] = useState(initialSearch);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: initialData,
});
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'FETCH_INIT' });
try {
const result = await axios(
`https://hn.algolia.com/api/v1/search?query=${search}`,
);
dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
} catch (error) {
dispatch({ type: 'FETCH_FAILURE' });
}
};
fetchData();
}, [search]);
return [state, setSearch];
};
function EffectHooks() {
const [query, setQuery] = useState('redux');
const [{ data, isLoading, isError }, setSearch] = useDataApi(
{ hits: [] },
query,
);
return (
<Fragment>
<form
onSubmit={event => {
setSearch(query);
event.preventDefault();
}}
>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{_.map(data.hits, item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default EffectHooks;
欢迎扫码关注作者公众号