vue3 基礎概念 (含與 vue2.6 的對比)

vue3 特點

  1. vue3 支持 vue2 的大多數特性
  2. 性能提升:
    打包大小減少41%
    初次渲染快55%,更新快133%
    內存使用減少54%
  3. Composition API
    ref 和 reactive
    computed 和 watch
    新的生命周期函數
    自定義函數——Hooks 函數
  4. 其他新增特性
    Teleport——瞬移組建的位置
    Suspense——異步加載組件的新福音
    全局 API 的修改和優化
    更多的試驗性特性
  5. 更好的 Typescript 支持

為什么要有 vue3

  1. vue2 遇到的難題:同一邏輯分類的代碼分散,不利于維護。
  2. mixins 難點:命名沖突、不清楚暴露出來變量的作用、組件復用時會遇到問題
  3. vue2 對于 typescript 的支持非常有限

應用和組件

  • 創建應用:

    • vue3:
    const app = Vue.createApp({})
    app.component('SearchInput', SearchInputComponent)
    app.directive('focus', FocusDirective)
    app.use(LocalePlugin)
    app.mount('#app')
    
    // 或
    Vue.createApp({})
     .component('SearchInput', SearchInputComponent)
     .directive('focus', FocusDirective)
     .use(LocalePlugin)
     .mount('#app')
    
    • vue2:
    new Vue({
      el: '#app',
      data: obj
    })
    
  • 組件是可復用的 Vue 實例,且帶有一個名字,如 <button-counter>。我們可以在一個通過 new Vue 創建的 Vue 根實例中,把這個組件作為自定義元素來使用。

生命周期鉤子函數

vue3 Lifecycle Hooks
vue2 Lifecycle Hooks
// 主要區別在于銷毀時
beforeCreate() { console.log('實例剛剛被創建') },
created() { console.log('實例創建完成') },
beforeMount() { console.log('實例掛載之前') },
// 請求數據,操作dom , 放在這個里面
mounted() { console.log('實例掛載完成') },
// 數據更新時,虛擬 DOM 變化之前調用,這里適合在更新之前訪問現有的 DOM,比如手動移除已添加的事件監聽器。
beforeUpdate() { console.log('數據更新之前') },
// 數據更新和虛擬 DOM 變化之后調用。請不要在此函數中更改狀態,否則會觸發死循環。
updated() { console.log('數據更新完畢') },

// vue3:
// 實例銷毀之前調用,在這一步,實例仍然完全可用。一般在這里移除事件監聽器、定時器等,避免內存泄漏
beforeUnmount() { console.log('實例銷毀之前') },
// Vue 實例銷毀后調用。調用后,Vue 實例指示的所有東西都會解綁,所有的事件監聽器會被移除,所有的子實例也會被銷毀。
unmounted() { console.log('實例銷毀完成') },

// vue2:
beforeDestroy() { console.log('實例銷毀之前') },
destroyed() { console.log('實例銷毀完成') },

不要在選項 property 、回調上或生命周期函數上使用箭頭函數,比如 created: () => console.log(this.a)vm.$watch('a', newValue => this.myMethod())
因為箭頭函數并沒有 thisthis 會作為變量一直向上級詞法作用域查找,直至找到為止。經常導致 Uncaught TypeError: Cannot read property of undefinedUncaught TypeError: this.myMethod is not a function 之類的錯誤。

不常用模板語法

  • v-once 指令:執行一次性地插值,當數據改變時,插值處的內容不會更新。但請留心這會影響到該節點上的其它數據綁定:

  • 動態參數(2.6.0 新增):
    <a v-bind:[attributeName]="url"> ... </a>
    <a v-on:[eventName]="doSomething"> ... </a>

    • 對動態參數表達式的約束:
      1、動態參數表達式有一些語法約束,因為某些字符,如空格和引號,放在 HTML attribute 名里是無效的。例如:

      <!-- 這會觸發一個編譯警告 -->
      <a v-bind:['foo' + bar]="value"> ... </a>
      

      變通的辦法是使用沒有空格或引號的表達式,或用計算屬性替代這種復雜表達式。

      2、在 DOM 中使用模板時 (直接在一個 HTML 文件里撰寫模板),還需要避免使用大寫字符來命名鍵名,因為瀏覽器會把 attribute 名全部強制轉為小寫:

      <!-- 
      在 DOM 中使用模板時這段代碼會被轉換為 `v-bind:[someattr]`。
      除非在實例中有一個名為“someattr”的 property,否則代碼不會工作。
      -->
      <a v-bind:[someAttr]="value"> ... </a>
      

      動態參數預期會求出一個字符串,異常情況下值為 null。這個特殊的 null 值可以被顯性地用于移除綁定。任何其它非字符串類型的值都將會觸發一個警告。

