什么导致在这个Vue应用程序中无法将Axios注册为全局变量?

huangapple go评论66阅读模式
英文:

What causes the failure to register Axios as a global variable in this Vue app?

问题

I have been working on an SPA with Vue 3, TypeScript and The Movie Database (TMDB) API.

I have been using Axios by importing it in every component that makes use of it (with import axios from "axios") but, since most of my components need it, I thought it was a good idea to register Axios as a global variable.

So, in main.js, I have:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import axios from 'axios'
import "bootstrap/dist/css/bootstrap.min.css"
import "bootstrap"

const app = createApp(App)

app.use(router)

app.config.globalProperties.$axios = axios

app.mount('#app')

In src\components\MoviesList.vue (I use one of the many components as an example here), I have:

<template>
  <div class="row list">
    <div
      v-for="movie in movies"
      :key="movie.id"
      class="col-xs-12 col-sm-6 col-lg-4 col-xl-3"
    >
      <MovieCard :movie="movie" :genres="genres" :showRating="true" />
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import env from "../env";
// import axios from "axios";
import MovieCard from "./MovieCard.vue";

export default defineComponent({
  name: "MoviesList",
  components: { MovieCard },

  props: {
    listType: {
      type: String,
      required: true,
    },
  },

  data: () => ({
    searchTerm: "",
    movies: [],
    genres: [],
  }),

  mounted() {
    this.listMovies();
    this.getGenres();
  },

  methods: {
    listMovies() {
      this.$axios
        .get(
          `${env.api_url}/movie/${this.$props.listType}?api_key=${env.api_key}`
        )
        .then((response) => {
          this.movies = response.data.results;
        })
        .catch((err) => console.log(err));
    },

    getGenres() {
      this.$axios
        .get(`${env.api_url}/genre/movie/list?api_key=${env.api_key}`)
        .then((response) => {
          this.genres = response.data.genres;
        })
        .catch((err) => console.log(err));
    },
  },
});
</script>

The problem

Since making this change, the code fails to compile, and the terminal throws the error:

Property '$axios' does not exist on type 'CreateComponentPublicInstance'.

Questions

  1. What am I doing wrong?
  2. What is the most reliable way to fix this issue?
英文:

I have been working on an SPA with Vue 3, TypeScript and The Movie Database (TMDB) API.

I have been using Axios by importing it in every component that makes use of it (with import axios from &quot;axios&quot;) but, since most of my components need it, I thought it was a good idea to register Axios as a global variable.

So, in main.js, I have:

