You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Vue.js中如何在API返回满足条件时显示组件?解决v-if报错及Enter触发问题

解决Vue中API未加载完成时的Cannot read property '0' of undefined错误

我完全懂你碰到的问题——初始状态下weather是空对象,Vue尝试渲染组件时,weather.weather还没被API赋值,自然是undefined,再去访问[0]就会抛出类型错误。而且你希望只有按下Enter获取数据后才显示对应动画组件,而不是一开始就加载所有组件。

下面是具体的修复方案:

步骤1:添加状态标记是否已获取天气数据

data中新增一个布尔值属性,用来标记是否已经通过Enter键触发了API请求并获取到数据:

data () {
  return {
    api_key: '08f1525958fbc6584f628b6dac25a906',
    url_base: 'https://api.openweathermap.org/data/2.5/',
    query: '',
    weather: {},
    hasFetchedWeather: false // 新增这个状态
  }
},

步骤2:获取数据后更新状态

setResults方法中,把hasFetchedWeather设为true,表示已经获取到天气数据,可以开始判断动画组件了:

setResults (results) {
  this.weather = results;
  this.hasFetchedWeather = true; // 新增这行
},

步骤3:修改动画组件的v-if条件

给每个动画组件的v-if添加两层防御:先判断hasFetchedWeather(确保已经触发过API请求),再判断weather.weather是否存在,最后才判断具体的天气类型。这样就能避免未加载完成时的undefined错误:

<CloudsAnimation v-if="hasFetchedWeather && weather.weather && weather.weather[0].main=='Clouds'"></CloudsAnimation>
<SunAnimation v-else-if="hasFetchedWeather && weather.weather && weather.weather[0].main=='Clear'"></SunAnimation>
<NoAnimation v-else-if="hasFetchedWeather"></NoAnimation>

如果你的项目使用Vue 3,或者Vue 2配合了可选链语法的支持(比如通过Babel插件),可以简化条件为:

<CloudsAnimation v-if="hasFetchedWeather && weather.weather?.[0]?.main=='Clouds'"></CloudsAnimation>
<SunAnimation v-else-if="hasFetchedWeather && weather.weather?.[0]?.main=='Clear'"></SunAnimation>
<NoAnimation v-else-if="hasFetchedWeather"></NoAnimation>

完整修改后的代码

<template> 
  <div id="container" :class="containerTemperature"> 
    <h1>Better<br>Weather</h1> 
    <main id="app" :class="appTemperature"> 
      <div class="search-box"> 
        <input type="text" class="search-bar" placeholder="Search..." v-model="query" @keypress="fetchWeather" /> 
      </div> 
      <!-- 修改后的动画组件条件 -->
      <CloudsAnimation v-if="hasFetchedWeather && weather.weather && weather.weather[0].main=='Clouds'"></CloudsAnimation> 
      <SunAnimation v-else-if="hasFetchedWeather && weather.weather && weather.weather[0].main=='Clear'"></SunAnimation> 
      <NoAnimation v-else-if="hasFetchedWeather"></NoAnimation> 
      <div class="weather-wrap" v-if="typeof weather.main != 'undefined'"> 
        <div class="location-box"> 
          <div class="location">{{ weather.name }}, {{ weather.sys.country }}</div> 
          <div class="date">{{ dateBuilder() }}</div> 
        </div> 
        <div class="weather-box"> 
          <div class="temp">{{ Math.round(weather.main.temp) }}°c</div> 
          <div class="weather">{{ weather.weather[0].main }}</div> 
        </div> 
      </div> 
    </main> 
  </div> 
</template> 

<script> 
import CloudsAnimation from "./components/CloudsAnimation" 
import SunAnimation from "./components/SunAnimation" 
import NoAnimation from "./components/NoAnimation" 

export default { 
  name: 'App', 
  components: { CloudsAnimation, SunAnimation, NoAnimation }, 
  data () { 
    return { 
      api_key: '08f1525958fbc6584f628b6dac25a906', 
      url_base: 'https://api.openweathermap.org/data/2.5/', 
      query: '', 
      weather: {},
      hasFetchedWeather: false // 新增状态
    } 
  }, 
  computed: { 
    containerTemperature: function () { 
      return { 
        'warm-container': typeof this.weather.main != 'undefined' && this.weather.main.temp > 20, 
        'cold-container': typeof this.weather.main != 'undefined' && this.weather.main.temp < 9 
      } 
    }, 
    appTemperature: function () { 
      return { 
        'warm': typeof this.weather.main != 'undefined' && this.weather.main.temp > 20, 
        'cold': typeof this.weather.main != 'undefined' && this.weather.main.temp < 9 
      } 
    } 
  }, 
  methods: { 
    fetchWeather (e) { 
      if (e.key == "Enter") { 
        fetch(`${this.url_base}weather?q=${this.query}&units=metric&APPID=${this.api_key}`) 
          .then(res => { return res.json(); })
          .then(this.setResults) 
          .then(() => this.query = ""); // 修复this指向问题
      } 
    }, 
    setResults (results) { 
      this.weather = results; 
      this.hasFetchedWeather = true; // 更新状态
    }, 
    dateBuilder () { 
      let d = new Date(); 
      let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; // 补全缺失的月份
      let days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; 
      let day = days[d.getDay()]; 
      let date = d.getDate(); 
      let month = months[d.getMonth()]; 
      let year = d.getFullYear(); 
      return `${day} ${date} ${month} ${year}`; 
    } 
  } 
} 
</script> 

