Vue 中获取接口数据的一些方法

2,694 阅读3分钟

前言

在具有多个组件的大型应用,获取数据的顺序是一个常见的问题——在加载组件之前或之后?。

在这篇文章中,我们将了解在 Vue 复杂场景中获取数据的一些方法。同时我们还将了解如何使用 Vue 3 的组合 API 结合 stale-while-revalidate 库进行数据获取。

路由导航时获取数据

当涉及各种路由组件时,数据获取可能会变得复杂。在某些情况下,我们可能希望在导航特定时机显示一些数据。 Vue Router 通过提供在导航之前或之后获取数据的生命周期来处理。让我们来看看这两种情况。

导航前获取数据

Vue Router 提供了许多生命周期 / 钩子 来处理导航前的数据获取:

  • beforeRouteEnter() 钩子接受三个参数—— to 、 from 和 next 。它用于在对组件进行导航之前获取组件中所需的数据
  • beforeRouteUpdate() 钩子也采用与 beforeRouteEnter() 相同的参数,用于使用已获取的数据更新组件

我们创建带有两个路由组件,显示我们银行账户的当前和分类帐余额。

当显示我们的分类帐余额时,我们希望在导航到组件之前获取数据。首先,我们将创建为分类帐余额提供数据的模拟API数据返回:

// LedgerBalance.js

export default (callback) => {
  const LedgerBalance = "1500 元";
  setTimeout(() => {
    callback(null, LedgerBalance);
  }, 3000);
};

接下来,我们将创建分类帐余额组件:

<!-- LedgerBalance.vue -->

<template>
  <div>分类帐余额: {{ balance }}</div>
</template>
<script>
import ledgerBalance from "../scripts/LedgerBalance";
export default {
  name: "Ledger",
  data() {
    return { balance: null };
  },
  // 组件已被加载
  beforeRouteEnter(to, from, next) {
    ledgerBalance((err, balance) => {
      next(vm => vm.setBalance(err, balance));
    });
  },
  // 在调用 beforeRouteUpdate 时,组件被加载并且路由发生变化
  beforeRouteUpdate(to, from, next) {
    this.balance = null;
    ledgerBalance((err, balance) => {
      this.setBalance(err, balance);
      next();
    });
  },
  methods: {
    setBalance(err, balance) {
      if (err) {
        console.error(err);
      } else {
        this.balance = balance;
      }
    }
  }
};
</script>

在 LedgerBalance.vue 中, beforeRouteEnter() 钩子确保在加载 Ledger 组件之前从 LedgerBalance.js 获取数据。

接下来当 Ledger 被加载,路由发生变化时, beforeRouteUpdate() 中的 setBalance() 方法就会被用来设置数据。

然后我们将为我们的分类账余额定义路由路径:

// main.js

import Vue from "vue";
import VueRouter from "vue-router";
import App from "./App";
import Ledger from "./components/LedgerBalance";
Vue.use(VueRouter);
const router = new VueRouter({
  routes: [
    { path: "/Ledger", component: Ledger }
  ]
});
new Vue({
  render: (h) => h(App),
  router
}).$mount("#app");

定义路由路径后,我们将其包含在 APP 中:

<!-- App.vue -->

<template>
  <div id="app">
    <div class="nav">
      <router-link to="/Ledger">分类账余额</router-link>
    </div>
    <hr>
    <div class="router-view">
      <router-view></router-view>
    </div>
  </div>
</template>
<script>
export default { name: "App" };
</script>

当导航到 Ledger 组件时,我们可以观察到路由没有及时跳转,而是等待 LedgerBalance.js 中的 setTimeout() 函数执行完毕之后才进行跳转

导航后获取数据

在某些情况下,我们可能希望在导航到组件后获取数据。当我们处理实时变化的数据时很有用。比如一个账户的当前余额。在这种情况下,我们首先定义处理当前余额数据的函数:

// CurrentBalance.js

export default (callback) => {
  const CurrentBalance = "1000 元";
  setTimeout(() => {
    callback(null, CurrentBalance);
  }, 3000);
};

接下来,在创建我们的 CurrentBalance 组件时,我们将使用 created() 生命周期钩子来调用我们的数据获取方法 fetchBalance() :