import { createApp } from &#39;vue&#39;
import App from &#39;./App.vue&#39;
import router from &#39;./router&#39;
import axios from &#39;axios&#39;
import &quot;bootstrap/dist/css/bootstrap.min.css&quot;
import &quot;bootstrap&quot;
const app = createApp(App)
app.use(router)
app.config.globalProperties.$axios=axios
app.mount(&#39;#app&#39;)

In src\components\MoviesList.vue (I use one of the many components as an example here), I have:

&lt;template&gt;
&lt;div class=&quot;row list&quot;&gt;
&lt;div
v-for=&quot;movie in movies&quot;
:key=&quot;movie.id&quot;
class=&quot;col-xs-12 col-sm-6 col-lg-4 col-xl-3&quot;
&gt;
&lt;MovieCard :movie=&quot;movie&quot; :genres=&quot;genres&quot; :showRating=&quot;true&quot; /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;script lang=&quot;ts&quot;&gt;
import { defineComponent } from &quot;vue&quot;;
import env from &quot;../env&quot;;
// import axios from &quot;axios&quot;;
import MovieCard from &quot;./MovieCard.vue&quot;;
export default defineComponent({
name: &quot;MoviesList&quot;,
components: { MovieCard },
props: {
listType: {
type: String,
required: true,
},
},
data: () =&gt; ({
searchTerm: &quot;&quot;,
movies: [],
genres: [],
}),
mounted() {
this.listMovies();
this.getGenres();
},
methods: {
listMovies() {
this.$axios
.get(
`${env.api_url}/movie/${this.$props.listType}?api_key=${env.api_key}`
)
.then((response) =&gt; {
this.movies = response.data.results;
})
.catch((err) =&gt; console.log(err));
},
getGenres() {
this.$axios
.get(`${env.api_url}/genre/movie/list?api_key=${env.api_key}`)
.then((response) =&gt; {
this.genres = response.data.genres;
})
.catch((err) =&gt; console.log(err));
},
},
});
&lt;/script&gt;

The problem

Since making this change, the code fails to compile, and the terminal throw the error:

> Property '$axios' does not exist on type 'CreateComponentPublicInstance'

Questions

  1. What am I doing wrong?
  2. What is the most reliable way to fix this issue?

答案1

得分: 4

The reason it doesn't compile is because you have a Typescript error.
The compiler is instructed to fail the build process on TS errors (not on warnings, though) because the assumption is it will also generate a JS error at runtime. That's not the case here, because $axios is actually added to every component instance, even though Typescript doesn't know it.

One (not recommended) way to go about it is to just ignore the error, keeping Typescript in the dark, using a @ts-ignore comment above every line where you're using this.$axios. You'll find it builds and works as expected.

The proper way of fixing this issue is to inform Typescript about the custom $axios prop you added to every component instance, using a Typescript declaration file (vue-shims.d.ts - or any other name, the .d.ts part is what matters), placed in src/ folder:

import axios from 'axios';

declare module 'vue' {
  interface ComponentCustomProperties {
    $axios: typeof axios
  }
}

This fix has the additional benefit of inferring axios's types, so you get code completion on its methods in most IDE's.

To learn more about Typescript module declarations, here's the documentation.
The most important aspect of module declarations is that they are cumulative. So you're not replacing Vue's 'vue' module declaration, you're adding to it.

As a side note, although what you have works, the recommended way to add an axios instance to Vue config's globalProperties is writing a Vue plugin:

axios-plugin.ts

import { axios } from axios;

const myAxiosInstance = axios.create({
  // your axios config here, interceptors, retries, etc...
})

export default {
  install(app) {
    app.config.globalProperties.$axios = myAxiosInstance
  }
}

... and, in main.ts:

import axiosPlugin from './path/to/axios-plugin';

const app = createApp()
  .use(axiosPugin)
  .mount...

Writing a plugin has the benefit of keeping main.ts clean. Besides, you're passing the same instance of axios to every component (which is useful when you want to apply some configuration or custom logic, like automatically retrying failed requests or re-fetching the auth token upon expiry).

英文:

The reason it doesn't compile is because you have a Typescript error.
The compiler is instructed to fail the build process on TS errors (not on warnings, though) because the assumption is it will also generate a JS error at runtime. That's not the case here, because $axios is actually added to every component instance, even though Typescript doesn't know it.

One (not recommended) way to go about it is to just ignore the error, keeping Typescript in the dark, using a @ts-ignore comment above every line where you're using this.$axios. You'll find it builds and works as expected.

The proper way of fixing this issue is to inform Typescript about the custom $axios prop you added to every component instance, using a Typescript declaration file (vue-shims.d.ts - or any other name, the .d.ts part is what matters), placed in src/ folder:

import axios from &#39;axios&#39;

declare module &#39;vue&#39; {
  interface ComponentCustomProperties {
    $axios: typeof axios
  }
}

This fix has the additional benefit of inferring axios's types, so you get code completion on its methods in most IDE's.


To learn more about Typescript module declarations, here's the documentation.
The most important aspect of module declarations is that they are cumulative. So you're not replacing Vue's &#39;vue&#39; module declaration, you're adding to it.


As a side note, although what you have works, the recommended way to add an axios instance to Vue config's globalProperties is writing a vue plugin:

axios-plugin.ts

import { axios } from axios

const myAxiosInstance = axios.create({
  // your axios config here, interceptors, retries, etc...
})

export default {
  install(app) {
    app.config.globalProperties.$axios = myAxiosInstance
  }
}

... and, in main.ts:

import axiosPlugin from &#39;./path/to/axios-plugin&#39;

const app = createApp()
  .use(axiosPugin)
  .mount...

Writing a plugin has the benefit of keeping main.ts clean. Besides, you're passing the same instance of axios to every component (which is useful when you want to apply some configuration or custom logic (automatically retry failed requests, re-fetch auth token upon expiry, etc...).

答案2

得分: 1

第一次我使用 Vue 3 时,我有与你相同的想法和相同的问题,我阅读了一些指南,发现处理 Axios 的最佳方法是实例化一个 ApiClient

export const apiClient: AxiosInstance = axios.create({
    baseURL: myBaseUrl,
    headers: {
        "Content-type": "application/json",
    },
});

然后,你可以轻松地像这样调用 Axios 动词,我建议你将这些方法封装在函数内:

function get(route, config) {
    return apiClient.get(route, config);
}

如果你期望另一种回答,我很抱歉,但这是我处理你相同问题的方式。

英文:

The first time I worked with Vue 3 I had the your same idea with the same problems, I read about a couple of guides and discovered the best way to handle Axios is to instance an ApiClient :

export const apiClient: AxiosInstance = axios.create({
baseURL: myBaseUrl,
headers: {
&quot;Content-type&quot;: &quot;application/json&quot;,
},
});

Then, you can easily call the Axios verbs like this, I suggest you to wrap these methods inside functions

function get(route, config) {
return apiClient.get(route, config);
}

I am sorry if you expected another response but this is the way I handled your same problem

huangapple
  • 本文由 发表于 2023年5月18日 04:13:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76275896.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定