<style lang="scss"> 
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400&display=swap'); 
@import url('https://fonts.googleapis.com/css2?family=Rajdhani:wght@300&display=swap'); 

* { 
  margin: 0; 
  padding: 0; 
  box-sizing: border-box; 
}

body { 
  font-family: 'Montserrat', sans-serif; 
}

#container { 
  position: relative; 
  display: flex; 
  justify-content: center; 
  align-items: center; 
  flex-direction: column; 
  height: 100vh; 
  background: rgb(167,207,242); 
  background: linear-gradient(to bottom, #BDF2F2 0%, #34A6BF 50%, #074E8C 100%); 

  h1 { 
    position: absolute; 
    top: 4vh; 
    color: white; 
    text-shadow: 1px 3px 2px rgba(0, 0, 0, 0.25); 
    font-style: oblique; 
    text-align: center; 
    font-family: 'Rajdhani' 
  } 

  #app { 
    background-image: url('./assets/cold.jpg'); 
    background-size: cover; 
    background-position: bottom; 
    transition: 0.4s; 
    width: 350px; 
    height: 500px; 
    border-radius: 20px; 
    box-shadow: 0px 0px 16px rgba(0, 0, 0, 0.25); 
    position: relative; 
    overflow: hidden; 

    &.warm { 
      background-image: url('./assets/warm.jpg') 
    } 

    &.cold { 
      background-image: url('./assets/coldd.jpg') 
    } 

    main { 
      height: 100vh; 
      padding: 25px; 
      background-image: linear-gradient(to bottom, rgba(0,0,0,0.25), rgba(0,0,0,0.75)) 
    } 

    .search-box { 
      width: 100%; 
      margin-bottom: 30px; 

      .search-bar { 
        display: block; 
        width: 100%; 
        padding: 15px; 
        color: #313131; 
        font-size: 20px; 
        appearance: none; 
        border: none; 
        outline: none; 
        background: none; 
        box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.25); 
        background-color: rgba(255, 255, 255, 0.5); 
        border-radius: 0px 16px 0px 16px; 
        transition: 0.4s; 

        &:focus { 
          box-shadow: 0px 0px 16px rgba(0, 0, 0, 0.25); 
          background-color: rgba(255, 255, 255, 0.75); 
          border-radius: 16px 0px 16px 0px; 
        } 
      } 
    } 

    .location-box { 
      .location { 
        color: white; 
        font-size: 32px; 
        font-weight: 500; 
        text-align: center; 
        text-shadow: 1px 2px 2px rgba(0, 0, 0, 0.4); 
      } 

      .date { 
        color: white; 
        font-size: 20px; 
        font-weight: 300; 
        font-style: italic; 
        text-align: center; 
        text-shadow: 1px 2px 2px rgba(0, 0, 0, 0.4); 
      } 
    } 

    .weather-box { 
      text-align: center; 

      .temp { 
        display: inline-block; 
        padding: 10px 25px; 
        color: white; 
        font-size: 82px; 
        font-weight: 900; 
        text-shadow: 3px 6px rgba(0, 0, 0, 0.4); 
        background-color: rgba(255, 255, 255, 0.4); 
        border-radius: 16px; 
        margin: 30px 0px; 
        box-shadow: 3px 6px rgba(0, 0, 0, 0.4); 
      } 

      .weather { 
        color: white; 
        font-size: 48px; 
        font-weight: 700; 
        font-style: italic; 
        text-shadow: 3px 6px 1px rgba(0, 0, 0, 0.25) 
      } 
    } 
  } 

  &.warm-container{ 
    background: rgb(242,160,160); 
    background: linear-gradient(to bottom right, rgba(242,160,160,1) 0%, rgba(242,82,170,1) 50%, rgba(34,32,89,1) 100%); 
  } 

  &.cold-container { 
    background: #D9D9D9; 
    background: linear-gradient(to bottom left, #f0efef 0%, #B4B7BF 50%, #909197 100%); 
  } 
} 
</style>

另外我还修复了你代码里的两个小问题:

  1. fetchWeather方法里的then(this.query = "")改成了箭头函数,避免this指向错误;
  2. dateBuilder里的months数组缺少了April到July,已经补全。

这样修改后,只有当你按下Enter获取到天气数据后,才会根据天气类型显示对应的动画组件,初始状态下不会渲染任何动画组件,也就不会出现undefined的错误了。

内容的提问来源于stack exchange,提问作者Mikado94

火山引擎 最新活动