計算屬性和偵聽器

計算屬性是基于它們的響應式依賴進行緩存的。只在相關響應式依賴發生改變時它們才會重新求值。這就意味著只要 message 還沒有發生改變,多次訪問 reversedMessage 計算屬性會立即返回之前的計算結果,而不必再次執行函數。
相比之下,每當觸發重新渲染時,調用方法將總會再次執行函數。
通常更好的做法是使用計算屬性而不是命令式的 watch 回調。

Class 與 Style 綁定

  • 自動添加前綴
    v-bind:style 使用需要添加瀏覽器引擎前綴的 CSS property 時,如 transformVue.js 會自動偵測并添加相應的前綴。

  • 多重值
    2.3.0 起你可以為 style 綁定中的 property 提供一個包含多個值的數組,常用于提供多個帶前綴的值,如:<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
    這樣寫只會渲染數組中最后一個被瀏覽器支持的值。在本例中,如果瀏覽器支持不帶瀏覽器前綴的 flexbox,那么就只會渲染 display: flex

條件渲染

  • key 管理可復用的元素
    Vue 會盡可能高效地渲染元素,通常會復用已有元素而不是從頭開始渲染。
    這樣也不總是符合實際需求,所以 Vue 為你提供了一種方式來表達“這兩個元素是完全獨立的,不要復用它們”。只需添加一個具有唯一值的 key 即可。

  • v-if vs v-show
    1、v-if 是“真正”的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷毀和重建。
    2、v-if 也是惰性的:如果在初始渲染時條件為假,則什么也不做——直到條件第一次變為真時,才會開始渲染條件塊。
    3、相比之下,v-show 就簡單得多——不管初始條件是什么,元素總是會被渲染,并且只是簡單地基于 CSS 進行切換。
    4、一般來說,v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用 v-show 較好;如果在運行時條件很少改變,則使用 v-if 較好。

    注意,v-show 不支持 <template> 元素,也不支持 v-else

  • v-ifv-for 一起使用
    v-ifv-for 一起使用時,v-for 具有比 v-if 更高的優先級,這意味著 v-if 將分別重復運行于每個 v-for 循環中。不推薦同時使用 v-ifv-for。若使用,eslint 會報錯~~

數組更新檢測

Vue 將被偵聽的數組的變更方法進行了包裹,所以它們也將會觸發視圖更新。這些被包裹過的方法包括:push()pop()shift()unshift()splice()sort()reverse()

事件綁定

同時綁定多個事件

<button @click="one($event), two($event)">Submit</button>

methods: {
  one(event) {
    // first handler logic...
  },
  two(event) {
    // second handler logic...
  }
}

