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”值),所以我像你建议的那样摆脱了它。进一步感谢您的说明!实施其他建议给我留下了一个无限循环,但这是进步,它现在正在改变状态并重新渲染,至少! @chrisstudstill CountryFound 中的 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 不会重新渲染页面

即使状态没有改变,为啥 setState 会导致太多的重新渲染错误

React生命周期, setState、props改变触发的钩子函数