重新渲染组件不会更新功能组件内的对象

时间:2018-12-05 07:10:32

标签: javascript reactjs

取决于天气,应显示某种类型的图标。

Weather组件中,我创建了一个包含所有相关图标的对象。我这样做,而不是将其添加到state中,因为据React所述:

  

您可以根据您的任何其他状态或道具计算它吗   零件?如果是这样,则它不是状态。

可以根据this.currentWeather.main的值来计算图标。

在第一次运行时一切正常,但是如果您将城市(更改为具有不同天气类型的城市),则图标保持不变。我不知道为什么。 (即尝试使用美国Tustin --->美国Rochester)

我尝试进行console.log(currentIcon),但是我得到了一个符号对象,并且在其中具有正确的属性值,但是无法正确显示。

我的理解是,在更新州(通过第二次输入另一个城市和国家)时,应该重新渲染Weather组件,并且所有代码都在{{1}之前}语句应该已经重新运行,我认为确实如此。

只是不确定为什么return语句中的{currentIcon}没有反映出这种变化。

我希望得到一个答案,但是,我更想知道为什么显示屏未更新。

return
const root = document.querySelector('.root');

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      unit: '',
      currentWeather: {
        main: '',
        desc: '',
        temp: '',
      }
    }
    
    this.getWeather = this.getWeather.bind(this);
    this.unitHandler = this.unitHandler.bind(this);
  }
  
  getWeather(e) {
    e.preventDefault();
    const city = e.target.elements.city.value;
    const country = e.target.elements.country.value;
    const appID = 'bf6cdb2b4f3c1293c29610bd1d54512b';
    
    const currentWeatherURL = `https://api.openweathermap.org/data/2.5/weather?q=${city},${country}&units=imperial&APPID=${appID}`;
    const forecastURL = `https://api.openweathermap.org/data/2.5/forecast?q=${city},${country}&units=imperial&APPID=${appID}`;
    
    //fetch CURRENT weather data ONLY
    fetch(currentWeatherURL)
      .then((response) => response.json())
      .then((data) => {
        this.setState({
          unit: '°F',
          currentWeather: {
           main: data.weather[0].main,
           desc: data.weather[0].description,
           temp: data.main.temp,
          }
        });
    })
    .catch(() => {console.log('something went wrong, but we caught the error')});
  }
  
  unitHandler(e) {
    function convertToCelsius(fahrenheit) {
      return ((fahrenheit-32)*5/9)
    }
    
    function convertToFahrenheit(celsius) {
      return ((celsius*9/5) + 32)
    }
    
    //if fahrenheit is checked
    if(e.target.value === 'fahrenheit') {
      const fahrenheitTemp = convertToFahrenheit(this.state.currentWeather.temp);
      this.setState(prevState => ({
        unit: '°F',
          currentWeather: {
            ...prevState.currentWeather,
            temp: fahrenheitTemp,
          }
      }));
    } 
    //otherwise, celsius is checked
    else {
      const celsiusTemp = convertToCelsius(this.state.currentWeather.temp);
      this.setState(prevState => ({
        unit: '°C',
        currentWeather: {
        ...prevState.currentWeather,
        temp: celsiusTemp,
      }
      }));
    }
  }
  
  render() {
    return (
      <div className='weather-app'>
        <LocationInput getWeather={this.getWeather} unitHandler={this.unitHandler}/>
        <CurrentWeather weatherStats={this.state.currentWeather} unit={this.state.unit} />
      </div>
    )
  }
}

// Component where you enter your City and State 
function LocationInput(props) {
  return (
    <div className='location-container'>
      <form className='location-form' onSubmit={props.getWeather}>
         <input type='text' name='city' placeholder='City'/>
         <input type='text' name='country' placeholder='Country'/>
        <button>Search</button>
        <UnitConverter unitHandler={props.unitHandler} />
      </form>
    </div>
  )
}

// Component to convert all units (fahrenheit <---> Celsius)
function UnitConverter(props) {
  return (
    <div className='unit-converter' onChange={props.unitHandler}>
      <label for='fahrenheit'>
        <input type='radio' name='unit' value='fahrenheit' defaultChecked/>
        Fahrenheit
      </label>
      <label for='celsius'>
        <input type='radio' name='unit' value='celsius'/>
        Celsius
      </label>
    </div>
  )
}