修飾符

  • 事件修飾符
    .prevent:等同于 JavaScript 中的 event.preventDefault(),用于取消默認事件
    .stop:等同于 JavaScript 中的 event.stopPropagation(),防止事件冒泡(由內到外)
    .self:只會觸發自己范圍內的事件,不包含子元素
    .capture:與事件冒泡的方向相反,事件捕獲由外到內
    .once:事件將只會觸發一次
    .passive:設置 {passive: true},表示處理事件函數中不會調用 preventDefault 函數,減少了額外的監聽,從而提高了性能;所以不能和 .prevent 修飾符一同使用,否則瀏覽器會報錯。尤其能夠提升移動端的性能
    .native:把一個 vue 組件轉化為一個普通的 HTML 標簽,并且該修飾符對普通 HTML 標簽是沒有任何作用的。

  • 按鍵修飾符
    .enter.tab.esc.space.up.down.left.right.delete: 捕獲“刪除”和“退格”鍵

  • 系統修飾鍵
    .ctrl.alt.shift.meta

  • 鼠標按鈕修飾符
    .left.right.middle

  • 精確修飾符
    .exact:允許你控制由精確的系統修飾符組合觸發的事件。

    <!-- 即使 Alt 或 Shift 被一同按下時也會觸發 -->
    <button v-on:click.ctrl="onClick">A</button>
    
    <!-- 有且只有 Ctrl 被按下的時候才觸發 -->
    <button v-on:click.ctrl.exact="onCtrlClick">A</button>
    
    <!-- 沒有任何系統修飾符被按下的時候才觸發 -->
    <button v-on:click.exact="onClick">A</button>
    
  • 表單修飾符
    .lazy:在默認情況下,v-model 在每次 input 事件觸發后將輸入框的值與數據進行同步 (除了上述輸入法組合文字時)。你可以添加 lazy 修飾符,從而轉為在 change 事件之后進行同步。
    .number:自動將用戶的輸入值轉為數值類型。
    .trim:自動過濾用戶輸入的首尾空白字符。

    修飾符可以串聯,也可以只有修飾符。
    使用修飾符時,順序很重要;相應的代碼會以同樣的順序產生。因此,用 v-on:click.prevent.self 會阻止所有的點擊,而 v-on:click.self.prevent 只會阻止對元素自身的點擊。

組件

  • 全局組件:使用 app.component('search-input', SearchInputComponent) 定義。只要定義了,處處可以使用,性能不高,但是使用起來簡單。建議小寫字母開頭中劃線間隔命名。

  • 局部組件:使用 components 注冊。定義了,要注冊之后才能使用,性能較高,使用起來麻煩。建議大寫字母開頭駝峰命名。

  • Prop
    1、Prop 的大小寫
    HTML 中的 attribute 名是大小寫不敏感的,所以瀏覽器會把所有大寫字符解釋為小寫字符。這意味著當你使用 DOM 中的模板時,camelCase (駝峰命名法) 的 prop 名需要使用其等價的 kebab-case (短橫線分隔命名) 命名:

    const app = Vue.createApp({})
    
    app.component('blog-post', {
      // camelCase in JavaScript
      props: ['postTitle'],
      template: '<h3>{{ postTitle }}</h3>'
    })
    
    <!-- 在 HTML 中是 kebab-case 的 -->
    <blog-post post-title="hello!"></blog-post>
    

    2、傳入一個對象的所有 property
    如果你想要將一個對象的所有 property 都作為 prop 傳入,你可以使用不帶參數的 v-bind (取代 v-bind:prop-name)。例如,對于一個給定的對象 post

    post: {
      id: 1,
      title: 'My Journey with Vue'
    }
    

    下面的模板:

    <blog-post v-bind="post"></blog-post>
    

    等價于:

    <blog-post :id="post.id" :title="post.title"></blog-post>
    

    3、單項數據流
    所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外變更父級組件的狀態,從而導致你的應用的數據流向難以理解。

    4、Prop 驗證

    app.component('my-component', {
      props: {
        // 基礎的類型檢查 (`null` 和 `undefined` 會通過任何類型驗證)
        propA: Number,
        // 多個可能的類型
        propB: [String, Number],
        // 必填的字符串
        propC: {
          type: String,
          required: true
        },
        // 帶有默認值的數字
        propD: {
          type: Number,
          default: 100
        },
        // 帶有默認值的對象
        propE: {
          type: Object,
          // 對象或數組默認值必須從一個工廠函數獲取
          default: function () {
            return { message: 'hello' }
          }
        },
        // 自定義驗證函數
        propF: {
          validator: function (value) {
            // 這個值必須匹配下列字符串中的一個
            return ['success', 'warning', 'danger'].indexOf(value) !== -1
          }
        }
      }
    })
    

    5、非 prop 的 attribute
    組件可以接受任意的 attribute,而這些 attribute 會被添加到這個組件的根元素上。
    如果你不希望組件的根元素繼承 attribute,你可以在組件的選項中設置 inheritAttrs: false。(不會影響 styleclass 的綁定)

    app.component('date-picker', {
    inheritAttrs: false,
    template: `
      <div class="date-picker">
        <input type="datetime" v-bind="$attrs" />
      </div>
    `
    })
    

    當組件有多個根節點時:

    <custom-layout id="custom-layout" @click="changeValue"></custom-layout>
    
    // This will raise a warning
    app.component('custom-layout', {
      template: `
        <header>...</header>
        <main>...</main>
        <footer>...</footer>
      `
    })
    
    // No warnings, $attrs are passed to <main> element
    app.component('custom-layout', {
      template: `
        <header>...</header>
        <main v-bind="$attrs">...</main>
        <footer>...</footer>
      `
    })
    