/<!-- CurrentBalance.vue -->

<template>
  <div>你的当前余额是{{ balance }}</div>
</template>
<script>
import currentBalance from "../scripts/CurrentBalance";
export default {
  name: "Current",
  data() {
    return { balance: null };
  },
  created() {
    this.fetchBalance();
  },
  methods: {
    fetchBalance() {
      currentBalance((err, balance) => {
        if (err) {
          console.error(err);
        } else {
          this.balance = balance;
        }
      });
    }
  }
};
</script>

考虑到数据将在导航后获取,通过增加一个加载组件(如进度条或骨架屏)交互会更好一点

Stale-while-revalidate

传统上,Vue 应用程序使用 mounted() 钩子来获取数据。从 API 获取数据类似于下面的代码示例:

<template>
  <div :key="car.carmodel" v-for="car in cars">
    {{ car.carmodel }}
  </div>
</template>

<script>
export default {
  name: 'Cars',
  data() {
    return {
      cars: []
    }
  },
  mounted() {
    fetch('/api/cars')
      .then(res => res.json())
      .then(carJson => {
        this.cars = carJson
      })
  }
}
</script>

假设此处需要更多数据,这可能会导致从多个地方获取数据。在不同组件之间导航将导致为每个导航发出 API 请求。这些 API 请求可能会造成堵塞并给用户带来糟糕的体验:

<template>
   <div v-if="about">
     <div>{{ about.car.model }}</div>
     <div>{{ about.bio }}</div>
   </div>
   <div v-else>
     <Loading />
   </div>
 </template>

 <script>
 export default {
   name: 'Bio',
   props: {
     model: {
       type: String,
       required: true
     }
   },
   data() {
     return {
       about: null
     }
   },

   mounted() {
     fetch(`/api/car/${this.model}`)
       .then(res => res.json())
       .then(car => fetch(`/api/car/${car.id}/about`))
       .then(res => res.json())
       .then(about => {
         this.about = {
           ...about,
           car
         }
       })
   }
 }
 </script>

理想情况下,需要一种有效的方法来缓存已经访问过的组件,这样每个已经访问过的组件都可以很容易地呈现数据,即使它被导航离开和返回时也是如此。这就需要永达stale-while-revalidate

Stale-while-revalidate (swr) 使我们能够在获取额外请求的数据时缓存已获取的数据。在上面的代码示例中,每个查看汽车简介的请求也会重新触发查看汽车模型的请求。

使用 swr ,我们可以缓存汽车的模型,这样当用户请求汽车的简介时,可以在获取汽车简介的数据时看到模型。

在 Vue 中,stale-while-revalidate 概念是通过一个库 (swrv) 实现的,该库主要使用 Vue 的组合 API。采用更具可扩展性的方法,我们可以使用 swrv 来获取汽车的详细信息,如下所示:

import carInfo from './carInfo'
import useSWRV from 'swrv'

export default {
  name: 'Bio',
  props: {
    model: {
      type: String,
      required: true
    }
  },

  setup(props) {
    const { data: car, error: carError } = useSWRV(
      `/api/cars/${props.model}`,
      carInfo
    )
    const { data: about, error: aboutError } = useSWRV(
      () => `/api/cars/${car.value.id}/about`,
      carInfo
    )

    return {
      about
    }
  }
}

第一个 useSWRV 挂钩使用 API 的 URL 作为每个请求的唯一标识符,并且还接受 carInfo 函数。 carInfo 是一个异步函数,用于获取有关汽车的详细信息,它还使用 API 的 URL 作为唯一标识符。

第二个 useSWRV 挂钩监视第一个挂钩的变化,并根据这些变化重新验证其数据。 data 和 error 值分别针对 carInfo 函数发出的请求的成功和失败响应进行填充。

总结

  • 要在路由跳转前获取数据,使用 Vue-Router 提供的生命周期 beforeRouterEnter
  • 在页面加载后获取数据,使用 Vue 生命周期 created 或者 mounted,并结合 loading 提高用户体验
  • 要在路由跳转前、页面加载后 等多处获取数据,提高交互体验,用第三方库 Stale-while-revalidate (swr) 

全文完

谢谢!

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 19 天

点击查看活动详情