// Base weather component (intention of making specialized components for weekly forecast)
function Weather (props) {  
   const icons = {
        thunderstorm: <i class="fas fa-bolt"></i>,
        drizzle: <i class="fas fa-cloud-rain"></i>,
        rain: <i class="fas fa-cloud-showers-heavy"></i>,
        snow: <i class="far fa-snowflake"></i>,
        clear: <i class="fas fa-sun"></i>,
        atmosphere: 'No Icon Available',
        clouds: <i class="fas fa-cloud"></i>,
      };
  
  let currentIcon = icons[props.weatherStats.main.toLowerCase()];
  console.log(currentIcon);

  return (
    <div className={'weather-' + props.type}>
      <h1>{props.location}</h1>
      <h2>{props.day}</h2>
      <figure className='weather-icon'>
        <div className='weather-icon'> 
          {currentIcon}
        </div>
        <figcaption>
          <h3 className='weather-main'>{props.weatherStats.main}</h3>
          <div className='weather-desc'>{props.weatherStats.desc}</div>
          {props.weatherStats.temp && <div className='weather-temp'>{Math.round(props.weatherStats.temp)}{props.unit}</div>}
        </figcaption>
      </figure>      
    </div>
  ) 
}

// Using the specialization concept of React to create a more specific Weather component from base
function CurrentWeather(props) {
  const dateObj = new Date();
  const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
  const currentDay = days[dateObj.getDay()];
  
  return (
    <Weather 
      type={'current'} 
      weatherStats={props.weatherStats} 
      day={currentDay}
      unit={props.unit}
      />
  )
}

ReactDOM.render(<App />, root);
.weather-app {
  text-align: center;
}

.weather-current {
  display: inline-block;
}

.wf-container {
  display: flex;
  justify-content: center;
  align-items: center;
}

4 个答案:

答案 0 :(得分:1)

Class组件中的return()与功能组件render()之间的差异将始终在返回dom之前重新评估其内部的值,在功能组件中更改道具可能不会返回期望值。

您可能想尝试一下:

  let currentIcon = () => icons[props.weatherStats.main.toLowerCase()]

在您的退货中,将{currentIcon}更改为{currentIcon()}

您可能想考虑重命名变量,例如let getWeatherIcon

答案 1 :(得分:0)

我认为我看到了您的问题,请尝试以下操作:

 const icons = {
    thunderstorm: () => <i class="fas fa-bolt"></i>,
  };

将该对象中的每个图标更改为带有返回的函数。

希望这会有所帮助

劳埃德

答案 2 :(得分:0)

Fontawesome JS库似乎进行了一些DOM操作以用svg替换您的图标元素-在首页渲染时效果很好,但是当您开始尝试使用React更新DOM节点时,Fontawesome lib不知道应该替换它具有新svg路径的图标。

此类DOM操作(当React不知道时)可能会导致此类错误

  

“反应未捕获的DOMException:无法在'Node'上执行'removeChild':要删除的节点不是此节点的子级。”

有时它们非常繁琐且难以调试。

您需要做的就是更改

的依赖关系
"https://use.fontawesome.com/releases/v5.5.0/js/all.js"

"https://use.fontawesome.com/releases/v5.5.0/css/all.css"

后者将使用静态字体图标字形,而不会进行过多的DOM更新,以便一切正常。

在修复代码时,我在您的代码中编辑了一些错别字,您可以查看应用的完整示例:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      unit: "",
      currentWeather: {
        main: "",
        desc: "",
        temp: ""
      }
    };

    this.getWeather = this.getWeather.bind(this);
    this.unitHandler = this.unitHandler.bind(this);
  }

  getWeather(e) {
    e.preventDefault();
    const city = e.target.elements.city.value;
    const country = e.target.elements.country.value;
    const appID = "bf6cdb2b4f3c1293c29610bd1d54512b";

    const currentWeatherURL = `https://api.openweathermap.org/data/2.5/weather?q=${city},${country}&units=imperial&APPID=${appID}`;
    const forecastURL = `https://api.openweathermap.org/data/2.5/forecast?q=${city},${country}&units=imperial&APPID=${appID}`;

    //fetch CURRENT weather data ONLY
    fetch(currentWeatherURL)
      .then(response => response.json())
      .then(data => {
        this.setState({
          unit: "°F",
          currentWeather: {
            main: data.weather[0].main,
            desc: data.weather[0].description,
            temp: data.main.temp
          }
        });
      })
      .catch(() => {
        console.log("something went wrong, but we caught the error");
      });
  }

  unitHandler(e) {
    function convertToCelsius(fahrenheit) {
      return ((fahrenheit - 32) * 5) / 9;
    }

    function convertToFahrenheit(celsius) {
      return (celsius * 9) / 5 + 32;
    }

    //if fahrenheit is checked
    if (e.target.value === "fahrenheit") {
      const fahrenheitTemp = convertToFahrenheit(
        this.state.currentWeather.temp
      );
      this.setState(prevState => ({
        unit: "°F",
        currentWeather: {
          ...prevState.currentWeather,
          temp: fahrenheitTemp
        }
      }));
    }
    //otherwise, celsius is checked
    else {
      const celsiusTemp = convertToCelsius(this.state.currentWeather.temp);
      this.setState(prevState => ({
        unit: "°C",
        currentWeather: {
          ...prevState.currentWeather,
          temp: celsiusTemp
        }
      }));
    }
  }

  render() {
    return (
      <div className="weather-app">
        <LocationInput
          getWeather={this.getWeather}
          unitHandler={this.unitHandler}
        />
        <CurrentWeather
          weatherStats={this.state.currentWeather}
          unit={this.state.unit}
        />
      </div>
    );
  }
}