自定義事件

不同于組件和 prop,事件名不存在任何自動化的大小寫轉換。因為 HTML 是大小寫不敏感的,因此推薦你始終使用 kebab-case 的事件名。

  • 驗證發出的事件
app.component('custom-form', {
  emits: {
    // No validation
    click: null,

    // Validate submit event
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        console.warn('Invalid submit event payload!')
        return false
      }
    }
  },
  methods: {
    submitForm() {
      this.$emit('submit', { email, password })
    }
  }
})
  • 自定義組件的 v-model
<my-component v-model:title="bookTitle"></my-component>
app.component('my-component', {
  props: {
    title: String
  },
  emits: ['update:title'],
  template: `
    <input 
      type="text"
      :value="title"
      @input="$emit('update:title', $event.target.value)">
  `
})
  • 同時綁定多個 v-model
<user-name
  v-model:first-name="firstName"
  v-model:last-name="lastName"
></user-name>
app.component('user-name', {
  props: {
    firstName: String,
    lastName: String
  },
  emits: ['update:firstName', 'update:lastName'],
  template: `
    <input 
      type="text"
      :value="firstName"
      @input="$emit('update:firstName', $event.target.value)">

    <input
      type="text"
      :value="lastName"
      @input="$emit('update:lastName', $event.target.value)">
  `
})
  • v-model 增加自定義修飾符
<my-component v-model.capitalize="myText"></my-component>
app.component('my-component', {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  template: `
    <input type="text" 
      :value="modelValue"
      @input="emitValue">
  `,
  created() {
    console.log(this.modelModifiers) // { capitalize: true }
  },
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  },
})

插槽

  • 編譯作用域:父級模板里的所有內容都是在父級作用域中編譯的;子模板里的所有內容都是在子作用域中編譯的。

  • 后備內容:

// slot 標簽內為后備內容,父組件提供了插槽內容,則展示;否則展示后備內容
<button type="submit">
  <slot>Submit</slot>
</button>
  • 具名插槽:v-slot 只能添加在 <template> 上(獨占默認插槽的縮寫語法除外)
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <!-- 一個不帶 name 的 <slot> 出口會帶有隱含的名字“default” -->
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
<base-layout>
  <!-- 可簡寫為 <template #header> -->
  <!-- 還可使用動態插槽名:<template v-slot:[dynamicSlotName]> -->
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>
  • 作用域插槽
    為了讓插槽內容能夠訪問子組件中才有的數據,可以將 data 作為 <slot> 元素的一個 attribute 綁定上去。
<!-- 綁定在 <slot> 元素上的 attribute 被稱為插槽 prop -->
<span>
 <slot :user="user">
   {{ user.lastName }}
 </slot>
</span>

<!-- 將包含所有插槽 prop 的對象命名為 slotProps,也可以使用任意你喜歡的名字 -->
<current-user>
 <template v-slot:default="slotProps">
   {{ slotProps.user.firstName }}
 </template>
</current-user>
  • 獨占默認插槽的縮寫語法
    被提供的內容只有默認插槽時,組件的標簽才可以被當作插槽的模板來使用
<!--
解構插槽 Prop:
<current-user v-slot="{ user: person }"> or <current-user #default="{ user }">
-->
<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

provide / inject

使用場景:由于 vue$parent 屬性可以讓子組件訪問父組件。但孫組件想要訪問祖先組件就比較困難。通過 provide / inject 可以輕松實現跨級訪問祖先組件的數據。
provide:Object | () => Object
inject:Array<string> | { [key: string]: string | Symbol | Object }

// TodoList -> TodoListFooter -> TodoListStatistics
app.component('todo-list', {
  // ...
  provide() {
    return {
      todoLength: Vue.computed(() => this.todos.length)
    }
  }
})

app.component('todo-list-statistics', {
  inject: ['todoLength'],
  created() {
    console.log(`Injected property: ${this.todoLength.value}`) // > Injected property: 5
  }
})

