我有一个问题,我无法用 React 的useState
解决。 我正在尝试发出电影和分页的 API 请求,但我的状态之一是undefined
,我真的不明白为什么...... 我的组件是一个功能组件。
这是我useState
:
const [state, setState] = useState({
films: [],
page: 1,
genres: [],
currentCategory: ""
})
这是我的 API 请求:
const getFilms = () => {
Axios.get(`${baseUrlDiscover}&page=${state.page}&with_genres=${state.currentCategory}`)
.then(response => {
setState({
films: response.data.results
});
})
.catch(error => {
console.log(error);
setState({
films: []
});
});
}
getFilms()
挂载组件的调用。
useEffect(() => {
getFilms()
}, [])
我用这样的.map
显示收到的电影:
const theMovies = state.films.map((film, idx) => {
return <Film film={film} key={idx} />
});
此时,我在第一页上收到 20 部电影,但是当我在控制台中记录page state
以获取第二页时,我收到了undefined
. 我想使用下面的这部分代码来获取 20 部电影的第二页,但没有page state
,我会收到类似state.films.map is undefined
const btnClickNext = (e) => {
if (state.films && state.page !== 500) {
setState(
prevState => ({ page: (prevState.page += 1) }),
getFilms
);
}
getFilms();
}
在return
中,我调用btnClickNext
函数:
<Button
href=""
target="_blank"
onClick={() => btnClickNext()}
>
Next <FaChevronRight />
</Button>
你知道问题可能来自哪里吗?为什么console.log(state.page)
会给出undefined
?
谢谢
请参阅文档中的此注释:
注意
与类组件中的
setState
方法不同,useState
不会自动合并更新对象。您可以通过将函数更新程序表单与对象传播语法相结合来复制此行为:setState(prevState => { // Object.assign would also work return {...prevState, ...updatedValues}; });
另一个选项是
useReducer
,它更适合管理包含多个子值的状态对象。
使用当前执行状态的方式,对所有状态使用单个对象,在仅更新一个项目时,您必须分散状态的其他方面。因此,例如,而不是:
// Incorrect
setState({
films: []
});
你会做:
// Incorrect
setState(state => ({
...state,
films: []
}));
不要这样做:
// DON'T DO THIS, IT'S LIKELY TO CAUSE TROUBLE
setState({
...state,
films: []
});
状态更新可能是异步的,并且可以堆叠在一起。如果这样做,您可能会消除先前更新的效果。
但是,我建议阅读整个页面,而不是使用单个状态对象。从根本上说,使用钩子,而不是:
// Not this
const [state, setState] = useState({
films: [],
page: 1,
genres: [],
currentCategory: ""
})
您通常需要以下内容:
// This instead
const [films, setFilms] = useState([]);
const [page, setPage] = useState(1);
const [genres, setGenres] = useState([]);
const [currentCategory, setCurrentCategory] = useState("");
然后使用相应的setXYZ
函数。而不是setState({films: []})
,你会做:
setFilms([]);
在评论中,您说:
我为每个元素做了一个useState,最终得到了正确的一个。我的函数增加了页数,但我无法从这些页面获取电影。
由于你为其提供了一个空的依赖项数组,所以useEffect
回调只会在首次创建组件时运行一次。如果您希望在发生更改时再次调用效果回调(例如,如果page
更改(,请将其添加到依赖项数组中。看起来getFilms
同时使用page
和currentCategory
,因此您将同时包含这两个。我还会在getFilms
中使它们成为参数,而不是让它从状态中使用它们:
useEffect(() => {
getFilms(page, currentCategory); // ***
}, [page, currentCategory]); // ***
现在,当page
或currentCategory
更改时,将调用回调(但不要担心,如果您一个接一个地更改它们,它通常只会被调用一次(。
如果您要开始新GET
,也可以考虑取消未完成的。(或者至少,至少忽略其结果。 有关详细信息,请参阅 axios 文档。从文档中看,它似乎使用了旧的现已撤回的可取消承诺提案。 以下是接受取消令牌的getFilms
版本:
const getFilms = (page, currentCategory, cancelToken) => {
return Axios.get(
`${baseUrlDiscover}&page=${page}&with_genres=${currentCategory}`,
{ cancelToken }
);
};
然后你会像这样使用它:
useEffect(() => {
let cancel;
let cancelToken = new Axios.CancelToken(c => {
cancel = () => {
c();
c.cancelled = true;
};
});
getFilms(page, currentCategory, cancelToken)
.then(films => setFilms(films))
.catch(error => {
if (!cancel.cancelled) {
// ...handle/report error, then:
setFilms([]);
}
});
return cancel;
}, [page, currentCategory]);
相当笨拙,也许有更好的Axios特定方式(我不使用Axios(。FWIW,这是使用fetch
和AbortSignal
的现代版本的getFilms
:
const getFilms = (page, currentCategory, signal) => {
return fetch(
${baseUrlDiscover}&page=${page}&with_genres=${currentCategory}
, {信号} ); };
然后你会像这样使用它:
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
getFilms(page, currentCategory, signal)
.then(films => setFilms(films))
.catch(error => {
if (!signal.aborted) {
// ...handle/report error, then:
setFilms([]);
}
});
return () => controller.abort();
}, [page, currentCategory]);
我推荐这篇文章useEffect
(它实际上不仅仅是关于useEffect
,而是关于使用钩子的功能组件如何工作(。
实际上这不是你应该使用 useState 的方式 你应该像这样创建单独的使用状态
const [films, setFilms] = useState([]),
const [page, setPage] = useState(1),
const [genres,setGenres] = useState([]),
const [currentCategory, setCurrentCategory] = useState("")
无论您在哪里需要更新这些状态,都可以相应地调用相应的 setter。