// Component where you enter your City and State
function LocationInput(props) {
  return (
    <div className="location-container">
      <form className="location-form" onSubmit={props.getWeather}>
        <input type="text" name="city" placeholder="City" />
        <input type="text" name="country" placeholder="Country" />
        <button>Search</button>
        <UnitConverter unitHandler={props.unitHandler} />
      </form>
    </div>
  );
}

// Component to convert all units (fahrenheit <---> Celsius)
function UnitConverter(props) {
  return (
    <div className="unit-converter" onChange={props.unitHandler}>
      <label for="fahrenheit">
        <input type="radio" name="unit" value="fahrenheit" defaultChecked />
        Fahrenheit
      </label>
      <label for="celsius">
        <input type="radio" name="unit" value="celsius" />
        Celsius
      </label>
    </div>
  );
}

// Base weather component (intention of making specialized components for weekly forecast)
function Weather(props) {
  const icons = {
    thunderstorm: <i className="fas fa-bolt" />,
    drizzle: <i className="fas fa-cloud-rain" />,
    rain: <i className="fas fa-cloud-showers-heavy" />,
    snow: <i className="far fa-snowflake" />,
    clear: <i className="fas fa-sun" />,
    atmosphere: "No Icon Available",
    clouds: <i className="fas fa-cloud" />,
    mist: "No Icon Available"
  };

  const currentIcon = icons[props.weatherStats.main.toLowerCase()];

  return (
    <div className={"weather-common"}>
      <h1>{props.location}</h1>
      <h2>{props.day}</h2>
      <figure className="weather-icon">
        <div className="weather-icon">{currentIcon}</div>
        <figcaption>
          <h3 className="weather-main">{props.weatherStats.main}</h3>
          <div className="weather-desc">{props.weatherStats.desc}</div>
          {props.weatherStats.temp && (
            <div className="weather-temp">
              {Math.round(props.weatherStats.temp)}
              {props.unit}
            </div>
          )}
        </figcaption>
      </figure>
    </div>
  );
}

// Using the specialization concept of React to create a more specific Weather component from base
function CurrentWeather(props) {
  const dateObj = new Date();
  const days = [
    "Sunday",
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday"
  ];
  const currentDay = days[dateObj.getDay()];

  return (
    <Weather
      type={"current"}
      weatherStats={props.weatherStats}
      day={currentDay}
      unit={props.unit}
    />
  );
}

const root = document.getElementById("root");
ReactDOM.render(<App />, root);
.App {
  font-family: sans-serif;
  text-align: center;
}

.weather-app {
  text-align: center;
}

.weather-current {
  display: inline-block;
}

.wf-container {
  display: flex;
  justify-content: center;
  align-items: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <meta name="theme-color" content="#000000" />
    <!--
      manifest.json provides metadata used when your web app is added to the
      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <link
      rel="stylesheet"
      href="https://use.fontawesome.com/releases/v5.5.0/css/all.css"
      integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU"
      crossorigin="anonymous"
    />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>

  <body>
    <noscript> You need to enable JavaScript to run this app. </noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

答案 3 :(得分:0)

您没有得到更新,因为react中的组件基于浅化的渲染比较。因此,您的代码let currentIcon = icons[props.weatherStats.main.toLowerCase()];是深层嵌套的,因此react不会检测到任何更改。如果您想根据图标值的更改来更新组件,请使用shouldComponentUpdate钩子并测试正在更改的属性,例如

 shouldComponentUpdate(prevProps){
  if (this.prevProps.weatherStats.main !== this.props.weatherStats.main){
      return true;
}