提示:provideinject 綁定并不是可響應的。然而,如果你傳入了一個可監聽的對象,那么其對象的屬性還是可響應的。

動態組件與異步組件

動態組件:

<!-- 失活的組件將會被緩存!-->
<keep-alive>
  <component :is="currentTabComponent"></component>
</keep-alive>

注意這個 <keep-alive> 要求被切換到的組件都有自己的名字,不論是通過組件的 name 選項還是局部 / 全局注冊。

異步組件:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/AsyncComponent.vue')
)

app.component('async-component', AsyncComp)

// or
import { createApp, defineAsyncComponent } from 'vue'

createApp({
  // ...
  components: {
    AsyncComponent: defineAsyncComponent(() =>
      import('./components/AsyncComponent.vue')
    )
  }
})

mixin

混入 mixin 提供了一種非常靈活的方式,來分發 Vue 組件中的可復用功能。一個混入對象可以包含任意組件選項。當組件使用混入對象時,所有混入對象的選項將被“混合”進入該組件本身的選項。

  • 例子
// 定義一個混入對象
const myMixin = {
  created() {
    this.hello()
  },
  methods: {
    hello() {
      console.log('hello from mixin!')
    }
  }
}

// 定義一個使用混入對象的組件
const app = Vue.createApp({
  mixins: [myMixin]
})

app.mount('#app') // => "hello from mixin!"
  • 選項合并
    當組件和混入對象含有同名選項時,這些選項將以恰當的方式進行“合并”。

    1. 數據對象在內部會進行遞歸合并,并在發生沖突時以組件數據優先。
    2. 同名鉤子函數將合并為一個數組,因此都將被調用。另外,混入對象的鉤子將在組件自身鉤子之前調用。
    3. 值為對象的選項,例如 methodscomponentsdirectives,將被合并為同一個對象。兩個對象鍵名沖突時,取組件對象的鍵值對。
  • 全局混入
    混入也可以進行全局注冊。一旦使用全局混入,它將影響每一個之后創建的 Vue 實例 (包括第三方組件)。

const app = Vue.createApp({
  // 自定義屬性
  myOption: 'hello!'
})

