React:setState 不会导致重新渲染?

React: setState is not causing a re-render?

我是 React、JS、JSX 的新手。

似乎 setNewWeather 没有正确更新天气状态,因为它由初始值定义,但随后变为未定义。

因为如果更新,应该会导致重新渲染;我看过很多关于此的帖子,但他们建议喜欢,等待异步数据操作,但据我了解,使用“.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

问题

handleWeather 被定义为接受两个参数

const handleWeather = ( is, data ) => () => {
  setWeather( is, data )
  console.log('HEY HANDLEWEATHER', newWeather)
}

但是当你调用它时你只传递一个参数

handleWeather({ is: 'weather', data: response.data })

此外,React 状态更新是异步的,并且在 渲染周期之间进行批处理,因此在 更新之后 尝试控制台日志状态是enqueued 只会记录当前状态。

解决方案

您应该选择接受这两个参数并在状态中创建您想要的对象,或者始终将其传递给您想要存储的 already-created 对象。下面将使用后者。

const handleWeather = (newWeather) => () => setWeather(newWeather);

注意: 此时 handleWeather 只是简单地代理 newWeather 对象,因此可以对 不是代理,因为函数签名匹配,即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]);