状态更新后反应不重新渲染
React not re-rendering after state update
我目前正在学习 React。在我的主页组件中,我使用一个钩子来初始化和填充状态。我有 2 个状态,1 个包含随机口袋妖怪,另一个是 20 个随机口袋妖怪的数组。第一个工作正常,但不是数组。
这是主页组件:
// Hook
import { useHomeFetch } from "../hooks/useHomeFetch";
// Image
import NoImage from '../images/missingno.png';
const Home = () => {
const { state, collection, loading, error } = useHomeFetch();
return (
<>
{ state.heroPokemon ?
<HeroImage
image={`${API.fetchSprite(state.heroPokemon)}`}
title={state.heroPokemon.name}
text='Placeholder, should include abilities, etc. to show the pokemon off.'
/>
: null}
{ collection.pokemons[0] ?
<Grid header='Popular pokemons'>
{collection.pokemons.map( pokemon => (
<h3 key={pokemon.id}>{pokemon.name}</h3>
) )}
</Grid>
: null}
</>
);
}
export default Home;
添加条件渲染后英雄宝可梦运行良好。我的第一个想法是对集合做同样的事情,因为它可能在所有 API 调用承诺都已解决之前呈现。
如果我查看 React 开发工具,这就是我看到的状态:
hooks
HomeFetch
1 State : {heroPokemon: {…}}
2 State : {pokemons: Array(20)}
pokemons :
[{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …]
所以在我看来,调用确实完成得很好,里面有 20 个随机的 pokemon 对象。
这里是钩子:
const initialState = {
heroPokemon: null
}
const fetchRandomPokemons = async (ids) => {
let pokemons = [];
ids.forEach(async num => {
pokemons.push(await API.fetchPokemon(num));
});
return pokemons;
}
export const useHomeFetch = () => {
const [state, setState] = useState(initialState);
const [collection, setCollection] = useState({pokemons: []});
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const fetchPokemons = async (limit = 20) => {
try {
setError(false);
setLoading(true);
const heroPokemon = await API.fetchPokemon(randomInt(1, 898));
const randomInts = randomUniqueInt(1, 898, limit);
const pokemons = await fetchRandomPokemons(randomInts);
setState(prev => ({heroPokemon}));
setCollection(prev => ({pokemons}));
} catch (error) {
setError(true);
}
setLoading(false);
};
// Initial render
useEffect(() => {
fetchPokemons(20);
}, [])
return { state, collection, loading, error };
};
当我去掉条件时,Grid
出现了,但它没有带有口袋妖怪名称的 h3。如果我 console.log 出 state
和 collection
就在 home
的 return 之前。最后几个显示正确的 state.heroPokemon
和 collection.pokemons
正确填充。这使我相信状态已正确更新,但为什么 React 不重新渲染网格组件?
补充一下,我也试过这样补充:
<p>{collection.pokemons[0]}</p>
到主页组件,没有任何显示。我觉得我可能误解或遗漏了状态和重新渲染如何工作的关键部分,这导致我遗漏了我可能做错的地方。
编辑:
这是 API 调用 return 的内容:pokeapi。co/api/v2/pokemon/ditto
我还调试了 Home
,这就是它的样子:imgur.com/a/jVhaSsk
我现在很困惑,因为 pokemon.name
显然存在并且不是未定义的。为什么它们没有出现在 h3
中?
编辑 2:
我将主页组件更改为:
<Grid header='Popular pokemons'>
{
state.results.map(pokemon => {
console.log(pokemon.name)
return <h3>pokemon name</h3>
})
}
</Grid>
看起来 h3
仍然是空的。还在等待数组中的promise吗?但是 console.log
可以很好地打印出口袋妖怪的名字。
编辑 3:
感谢德鲁,我找到了问题所在。
这是我的 Grid
组件的样子:
import React from "react";
import { Wrapper, Content } from './Grid.styles';
const Grid = ({ header }) => (
<Wrapper>
<h1>{header}</h1>
</Wrapper>
);
export default Grid;
我也在使用 styled-components
,这是文件:
import styled from 'styled-components';
export const Wrapper = styled.div`
max-width: var(--maxWidth);
margin: 0 auto;
padding: 0 20px;
h1 {
color: var(--medGrey);
@media screen and (max-width: 768px) {
font-size: var(--fontBig);
}
}
`;
export const Content = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 2rem;
`;
如您所见,Content
不在 Wrapper
中。我改成后:
const Grid = ({ header, children }) => (
<Wrapper>
<h1>{header}</h1>
<Content>{children}</Content>
</Wrapper>
);
效果很好。看起来 Pokemon 数组正在作为 {children}
传递给 Grid
,所以当它不存在时它不会渲染它。
试试这个:
// Hook
import { useHomeFetch } from "../hooks/useHomeFetch";
// Image
import NoImage from '../images/missingno.png';
const Home = () => {
const [newCollection, setNewCollection] =useState([])
const { state, collection, loading, error } = useHomeFetch();
useEffect(()=>{
setNewCollection(collection || []);
},[collection])
return (
<>
{ state.heroPokemon ?
<HeroImage
image={`${API.fetchSprite(state.heroPokemon)}`}
title={state.heroPokemon.name}
text='Placeholder, should include abilities, etc. to show the pokemon off.'
/>
: null}
{ newCollection.pokemons[0] ?
<Grid header='Popular pokemons'>
{newCollection.pokemons.map( pokemon => (
<h3 key={pokemon.id}>{pokemon.name}</h3>
) )}
</Grid>
: null}
</>
);
}
export default Home;
如果仍然无法工作,请分享您的存储库。所以我可以帮助你更多。谢谢
问题
看来问题出在您的 fetchRandomPokemons
实用程序函数中。即使您已声明 fetchRandomPokemons
async
,您也不会等待任何事情。 Array.prototype.forEach
也是完全同步的。但是,您 确实 声明 .forEach
回调 async
并且 await
声明 API 响应,但是因为 .forEach
是同步的fetchRandomPokemons
函数已经 return 编辑了空 pokemons
数组并解析。 async
关键字只允许一个函数作用域使用await
关键字。
const fetchRandomPokemons = async (ids) => {
let pokemons = [];
ids.forEach(async num => { // <-- synchronous
pokemons.push(await API.fetchPokemon(num));
});
return pokemons; // <-- returns []
}
...
const pokemons = await fetchRandomPokemons(randomInts); // <-- []
setCollection({ pokemons }); // <-- { pokemons: [] }
您记录的是 pokemons
数组,当每个 API.fetchPokemon
Promise 解决时,.forEach
数组发生变异。
解决方案
将 ids
映射到一个 Promise 数组并等待它们全部解析,然后 return 解析的 pokemon 数组。
const fetchRandomPokemons = (ids) => {
return Promise.all(ids.map(API.fetchPokemon));
}
...
const fetchPokemons = async (limit = 20) => {
setError(false);
setLoading(true);
try {
const heroPokemon = await API.fetchPokemon(randomInt(1, 898));
const randomInts = randomUniqueInt(1, 898, limit);
const pokemons = await fetchRandomPokemons(randomInts);
setState({ heroPokemon });
setCollection({ pokemons });
} catch (error) {
setError(true);
}
setLoading(false);
};
更新
正在添加 运行 codesandbox 和 screencap。
我目前正在学习 React。在我的主页组件中,我使用一个钩子来初始化和填充状态。我有 2 个状态,1 个包含随机口袋妖怪,另一个是 20 个随机口袋妖怪的数组。第一个工作正常,但不是数组。
这是主页组件:
// Hook
import { useHomeFetch } from "../hooks/useHomeFetch";
// Image
import NoImage from '../images/missingno.png';
const Home = () => {
const { state, collection, loading, error } = useHomeFetch();
return (
<>
{ state.heroPokemon ?
<HeroImage
image={`${API.fetchSprite(state.heroPokemon)}`}
title={state.heroPokemon.name}
text='Placeholder, should include abilities, etc. to show the pokemon off.'
/>
: null}
{ collection.pokemons[0] ?
<Grid header='Popular pokemons'>
{collection.pokemons.map( pokemon => (
<h3 key={pokemon.id}>{pokemon.name}</h3>
) )}
</Grid>
: null}
</>
);
}
export default Home;
添加条件渲染后英雄宝可梦运行良好。我的第一个想法是对集合做同样的事情,因为它可能在所有 API 调用承诺都已解决之前呈现。
如果我查看 React 开发工具,这就是我看到的状态:
hooks
HomeFetch
1 State : {heroPokemon: {…}}
2 State : {pokemons: Array(20)}
pokemons :
[{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …]
所以在我看来,调用确实完成得很好,里面有 20 个随机的 pokemon 对象。
这里是钩子:
const initialState = {
heroPokemon: null
}
const fetchRandomPokemons = async (ids) => {
let pokemons = [];
ids.forEach(async num => {
pokemons.push(await API.fetchPokemon(num));
});
return pokemons;
}
export const useHomeFetch = () => {
const [state, setState] = useState(initialState);
const [collection, setCollection] = useState({pokemons: []});
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const fetchPokemons = async (limit = 20) => {
try {
setError(false);
setLoading(true);
const heroPokemon = await API.fetchPokemon(randomInt(1, 898));
const randomInts = randomUniqueInt(1, 898, limit);
const pokemons = await fetchRandomPokemons(randomInts);
setState(prev => ({heroPokemon}));
setCollection(prev => ({pokemons}));
} catch (error) {
setError(true);
}
setLoading(false);
};
// Initial render
useEffect(() => {
fetchPokemons(20);
}, [])
return { state, collection, loading, error };
};
当我去掉条件时,Grid
出现了,但它没有带有口袋妖怪名称的 h3。如果我 console.log 出 state
和 collection
就在 home
的 return 之前。最后几个显示正确的 state.heroPokemon
和 collection.pokemons
正确填充。这使我相信状态已正确更新,但为什么 React 不重新渲染网格组件?
补充一下,我也试过这样补充:
<p>{collection.pokemons[0]}</p>
到主页组件,没有任何显示。我觉得我可能误解或遗漏了状态和重新渲染如何工作的关键部分,这导致我遗漏了我可能做错的地方。
编辑:
这是 API 调用 return 的内容:pokeapi。co/api/v2/pokemon/ditto
我还调试了 Home
,这就是它的样子:imgur.com/a/jVhaSsk
我现在很困惑,因为 pokemon.name
显然存在并且不是未定义的。为什么它们没有出现在 h3
中?
编辑 2: 我将主页组件更改为:
<Grid header='Popular pokemons'>
{
state.results.map(pokemon => {
console.log(pokemon.name)
return <h3>pokemon name</h3>
})
}
</Grid>
看起来 h3
仍然是空的。还在等待数组中的promise吗?但是 console.log
可以很好地打印出口袋妖怪的名字。
编辑 3:
感谢德鲁,我找到了问题所在。
这是我的 Grid
组件的样子:
import React from "react";
import { Wrapper, Content } from './Grid.styles';
const Grid = ({ header }) => (
<Wrapper>
<h1>{header}</h1>
</Wrapper>
);
export default Grid;
我也在使用 styled-components
,这是文件:
import styled from 'styled-components';
export const Wrapper = styled.div`
max-width: var(--maxWidth);
margin: 0 auto;
padding: 0 20px;
h1 {
color: var(--medGrey);
@media screen and (max-width: 768px) {
font-size: var(--fontBig);
}
}
`;
export const Content = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 2rem;
`;
如您所见,Content
不在 Wrapper
中。我改成后:
const Grid = ({ header, children }) => (
<Wrapper>
<h1>{header}</h1>
<Content>{children}</Content>
</Wrapper>
);
效果很好。看起来 Pokemon 数组正在作为 {children}
传递给 Grid
,所以当它不存在时它不会渲染它。
试试这个:
// Hook
import { useHomeFetch } from "../hooks/useHomeFetch";
// Image
import NoImage from '../images/missingno.png';
const Home = () => {
const [newCollection, setNewCollection] =useState([])
const { state, collection, loading, error } = useHomeFetch();
useEffect(()=>{
setNewCollection(collection || []);
},[collection])
return (
<>
{ state.heroPokemon ?
<HeroImage
image={`${API.fetchSprite(state.heroPokemon)}`}
title={state.heroPokemon.name}
text='Placeholder, should include abilities, etc. to show the pokemon off.'
/>
: null}
{ newCollection.pokemons[0] ?
<Grid header='Popular pokemons'>
{newCollection.pokemons.map( pokemon => (
<h3 key={pokemon.id}>{pokemon.name}</h3>
) )}
</Grid>
: null}
</>
);
}
export default Home;
如果仍然无法工作,请分享您的存储库。所以我可以帮助你更多。谢谢
问题
看来问题出在您的 fetchRandomPokemons
实用程序函数中。即使您已声明 fetchRandomPokemons
async
,您也不会等待任何事情。 Array.prototype.forEach
也是完全同步的。但是,您 确实 声明 .forEach
回调 async
并且 await
声明 API 响应,但是因为 .forEach
是同步的fetchRandomPokemons
函数已经 return 编辑了空 pokemons
数组并解析。 async
关键字只允许一个函数作用域使用await
关键字。
const fetchRandomPokemons = async (ids) => {
let pokemons = [];
ids.forEach(async num => { // <-- synchronous
pokemons.push(await API.fetchPokemon(num));
});
return pokemons; // <-- returns []
}
...
const pokemons = await fetchRandomPokemons(randomInts); // <-- []
setCollection({ pokemons }); // <-- { pokemons: [] }
您记录的是 pokemons
数组,当每个 API.fetchPokemon
Promise 解决时,.forEach
数组发生变异。
解决方案
将 ids
映射到一个 Promise 数组并等待它们全部解析,然后 return 解析的 pokemon 数组。
const fetchRandomPokemons = (ids) => {
return Promise.all(ids.map(API.fetchPokemon));
}
...
const fetchPokemons = async (limit = 20) => {
setError(false);
setLoading(true);
try {
const heroPokemon = await API.fetchPokemon(randomInt(1, 898));
const randomInts = randomUniqueInt(1, 898, limit);
const pokemons = await fetchRandomPokemons(randomInts);
setState({ heroPokemon });
setCollection({ pokemons });
} catch (error) {
setError(true);
}
setLoading(false);
};
更新
正在添加 运行 codesandbox 和 screencap。