Vue.js 异步请求模式:使用无渲染组件 内容 想法起源 Vue 组件中的 HTTP 请求:一个典型示例 异步无渲染组件

2025-06-07

Vue.js 异步请求模式:使用无渲染组件

内容

想法的起源

Vue 组件中的 HTTP 请求:一个典型示例

异步无渲染组件

大多数 Vue 应用程序都需要异步 HTTP 请求,并且有很多方法可以实现它们:在mounted()生命周期钩子中、在按钮触发的方法中、在商店中(使用vuex时)或在asyncData()fetch()方法中(使用Nuxt)。

虽然使用 axios 发出简单请求非常容易,但我们通常希望涵盖至少两个附加状态:

  1. 在请求等待期间向用户显示一些内容
  2. 优雅地处理错误

处理这些状态会增加额外的代码,并且在必须实现许多不同的请求时会很快导致代码重复。

内容

  1. 想法的起源
  2. HTTP 请求:一个典型示例
  3. 异步无渲染组件

要直接切入正题,请跳至异步无渲染组件

注意:本例中使用 Axios 发出 HTTP 请求,但它与其他任何 AJAX 请求库配合使用效果同样出色。此外,本例还使用了非常棒的免费 Dog API:https://dog.ceo/dog-api/ 🐶。

想法的起源

这个想法不是我自己的,而是借鉴了 Vue.js 创建者 Evan You @youyuxi的想法,他在第 81 集的Full Stack Radio Podcast上与 Adam Whatan 讨论高级 Vue 组件时再次表达了这个想法

Vue 组件中的 HTTP 请求:一个典型示例

让我们从一个简单的示例开始,请求一张随机的狗狗图片。该mounted()钩子包含一个 axios 调用,用于填充image变量。

Vue.component("example", {
  el: "#example",
  data() {
    return {
      image: null
    };
  },
  mounted() {
    axios
      .get("https://dog.ceo/api/breeds/image/random")
      .then(function(response) {
        this.image = response.data;
      });
  }
});

很简单。但是,我们想要显示加载动画并处理请求错误。因此,除了image变量pending: false和之外,error: null还添加了 。该mounted()钩子如下所示:

Vue.component("example", {
  [...]
  mounted() {
    this.pending = true;
    axios
      .get("https://dog.ceo/api/breeds/image/random")
      .then(function(response) { this.image = response.data })
      .catch(function(error) { this.error = error })
      .finally(function () { this.pending = false });
  }
});

现在,可以为 显示加载指示器pending === true,并在 时显示基本错误消息error !== null。这非常简单,但重复实现待处理/成功/错误行为可能会很繁琐。此外,如果请求包含用户可以更改的参数(例如过滤器或排序选项),则请求必须转移到一个方法,每当参数发生变化时,都必须调用该方法来重新加载数据。

抽象出这种简单行为并使其可重复使用的一个简单有效的方法是......

异步无渲染组件

此组件利用了功能极其丰富的作用域插槽 (Scoped Slot) 功能。插槽是任何可以传递给组件的 HTML 片段,用于告诉组件:“这里,在某个地方渲染它”。有了作用域插槽,接收 HTML 片段的组件会回复:“太棒了,我会把你的 HTML 放在这里。如果你愿意,这里有一些数据,你可以用在片段中。”

Async Renderless 组件就是这样一个组件,它接收一段 HTML、一个 URL 和一些参数并回答:“嘿,看,我正在为你请求这些数据,这是datapendingerror你使用。”

完整的异步无渲染组件:

Vue.component("async", {
  props: {
    url: { type: String, default: "", required: true },
    params: { type: Object, default: () => ({}) }
  },
  data() {
    return {
      pending: true,
      error: false,
      data: null
    };
  },
  watch: {
    url() {
      this.requestData();
    },
    params: {
      handler() {
        this.requestData();
      },
      deep: true
    }
  },
  mounted() {
    this.requestData();
  },
  methods: {
    async requestData() {
      this.pending = true;
      try {
        const { data } = await axios.get(this.url, { params: this.params });
        this.data = data;
        this.error = false;
      } catch (e) {
        this.data = null;
        this.error = e;
      }
      this.pending = false;
    }
  },
  render() {
    return this.$scopedSlots.default({
      pending: this.pending,
      error: this.error,
      data: this.data
    });
  }
});

注意:我在这里使用了一些 javascript 魔法:箭头函数Async/Awaittry...catch

“无渲染”发生在render()标签中。这些组件不使用 HTML 标签,而是仅将其在插槽中接收到的 HTML 代码段渲染为作用域插槽,并向其传递三个数据点:pendingerrordata

这些watch函数确保无论何时urlparams发生改变,数据都会重新加载。

我们在模板中使用异步组件,如下所示:

<async url="https://dog.ceo/api/breed/husky/images">
  <template v-slot:default="{ pending, error, data }">
    <div v-if="pending">Loading ...</div>
    <div v-else-if="error">{{ error }}</div>
    <div v-else>{{ data }}</div>
  </template>
</async>

为什么是无渲染组件而不是混合或指令?

在 Vue 中,组件并非代码复用的唯一方式,另一种方式是使用Mixin自定义指令。这两种方法都能很好地解决这个问题。使用作用域插槽的无渲染组件正是 Vue 所期望的,它可以在需要时导入,就像你习惯于导入其他组件一样。因此,与无需单独导入的 mixin 或指令相比,这是一种非常明确的代码复用方式。最终,这取决于你的偏好。

一个应用示例

我在使用 API 时经常会遇到列表操作,这些 API 通常包含分页、筛选、排序和搜索等功能。因此,我决定编写一个“真实”示例,渲染一个简单的狗狗图片列表,并为不同品种设置一个非常简单的筛选选项(并调用一次错误的 API 来查看错误状态):

每当点击其中一个过滤器按钮时,传递给异步组件的 URL 都会更新为相应的品种。异步组件负责处理 HTTP 请求。父组件中不再需要 HTTP 请求逻辑,从而遵循了关注点分离原则,我们的思维得到了解放,世界也因此变得和谐统一 😄。

文章来源:https://dev.to/lhermann/vue-js-pattern-for-async-requests-using-renderless-components-3gd
PREV
保持整洁:重要的编码标准
NEXT
关于批评