// 為自定義的選項 'myOption' 注入一個處理器。
app.mixin({
  created() {
    // 獲取自定義屬性 this.$options.XXX
    const myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

// add myOption also to child component
app.component('test-component', {
  myOption: 'hello from component!'
})

app.mount('#app')

// => "hello!"
// => "hello from component!"
  • 自定義選項合并策略
    自定義選項將使用默認策略,即簡單地覆蓋已有值。如果想讓自定義選項以自定義邏輯合并,可以向 app.config.optionMergeStrategies 添加一個函數:
const app = Vue.createApp({})
// 返回合并后的值
app.config.optionMergeStrategies.customOption = (mixinVal, appVal) => mixinVal || appVal

自定義指令

  • 注冊一個全局自定義指令 v-focus
const app = Vue.createApp({})
app.directive('focus', {
  // 當被綁定的元素插入到 DOM 中時……
  mounted(el) {
    // 聚焦元素
    el.focus()
  }
})
  • 注冊局部指令:
app.directive('focus', {
  mounted (el) {
    el.focus()
  }
})

// 若只有 mounted 和 updated,且邏輯一致,可簡寫
// binding.arg 獲取參數;binding.value 獲取值
app.directive('top', (el, binding) => {
   el.style.top = binding.value + 'px'
})
  • 鉤子函數

    1. created: 在綁定元素的屬性或事件偵聽器被應用之前調用,只調用一次
    2. beforeMount: 當指令第一次綁定到元素上并且父組件掛載之前調用
    3. mounted: 當綁定元素的父組件掛載完成時調用
    4. beforeUpdate: 指令所在組件的 VNode 及其子 VNode 更新前調用
    5. updated: 指令所在組件的 VNode 及其子 VNode 更新后調用
    6. beforeUnmount: 當綁定元素的父組件銷毀之前調用
    7. unmounted: 只調用一次,指令與元素解綁時調用
  • 動態指令參數

<div id="dynamicexample">
 <h2>Scroll down the page</h2>
 <input type="range" min="0" max="500" v-model="pinPadding">
 <p v-pin:[direction]="pinPadding">Stick me {{ pinPadding + 'px' }} from the {{ direction }} of the page</p>
</div>
const app = Vue.createApp({
  data() {
    return {
      direction: 'right',
      pinPadding: 200
    }
  }
})

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  },
  updated(el, binding) {
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
})

傳送門 teleport

將插槽內容傳送至指定位置,接受一個 to 的屬性,它接受一個 css query selector 作為參數,這就是代表要把這個組件渲染到哪個 dom 元素中

<body>
    <div id="app"></div>
    <div id="childBox"></div>
    <div id="modals"></div>
    <div class="wrapper"></div>
    <div data-teleport></div>
</body>
const app = Vue.createApp({
        template: `
            <h1>Root</h1>
            <modal-button />
            <parent-component />
            <multiple-teleports />
        `
    })

    // 基礎用法
    app.component('modal-button', {
        template: `
            <button @click="modalOpen = true">
                Open full screen modal! (With teleport!)
            </button>

            <teleport to="body">
                <div v-if="modalOpen" class="modal">
                    I'm a teleported modal! (My parent is "body")
                    <button @click="modalOpen = false">Close</button>
                </div>
            </teleport>
        `,
        data() {
            return { 
                modalOpen: false
            }
        }
    })

    // 與Vue components一起使用,如果<teleport>包含Vue組件,則它仍將是<teleport>父組件的邏輯子組件
    app.component('parent-component', {
        template: `
            <h2>This is a parent component</h2>
            <teleport to="#childBox">
                <child-component name="John" />
            </teleport>
        `
    })

    app.component('child-component', {
        props: [ 'name' ],
        template: `<div>Hello, {{ name }}</div>`
    })

    // 在同一目標上使用多個teleport
    // disabled 可以用于禁用teleport組件的功能,這意味著它的插槽內容將不會被移動到任何位置,而是在周圍父組件中指定<teleport>的地方渲染。
    app.component('multiple-teleports', {
        template: `
            <teleport to="#modals" disabled>
                <div>A</div>
            </teleport>
            <teleport to="#modals">
                <div>B</div>
            </teleport>
            <teleport to=".wrapper">
                <div>C</div>
            </teleport>
            <teleport to="[data-teleport]">
                <div>D</div>
            </teleport>
        `
    })

    app.mount('#app')

Suspense

Suspense 組件用于在等待某個異步組件解析時顯示后備內容。

AsyncShow.vue

<template>
    <div v-for="(item, index) in list" :key="index">{{item}}</div>
</template>

<script lang="ts">
import axios from 'axios'
import { defineComponent, PropType } from 'vue'

export default defineComponent({
    name: 'AsyncShow',
    async setup () {
        const result = await axios.get('https://XXX')

        return {
            list: result.data,
        }
    },
})
</script>

index.vue

<template>
    <Suspense>
        <template #default>
            <async-show />
        </template>
        <template #fallback>
            <h1>Loading !...</h1>
        </template>
    </Suspense>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import AsyncShow from '@/components/AsyncShow.vue'

export default defineComponent({
    name: 'Index',
    components: {
        AsyncShow,
    },
})
</script>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • Vue.js(讀音/vju?/, 類似于 view)是一個構建數據驅動的web 界面的漸進式框架。Vue.js的目...
    xingyunfuhao閱讀 623評論 0 0
  • 指令用在我們的html中。 指令 (Directives) 是帶有v-前綴的特殊特性。指令特性的值預期是單個 Ja...
    Rising_life閱讀 450評論 0 3
  • 構造器 實例化vue時,需傳入一個選項對象,它可以包括 數據、模板、掛載元素、方法和生命周期鉤子屬性與方法 每個v...
    饑人谷_阿銀閱讀 444評論 0 0
  • 下載安裝搭建環境 可以選npm安裝,或者簡單下載一個開發版的vue.js文件 瀏覽器打開加載有vue的文檔時,控制...
    冥冥2017閱讀 6,072評論 0 42
  • 每個 Vue 應用都是通過用 Vue 函數創建一個新的 Vue 實例開始的: 實例生命周期鉤子 每個 Vue 實例...
    Timmy小石匠閱讀 1,385評論 0 11