React:setState 不会导致重新渲染?
Posted
技术标签:
【中文标题】React:setState 不会导致重新渲染?【英文标题】:React: setState is not causing a re-render? 【发布时间】:2021-01-17 12:27:57 【问题描述】:我是 React、JS、JSX 的新手。
setNewWeather 似乎没有正确更新天气状态,因为它是由初始值定义的,但随后变为 undefined。
因为如果它更新,它应该会导致重新渲染;我看过很多关于这个的帖子,但他们建议喜欢,等待异步数据操作,但我的理解是使用 '.then' 方法固有地做到这一点? 或者这是涉及 setNewWeather 语法的不同问题,比如它需要使用内部函数而不是字符串来更新状态?
我的代码:
import React, useState, useEffect from 'react'
import axios from 'axios'
const Header = ( text ) => <h1>text</h1>
const Header3 = ( text ) => <h3>text</h3>
const Image = ( source, alttext ) => <img src=source alt=alttext />
const Button = ( onClick, text ) => (<button onClick=onClick>text</button>)
const ListItem = ( item ) => <li>item</li>
const List = ( title, stuff ) =>
return(
<>
< Header3 text=title />
<ul>
stuff.map((item, index) => < ListItem key=index item=item />)
</ul>
</>)
const Search = ( text, value, onChange ) =>
return (
<>
text
<input value=value onChange=onChange />
</>)
const CountryMany = ( country, handleClick ) =>
return (
<>
<li>country.name</li>
< Button onClick=handleClick(country.name) text='Show' />
</>)
const CountryFound = ( country, api_key, handleWeather, newWeather ) =>
const countryFound = country[0]
const params =
access_key: api_key,
query: countryFound.capital
useEffect(() =>
axios.get('http://api.weatherstack.com/current', params)
.then(response =>
console.log('RESPONSE', response.data)
handleWeather( is: 'weather', data: response.data )
console.log(newWeather)
),
[params, newWeather, handleWeather])
console.log('yo')
console.log(newWeather)
const languages = countryFound.languages.map(lang => lang.name)
return (
<>
< Header text=countryFound.name />
<p>Capital: countryFound.capital</p>
<p>Population: countryFound.population</p>
< List title='Languages' stuff=languages />
< Header3 text='Flag' />
< Image source=countryFound.flag alttext='flag' />
< Header3 text='Weather' />
<ul>
<li>Temperature: newWeather</li>
<li> Image source= alttext=weather </li>
</ul></>)
const Countries = (props) =>
console.log('COUNTRIES PROPS', props)
console.log('WEATHER', props.newWeather)
const countries = props.countries
const foundCountries = countries.filter(country =>
country.name.toLowerCase().includes(props.newSearch.toLowerCase()))
if (foundCountries.length > 10 )
return (<p>Too Many Matches, Keep Typing!</p>)
if (foundCountries.length > 1)
return (
<ul>
foundCountries.map(country =>
< CountryMany key=country.population country=country handleClick=props.handleClick />)
</ul>)
if (foundCountries.length === 1)
return (
<>
<CountryFound api_key=props.a_k1 country=foundCountries
handleWeather=props.handleWeather weather=props.newWeather />
</>)
return (<></>)
const App = () =>
const api_key = process.env.REACT_APP_API_KEY
const [ countries, setCountries ] = useState([])
const [ newSearch, setNewSearch ] = useState('')
const [ newWeather, setWeather ] = useState( is: 'no ewather' )
const handleWeather = ( is, data ) => () =>
setWeather( is, data )
console.log('HEY HANDLEWEATHER', newWeather)
useEffect(() =>
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response =>
setCountries(response.data)
), [])
const handleClick = (value) => () =>
setNewSearch(value)
const handleSearch = (event) =>
setNewSearch(event.target.value)
return (
<div>
< Search text='Find A Country: ' value=newSearch onChange=handleSearch/>
< Countries countries=countries
a_k1=api_key
handleWeather=handleWeather
handleClick=handleClick
newSearch=newSearch
newWeather=newWeather
/>
</div>)
export default App
/*
const Weather = ( weather ) =>
return (
<>
< Header3 text='Weather' />
<ul>
<li>Temperature: weather.temperature</li>
<li> Image source= alttext=weather </li>
</ul>
</>)
*/
谢谢!
编辑:状态正在更新,但只能通过形成无限循环:
import React, useState, useEffect from 'react'
import axios from 'axios'
const Header = ( text ) => <h1>text</h1>
const Header3 = ( text ) => <h3>text</h3>
const Image = ( source, alttext ) => <img src=source alt=alttext />
const Button = ( onClick, text ) => (<button onClick=onClick>text</button>)
const ListItem = ( item ) => <li>item</li>
const List = ( title, stuff ) =>
return(
<>
< Header3 text=title />
<ul>
stuff.map((item, index) => < ListItem key=index item=item />)
</ul>
</>)
const Search = ( text, value, onChange ) =>
return (
<>
text
<input value=value onChange=onChange />
</>)
const CountryMany = ( country, handleClick ) =>
return (
<>
<li>country.name</li>
< Button onClick=handleClick(country.name) text='Show' />
</>)
const CountryFound = ( countryFound, api_key, handleWeather, newWeather ) =>
const params = access_key: api_key, query: countryFound.capital
useEffect(() =>
axios.get('http://api.weatherstack.com/current', params)
.then(response =>
console.log('RESPONSE', response.data)
handleWeather(response.data)
))
const languages = countryFound.languages.map(lang => lang.name)
if (newWeather.length > 0 )
return (
<>
< Header text=countryFound.name />
<p>Capital: countryFound.capital</p>
<p>Population: countryFound.population</p>
< List title='Languages' stuff=languages />
< Header3 text='Flag' />
< Image source=countryFound.flag alttext='flag' />
< Header3 text='Weather' />
<ul>
<li>Temperature/rendering newWeather</li>
<li> Image source= alttext=weather </li>
</ul></>)
return (
<></>
)
const Countries = (props) =>
console.log('COUNTRIES PROPS', props)
console.log('WEATHER', props.newWeather)
const foundCountries = props.countries.filter(country =>
country.name.toLowerCase().includes(props.newSearch.toLowerCase()))
if (foundCountries.length > 10 )
return (<p>Too Many Matches, Keep Typing!</p>)
if (foundCountries.length > 1)
return (
<ul>
foundCountries.map(country =>
< CountryMany key=country.population country=country handleClick=props.handleClick />)
</ul>)
if (foundCountries.length === 1)
return (
<>
<CountryFound api_key=props.a_k1 countryFound=foundCountries[0]
handleWeather=props.handleWeather newWeather=props.newWeather />
</>)
return (<></>)
const App = () =>
const api_key = process.env.REACT_APP_API_KEY
const [ countries, setCountries ] = useState([])
const [ newSearch, setNewSearch ] = useState('af')
const [ newWeather, setWeather ] = useState([])
const handleClick = (value) => () =>
setNewSearch(value)
const handleSearch = (event) =>
setNewSearch(event.target.value)
useEffect(() =>
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response =>
setCountries(response.data)
), [])
return (
<div>
< Search text='Find A Country: ' value=newSearch onChange=handleSearch/>
< Countries countries=countries
a_k1=api_key
handleWeather=setWeather
handleClick=handleClick
newSearch=newSearch
newWeather=newWeather
/>
</div>)
export default App
编辑最终结果:已解决!
以下标记的解决方案解决了初始问题,但产生了无限循环。我已经纠正了整个事情,虽然我还不太明白它是如何改变的。
import React, useState, useEffect from 'react'
import axios from 'axios'
const Header = ( text ) => <h1>text</h1>
const Header3 = ( text ) => <h3>text</h3>
const Image = ( source, alttext ) => <img src=source alt=alttext />
const Button = ( onClick, text ) => (<button onClick=onClick>text</button>)
const ListItem = ( item ) => <li>item</li>
const List = ( title, stuff ) =>
return(
<>
< Header3 text=title />
<ul>
stuff.map((item, index) => < ListItem key=index item=item />)
</ul>
</>)
const Search = ( text, value, onChange ) =>
return (
<>
text
<input value=value onChange=onChange />
</>)
const CountryMany = ( country, handleClick ) =>
return (
<>
<li>country.name</li>
< Button onClick=handleClick(country.name) text='Show' />
</>)
const CountryFound = ( countryFound, api_key, handleWeather, newWeather ) =>
useEffect(() =>
axios.get(`https://api.weatherbit.io/v2.0/current?city=$countryFound.capital&key=$api_key`)
.then(response =>
handleWeather(response.data.data[0])
))
const languages = countryFound.languages.map(lang => lang.name)
if (newWeather > '' )
const capital = countryFound.capital
const weatherTitle = `Weather in: $capital`
const weatherImage = `https://www.weatherbit.io/static/img/icons/$newWeather.weather.icon.png`
return (
<>
< Header text=countryFound.name />
<p>Capital: capital</p>
<p>Population: countryFound.population</p>
< List title='Languages' stuff=languages />
< Header3 text='Flag' />
< Image source=countryFound.flag alttext='flag' />
< Header3 text=weatherTitle />
< Image source=weatherImage alttext='weather' />
<ul>
<li>Temperature: newWeather.temp degrees Celsius</li>
<li>Wind: newWeather.wind_spd mph towards newWeather.wind_cdir</li>
</ul></>)
return (<><p>Loading...</p></>)
const Countries = (props) =>
const foundCountries = props.countries.filter(country =>
country.name.toLowerCase().includes(props.newSearch.toLowerCase()))
if (foundCountries.length > 10 )
return (<p>Too Many Matches, Keep Typing!</p>)
if (foundCountries.length > 1)
return (
<ul>
foundCountries.map(country =>
< CountryMany key=country.population
country=country
handleClick=props.handleClick />)
</ul>)
if (foundCountries.length === 1)
return (<>
<CountryFound api_key=props.a_k1 countryFound=foundCountries[0]
handleWeather=props.handleWeather newWeather=props.newWeather />
</>)
return (<></>)
const App = () =>
const api_key = process.env.REACT_APP_API_KEY
const [ countries, setCountries ] = useState([])
const [ newSearch, setNewSearch ] = useState('af')
const [ newWeather, setWeather ] = useState('')
const handleClick = (value) => () =>
setNewSearch(value)
const handleSearch = (event) =>
setNewSearch(event.target.value)
useEffect(() =>
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response =>
setCountries(response.data)
), [])
return (
<div>
< Search text='Find A Country: ' value=newSearch onChange=handleSearch/>
< Countries countries=countries
a_k1=api_key
handleWeather=setWeather
handleClick=handleClick
newSearch=newSearch
newWeather=newWeather
/>
</div>)
export default App
【问题讨论】:
在状态管理方面,我个人认为更安全的是“reducer”工作流程,例如 Redux 或 -my preference-useReducer,它是 React 本身的一部分。绝对值得一读:reactjs.org/docs/hooks-reference.html#usereducer 试试这个 --setWeather(is: data )
谢谢臭氧,会的。 @Saran:谢谢,是的,我明白了。没有解决更大的问题,但这是一回事!
【参考方案1】:
问题
handleWeather
被定义为接受两个参数
const handleWeather = ( is, data ) => () =>
setWeather( is, data )
console.log('HEY HANDLEWEATHER', newWeather)
但是当你调用它时,你只传递一个参数
handleWeather( is: 'weather', data: response.data )
此外,React 状态更新是异步的,并且在 渲染周期之间进行批处理,因此尝试在更新入队之后立即 记录状态只会记录当前状态。
解决方案
您应该选择接受两个参数并创建您想要的状态对象,或者始终将您想要存储的已创建对象传递给它。以下将使用后者。
const handleWeather = (newWeather) => () => setWeather(newWeather);
注意:此时handleWeather
只是代理newWeather
对象,因此可能对不是进行一些小的优化em> 代理,因为函数签名匹配,即const handleWeather = setWeather
,或者直接将setWeather
作为回调传递。
<Countries
countries=countries
a_k1=api_key
handleWeather=setWeather // <-- directly pass state update function
handleClick=handleClick
newSearch=newSearch
newWeather=newWeather
/>
使用效果记录更新的newWeather
,使用newWeather
作为依赖项。
useEffect(() =>
console.log('HEY HANDLEWEATHER', newWeather)
, [newWeather]);
【讨论】:
明白了,谢谢!这两个参数对象是我试图进一步解决非更新状态问题(创建然后翻转“is”值),所以我像你建议的那样摆脱了它。进一步感谢您的说明!实施其他建议给我留下了一个无限循环,但这是进步,它现在正在改变状态并重新渲染,至少! @chrisstudstillCountryFound
中的 useEffect
具有 newWeather
作为依赖项(用于控制台日志),但是您通过 axios 调用和 handleWeather
更新 newWeather
并创建一个新的对象引用。这肯定会导致渲染循环。
@chrisstudstill 如果更新状态未重新渲染问题已解决,请接受答案,以便其他人知道已解决。
是的,会的。我现在应该这样做吗?状态更新的唯一方法是形成无限循环,但我想我现在仍在测试,更新了 OP 中的代码。
在您更新的代码中,您从CountryFound
中的效果中完全删除了依赖数组,该效果会更新天气状态,因此效果将在每次渲染时运行。看起来您可能需要此依赖项:[countryFound.capital, api_key, handleWeather]
,或者至少需要一个空的依赖项数组 ([]
),以便在安装该组件时仅执行一次提取(我没有完全跟踪您的代码以了解确定CountryFound
是否已重新安装,尽管我认为是)。以上是关于React:setState 不会导致重新渲染?的主要内容,如果未能解决你的问题,请参考以下文章
React - 带有异步 setState 的 useEffect 导致额外的重新渲染
React:useState 钩子中的 setState 在啥情况下会导致重新渲染?
即使在调用 setState 之后,React.js 子状态也不会重新渲染?
React - 当 Axios 在单独的组件中调用时,setState 不会重新渲染页面