Vue3 Composition API

2020 年 9 月,它終于來(lái)了,盡管很早就上了 Beta 版本,但是尤大確一直沒有推出正式版,如今正式版上線,讓我們來(lái)一睹為快,看看 Vue3 帶來(lái)了哪些改變吧!

Vue3 文檔地址:https://v3.cn.vuejs.org/

Composition API


setup

什么是 setup ?vue3 之前我們的代碼邏輯會(huì)在組件的各個(gè)角落,大量碎片化的代碼使得理解和維護(hù)組件變得異常困難,在處理單個(gè)邏輯關(guān)注點(diǎn)時(shí),我們必須不斷地跳轉(zhuǎn)相關(guān)代碼的選項(xiàng)塊。試想一下如果我們能夠?qū)⑼粋€(gè)邏輯的相關(guān)代碼都配置在一起,是不是更容易理解和維護(hù),那么我們將這些邏輯和代碼寫在哪里了?在 vue3 中,我們將此位置稱為 setup 。

setup 執(zhí)行時(shí),組件實(shí)例尚未被創(chuàng)建 (beforeCreate - created),因此在 setup 中沒有 this 。setup 選項(xiàng)應(yīng)該是一個(gè)接收 propscontent 的函數(shù),我們這里先來(lái)看看 props 的用法,在 setup 函數(shù)中除了 props 之外,我們無(wú)法訪問組件中聲明的任何屬性(本地狀態(tài)、計(jì)算屬性或方法)。直接上栗子吧:

<!DOCTYPE html>
<html lang="en">
<head>
  <script src="https://unpkg.com/vue@next"></script>
</head>
<body>
  <div id="root"></div>
</body>
<script>
  const app = Vue.createApp({
    template: `
      <test username='張三' />
    `
  })
  app.component('test', {
    props: ['username'],
    setup(props) {
      console.log(props) // Proxy {username: "張三"}
      // console.log(this) // window
    },
    template: `<div>{{username}}</div>`
  })
  const vm = app.mount('#root')
</script>
</html>

上述代碼中,我們創(chuàng)建全局組件 test , 父組件往 test 上傳入 username,test 中通過 props 接收父組件傳遞過來(lái)的 username,此時(shí)在子組件的 setup 執(zhí)行時(shí)可以接收到 props 中的值,但是打印出 this 是指向 window 的。

但是 setup 函數(shù)返回的所有內(nèi)容都將暴露給組件的其余部分(計(jì)算屬性、方法、生命周期鉤子等等)以及組件的模板。我們還是在剛剛的 test 組件中來(lái)驗(yàn)證下這句話:

app.component('test', {
  props: ['username'],
  mounted() { // mounted 聲明周期中可以使用 setup 中返回出來(lái)的 username
    console.log(this.username) // 張三  
  },
  setup(props) {
    const username = props.username
    return { username }
  },
  template: `<div>{{username}}</div>` // 張三
  })
const vm = app.mount('#root') 

當(dāng)然我們也可以在組件的生命周期或者方法中直接訪問 setup 函數(shù)里面的方法,栗子如下:

const app = Vue.createApp({
  mounted() {
    // 可以直接在生命周期里面調(diào)用 setup() 里面的方法
    this.$options.setup().handleClick()
  },
  setup(props, context) {
    return {
      handleClick() {
        alert('setup')
    }
  }
}})

總結(jié):執(zhí)行 setup() 是在 created() 之前執(zhí)行,由于尚未創(chuàng)建實(shí)例,所以 setup()中無(wú)法直接使用 this,但是 methods/computed/watch 等其他地方都可以直接調(diào)用 setup()。并且 setup() 返回的所有內(nèi)容都將暴露給組件的其余部分(計(jì)算屬性、方法、生命周期鉤子等等)以及組件的模板。

ref 語(yǔ)法

vue3 之前,我們只需要在 data 中定義變量,那么這個(gè)變量在 methods/computed/watch 等中的改變就會(huì)直接響應(yīng)到組件模板中了。但是在 vue3 之中我們只有通過 ref 函數(shù)和 reactive 函數(shù)來(lái)實(shí)現(xiàn)響應(yīng)式變量在組件任何地方起作用。我們先通過小栗子可以了解 ref 函數(shù)的基本用法:

const app = Vue.createApp({
  setup() {
    // 通過 ref 初始化一個(gè)響應(yīng)式變量 count 并將其初始值設(shè)為 0
    const { ref } = Vue
    let count = ref(0)
    const increase = () => {
      return count.value += 1
    }
    return { count, increase }
  },
  template: `
    <div>{{count}}</div>
    <button @click="increase">增加</button>
  `
})

通過上述代碼我們可以看到 ref 接受參數(shù),并將其包裹在一個(gè)帶有 value property 的對(duì)象中返回,然后可以使用該 property 訪問或更改響應(yīng)式變量的值。那么問題來(lái)了為什么要將值封裝在一個(gè)對(duì)象中呢?這是因?yàn)樵?JavaScript 中,NumberString 等基本類型是通過值傳遞的,而不是通過引用傳遞的。我們知道值傳遞一般指在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)復(fù)制一份傳遞到函數(shù)中,這樣如果在函數(shù)中如果對(duì)參數(shù)進(jìn)行修改,不會(huì)影響到實(shí)際參數(shù),既占用內(nèi)存空間也無(wú)法實(shí)現(xiàn)數(shù)據(jù)的響應(yīng)式。而通過引用傳遞其實(shí)是直接把內(nèi)存地址傳過去,也就是說在引用傳值的過程中操作的都是源數(shù)據(jù),也就能更好的實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式驅(qū)動(dòng)。

其實(shí)簡(jiǎn)單點(diǎn)就是基礎(chǔ)類型的值都是存在棧中,引用類型的值都是存在堆中,堆共用一個(gè)數(shù)據(jù)源,而每個(gè)棧都是一個(gè)獨(dú)立的空間,所以我們將數(shù)據(jù)存入堆中才能更好的做到數(shù)據(jù)響應(yīng)式改變。

reactive 語(yǔ)法

reactive 語(yǔ)法和 ref 都是支持?jǐn)?shù)據(jù)的響應(yīng)式,但是 reactive 接收的一般是數(shù)組和對(duì)象,直接來(lái)看小栗子:

const app = Vue.createApp({
  setup() {
    // 通過 reactive 初始化一個(gè)響應(yīng)式對(duì)象
    const { reactive, computed } = Vue
    const data = reactive({
      count: 0,
      increase: () => data.count++,
      docuble: computed(() => data.count * 2)
    })
    return { data }
  },
  template: `
    <div>{{data.count}}</div>
    <div>{{data.docuble}}</div>
    <button @click="data.increase">增加</button>
  `
})

上述代碼中我們會(huì)發(fā)現(xiàn)模板中每次都要寫 data.count 、data.docuble 過于繁瑣,如果我們?cè)?return { data } 時(shí)使用展開運(yùn)算符或者對(duì)象賦值的方式導(dǎo)出,是否可以直接使用 count 、 docuble 等變量呢。

// 使用展開運(yùn)算符
return {...data}
// 使用對(duì)象賦值
return {
  count: data.count,
  increase: data.increase,
  docuble: data.docuble
}

我們發(fā)現(xiàn)取出來(lái)之后,count、increasedocuble 都喪失了響應(yīng)式的活性,他們變成了不同的 javascript 類型。那么我們?nèi)绾谓鉀Q這種情況呢?vue3 為我們推出了 toRefs 函數(shù)。

toRefs 語(yǔ)法

我們先試用 toRefs 改寫上面的代碼:

const app = Vue.createApp({
  setup() {
    // 通過 reactive 初始化一個(gè)響應(yīng)式對(duì)象
    const { reactive, computed, toRefs } = Vue
    const data = reactive({
      count: 0,
      increase: () => data.count++,
      docuble: computed(() => data.count * 2)
    })
    // toRefs 將 data 轉(zhuǎn)變?yōu)橐粋€(gè)響應(yīng)式對(duì)象
    const { count, increase, docuble } = toRefs(data)
    return { count, increase, docuble }
},
  template: `
    <div>{{count}}</div>
    <div>{{docuble}}</div>
    <button @click="increase">增加</button>
  `
})

toRefs 函數(shù)接收一個(gè) reactive 對(duì)象作為參數(shù),返回一個(gè)普通的對(duì)象,但是這個(gè)普通對(duì)象的每一項(xiàng)都變成了 ref 類型的對(duì)象,即支持響應(yīng)式。

1、ref / reactive 通過 proxy 對(duì)數(shù)據(jù)進(jìn)行封裝,當(dāng)數(shù)據(jù)變化時(shí),觸發(fā)模板等內(nèi)容的更新。
2、ref 處理基礎(chǔ)類型的數(shù)據(jù)。例如 ref('cc') 變成 proxy({value: 'chen'}) 這樣一個(gè)響應(yīng)式的引用。
3、reactive 處理非基礎(chǔ)類型的數(shù)據(jù)。例如 reactive({name: 'cc'}) 變成 proxy({name: 'cc'}) 這樣一個(gè)響應(yīng)式引用。
4、toRefs 接收一個(gè) reactive 對(duì)象作為參數(shù),返回一個(gè)普通對(duì)象,并將普通對(duì)象的每一項(xiàng)都變成了 ref 類型的對(duì)象,保持器響應(yīng)式活力。例如:toRefs proxy({name: 'cc'}) 變成 {name: proxy({value: 'cc'})}

readonly 語(yǔ)法

獲取一個(gè)對(duì)象 (響應(yīng)式或純對(duì)象) 或 ref 并返回原始 proxy 的只讀 proxy。舉個(gè)栗子:

const app = Vue.createApp({
    setup(props, context) {
      const { reactive, readonly, toRefs } = Vue
      let nameObj = reactive([123])
      // 獲取 nameObj 這個(gè)響應(yīng)對(duì)象為參數(shù)
      let copyNameObj = readonly(nameObj)
      setTimeout(() => {
        nameObj[0] = 456
        // 因?yàn)槭褂昧?readonly 無(wú)法改變 copyNameObj 的值
        copyNameObj[0] = 456
      }, 2000)
      return { nameObj, copyNameObj }
    }
  })

toRef 語(yǔ)法

toRefs 只差了一個(gè) s,那么它有什么用呢?讓我們先來(lái)假設(shè)一個(gè)場(chǎng)景,如下栗子:

const app = Vue.createApp({
  template: `
    <div>{{age}}</div>
  `,
  setup(props, context) {
    const { reactive, toRefs, toRef } = Vue
    const data = reactive({ name: 'zhangsan' })
    const { age } = toRefs(data)
    console.log(age) // undefined
    setTimeout(() => {
      age.value = 20 // 報(bào)錯(cuò) Cannot set property 'value' of undefined
    }, 2000)
    return { age }
  }
}).mount('#root')

我們定義了一個(gè)響應(yīng)式變量 data ,toRefs 會(huì)將相應(yīng)對(duì)象解析成一個(gè)普通對(duì)象,并保證該普通對(duì)象的屬性都是 ref 響應(yīng)式屬性,我們又想在新增加一個(gè) age 的響應(yīng)式變量,結(jié)果只得到了一個(gè) undefined 類型而不是預(yù)料中的 ref 類型。此時(shí)我們可以使用 toRef 改造上面的代碼:

const app = Vue.createApp({
  template: `
    <div>{{age}}</div>
  `,
  setup(props, context) {
    const { reactive, toRefs, toRef } = Vue
    const data = reactive({ name: 'zhangsan' })
    const age = toRef(data, 'age')
    setTimeout(() => {
      age.value = 20
    }, 2000)
    return { age }
  }
}).mount('#root')

toRef 可以用來(lái)為源響應(yīng)式對(duì)象上的某個(gè) property 新創(chuàng)建一個(gè) ref,這里我們就新建了一個(gè) ref 類型的 age 變量,然后 age 就可以完美繼承響應(yīng)活性了。

setup 參數(shù) context

前面我們介紹了 setup 函數(shù)中的第一個(gè)參數(shù) props,這里我們?cè)賮?lái)看看它的第二個(gè)參數(shù) context 的用法,根據(jù)官網(wǎng)文檔傳遞給 setup 函數(shù)的第二個(gè)參數(shù)是 context。context 是一個(gè)普通的 JavaScript 對(duì)象,它暴露三個(gè)組件的 property

export default {
  setup(props, context) {
    const { attrs, slots, emit } = context
  }
}

context 是一個(gè)普通的 JavaScript 對(duì)象,也就是說,它不是響應(yīng)式的,這意味著你可以安全地對(duì) context 使用 ES6 解構(gòu)。

export default {
  setup(props, { attrs, slots, emit }) {}
}

那首先我們來(lái)看一下 attrs 的使用:

const app = Vue.createApp({
  template: `
    <div>
      <child username="zhangsan"></child>
    </div>
  `
})
app.component('child', {
  template: `
    <div></div>
  `,
  setup(props, context) {
    const { attrs, slots, emit } = context
    // attrs 接收一個(gè) None-Props 屬性
    console.log(attrs.username)
  }
})
const vm = app.mount('#root')

什么是 None-Props 屬性 呢?正常情況下,父組件給子組件傳遞了 username ,子組件應(yīng)該通過 props: ['username'] 去接收,但是如果子組件如果不使用 props 接收,那么就可以在 attrs 處接收到 username。而這個(gè) username 就是 None-Props 屬性 。

slots 看名字應(yīng)該就可以大致猜到,這是用來(lái)接收插槽的,同樣是上面的栗子,我們改寫一下:

const app = Vue.createApp({
  template: `
  <div>
    <child>parent</child>
  </div>
`
})
app.component('child', {
  setup(props, context) {
    const { h } = Vue
    const { attrs, slots, emit } = context
    return () => h('div', {}, slots.default())
  }
})
const vm = app.mount('#root')

而如果不在 setup 函數(shù)中,而是在組件外部的生命周期函數(shù)中,我們只能通過 this 去獲取 slots ,現(xiàn)在我們可以完全脫離 vue2 的寫法,將所有的東西都聚合到 setup 函數(shù)中。

mounted() {
  console.log(this.$slots.default())
},

emit 看名字其實(shí)應(yīng)該也可以猜出來(lái),子組件像父組件的事件傳遞,直接看栗子:

const app = Vue.createApp({
  template: `
    <div>
      <child @change="change"></child>
    </div>
  `,
  setup() {
    const change = () => {
      alert('change')
    }
    return { change }
  }
})
app.component('child', {
  template: `
    <div @click="handleClick">child</div>
  `,
  setup(props, context) {
    const { attrs, slots, emit } = context
    const handleClick = () => {
      // 使用 emit 直接傳遞給父組件
      emit('change')
    }
    return { handleClick }
  }
})
const vm = app.mount('#root')

watch 語(yǔ)法

熟悉 vue2 的小伙伴對(duì) watch 肯定不陌生,在 vue3 中,我們可以把 watch 集成到 setup 函數(shù)中進(jìn)行使用。watch 需要偵聽特定的數(shù)據(jù)源,并在單獨(dú)的回調(diào)函數(shù)中執(zhí)行副作用。默認(rèn)情況下,它也是惰性的——即回調(diào)僅在偵聽源發(fā)生更改時(shí)被調(diào)用。

使用 watch 監(jiān)聽單個(gè)數(shù)據(jù)源

const app = Vue.createApp({
  setup() {
    const { ref, reactive, watch } = Vue
    let title = ref("")
    const editTitle = () => {
      title.value = 'hello world'
    }
    watch(title, (newValue, oldValue) => {
      console.log('old', oldValue) // 'old', ""
      console.log('new', newValue) // 'new', "hello world"
    })
    return { title, editTitle }
  },
  template: `
    <div>ref 值監(jiān)聽:{{title}}</div>
    <button @click="editTitle">改變title</button>
  `
}).mount('#root')

使用 watch 監(jiān)聽多個(gè)數(shù)據(jù)源

const app = Vue.createApp({
  setup() {
    const { ref, reactive, watch } = Vue
    let title = ref("")
    const data = reactive({
      count: 0
    })
    const editTitle = () => {
      title.value = 'hello world'
    }
    const editCount = () => {
      data.count++
    }
    // watch 可以監(jiān)聽一個(gè)數(shù)組,里面是需要監(jiān)聽的多個(gè)值
    watch([title, data], (newValue, oldValue) => {
      console.log('old', oldValue) //  ["", Proxy]
      console.log('new', newValue) // ["hello world", Proxy]
    })
    return { title, editTitle, data, editCount }
  },
  template: `
    <div>reactive 值監(jiān)聽:{{data.count}}</div>
    <div>ref 值監(jiān)聽:{{title}}</div>
    <button @click="editTitle">改變title</button>
    <button @click="editCount">改變count</button>
  `
}).mount('#root')

上面的栗子中,我們使用監(jiān)聽了 reactive 新增監(jiān)聽了一個(gè) data 對(duì)象,但是如果我們只想監(jiān)聽 data 對(duì)象中的 count 屬性,而不是監(jiān)聽整個(gè) data 對(duì)象,那么我們應(yīng)該如何實(shí)現(xiàn)呢?

// watch 中接收一個(gè)函數(shù),其返回值為 reactive 函數(shù)對(duì)象中想監(jiān)聽的值
watch([title, () => data.count], (newValue, oldValue) => {
  console.log('old', oldValue) // ["", 0]
  console.log('new', newValue) // ["hello world", 1]
})

watchEffect 語(yǔ)法

watchEffectwatch 都是用來(lái)對(duì)數(shù)據(jù)的偵聽,但是他們有 3 個(gè)比較大的差別:

1、watchEffect 立即執(zhí)行,沒有惰性。
2、不需要傳遞你要偵聽的內(nèi)容,自動(dòng)會(huì)感知代碼依賴,不需要傳遞很多參數(shù),只需要傳遞一個(gè)回調(diào)函數(shù)。
3、不能獲取之前數(shù)據(jù)的值 。

舉個(gè)栗子:

const app = Vue.createApp({
  template: `
    <input v-model="nameObj.name" />
  `,
  setup() {
    const { reactive, watchEffect } = Vue
    const nameObj = reactive({ name: 'cc' })
    watchEffect(() => {
      // 初次加載就會(huì)執(zhí)行打印出 `abc`
      // 但是自動(dòng)檢測(cè)到 `abc` 和頁(yè)面沒有任何關(guān)系后面就不會(huì)在執(zhí)行 
      console.log('abc')
      // nameObj.name 綁定了頁(yè)面的 input,所以每次輸入改變 name 的值都會(huì)執(zhí)行一次
      console.log(nameObj.name)
    })
    return { nameObj }
  }
}).mount('#root')

如果我們要在 5 秒后停止 watch 或者 watchEffect 對(duì)屬性的監(jiān)聽?wèi)?yīng)該如何做呢,如下栗子:

const app = Vue.createApp({
  template: `
    <input v-model="nameObj.name" />
  `,
  setup() {
    const { reactive, watchEffect } = Vue
    const nameObj = reactive({ name: 'cc' })
    // 將函數(shù)變成一個(gè)命名函數(shù),然后 5s 后在執(zhí)行一次這個(gè)命名函數(shù)(watch 同理)
    const stop = watchEffect(() => {
      console.log(nameObj.name)
      setTimeout(() => {
        stop()
      }, 5000)
    })
    return { nameObj }
  }
}).mount('#root')

老瓶新酒 - 生命周期

vue3 組件中的生命周期和 vue2 組件生命周期基本相同,唯一不同的是將 beforeDestroydestroyed 改成了 beforeUnmountunmounted,根據(jù)尤大的說法是后者語(yǔ)義性更強(qiáng),更能表達(dá)組件卸載的說法。通過代碼簡(jiǎn)單回顧下:

// 生命周期函數(shù):在某一時(shí)刻會(huì)自動(dòng)執(zhí)行的函數(shù)
const app = Vue.createApp({
  data() {
    return {
      message: 'see you'
    }
  },
  template: `
    <div @click="handleClickItem">{{message}}</div>
  `,
  methods: {
    handleClickItem() {
      this.message = 'bye bye'
      setTimeout(() => {
        app.unmount()
      }, 1000)
    }
  },
  // 在實(shí)例生成之前會(huì)自動(dòng)執(zhí)行的函數(shù)
  beforeCreate() {
    console.log('beforeCreated', this.message)
  },
  // 在實(shí)例生成之后會(huì)自動(dòng)執(zhí)行的函數(shù)
  created() {
    console.log('created', this.message)
  },
  // 在組件內(nèi)容被渲染到頁(yè)面之前自動(dòng)執(zhí)行的函數(shù)
  beforeMount() {
    console.log('beforeMounte', document.getElementById('root').innerHTML)
  },
  // 在組件內(nèi)容被渲染到頁(yè)面之后自動(dòng)執(zhí)行的函數(shù)
  mounted() {
    console.log('mounted', document.getElementById('root').innerHTML)
  },
  // 在組件內(nèi)容被修改之前自動(dòng)執(zhí)行的函數(shù)
  beforeUpdate() {
    console.log('beforeUpdate', document.getElementById('root').innerHTML)
  },
  // 在組件內(nèi)容被修改之后自動(dòng)執(zhí)行的函數(shù)
  updated() {
    console.log('updated', document.getElementById('root').innerHTML)
  },
  // 在 Vue 應(yīng)用失效時(shí),自動(dòng)執(zhí)行的函數(shù) 可以通過 app.unmount() 觸發(fā)
  beforeUnmount() {
    console.log('beforeUnmount', document.getElementById('root').innerHTML)
  },
  // 當(dāng) Vue 應(yīng)用失效且 Dom 完全銷毀之后,自動(dòng)執(zhí)行的函數(shù)
  unmounted() {
    console.log('unmount', document.getElementById('root'))
  }
})
const vm = app.mount('#root')

setup 函數(shù)中的調(diào)用生命周期鉤子

  • beforeCreate -> 不需要
  • created -> 不需要
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeUnmount -> onBeforeUnmount
  • unmounted -> onUnmounted
  • errorCaptured -> onErrorCaptured
  • renderTracked -> onRenderTracked
  • renderTriggered -> onRenderTriggered

因?yàn)?setup 是圍繞 beforeCreatecreated 生命周期鉤子運(yùn)行的,所以不需要顯式地定義它們。換句話說,在這些鉤子中編寫的任何代碼都應(yīng)該直接在 setup 函數(shù)中編寫。

vue3 模塊化開發(fā)

前面我們所有的邏輯代碼都寫進(jìn)了 setup 函數(shù)中,雖然所有的代碼都寫在 setup 函數(shù)中是可以將邏輯都聚合到一起,但是如果一個(gè)頁(yè)面所有的邏輯代碼都寫入一個(gè)函數(shù),只會(huì)讓代碼顯得更加難以維護(hù)。也許你寫完當(dāng)前也頁(yè)面的所有邏輯代碼有 1000 行,但是三天之后你在想維護(hù)這段代碼,估計(jì)你自己也會(huì)懵逼,不知從何寫起。所以日常開發(fā)中我們更應(yīng)該將 setup 當(dāng)做一個(gè)流程處理函數(shù),將該頁(yè)面的所有邏輯抽離成一個(gè)一個(gè)模塊,將每個(gè)模塊的結(jié)果導(dǎo)出到 setup 函數(shù)中供頁(yè)面模板使用。我們先來(lái)看一個(gè)簡(jiǎn)單的栗子:

我們這里實(shí)現(xiàn)一個(gè)簡(jiǎn)單的功能,就是記錄每次鼠標(biāo)點(diǎn)擊屏幕時(shí)打印出鼠標(biāo)當(dāng)前的坐標(biāo)值!

// 將鼠標(biāo)坐標(biāo)值更新邏輯抽離成一個(gè)單獨(dú)的函數(shù)
const { reactive, toRefs, onMounted, onUnmounted } = Vue
const updateMouseEffect = () => {
    const mouseObj = reactive({
      x: 0,
      y: 0
    })
    const updateMouse = (e) => {
      mouseObj.x = e.pageX
      mouseObj.y = e.pageY
    }
    onMounted(() => {
      document.addEventListener('click', updateMouse)
    })
    onUnmounted(() => {
      document.removeEventListener('click', updateMouse)
    })
    const { x, y } = toRefs(mouseObj)
    return { x, y }
}
const app = Vue.createApp({
    // 頁(yè)面上的每一個(gè)邏輯都抽離成對(duì)應(yīng)的函數(shù)
    // setup 函數(shù)只負(fù)責(zé)將封裝的邏輯中導(dǎo)出的值引入,不涉及具體邏輯編寫
    setup() {
      const { x, y } = updateMouseEffect()
      return { x, y }
    },
    template: `
      <h1>x: {{x}} y: {{y}}</h1>
    `
}).mount('#root')

看了上面的代碼,是不是覺得 vue3 可以完美實(shí)現(xiàn)各種 hooks ,接下來(lái)我們結(jié)合 axios 來(lái)實(shí)現(xiàn)一個(gè)涉及外部請(qǐng)求的封裝函數(shù)。這里都是用的 cdn ,如需復(fù)現(xiàn)栗子,記得頭部引入 axioscdn 鏈接。

const { ref } = Vue
const useURLLoader = (url) => {
    const result = ref(null) // 響應(yīng)結(jié)果
    const loading = ref(true) // 是否顯示loading
    const loaded = ref(false) // 是否加載完成
    const error = ref(null) // 是否響應(yīng)錯(cuò)誤

    axios.get(url).then((rawData) => {
      loading.value = false
      loaded.value = true
      result.value = rawData.data
    }).catch((e) => {
      error.value = e
    })

    return { result, loading, loaded, error }
}
const url = 'https://dog.ceo/api/breeds/image/random'
const app = Vue.createApp({
    setup() {
        const { result, loading, loaded, error } = useURLLoader(url)
        return { result, loading, loaded, error }
  },
    template: `
        <h1 v-if="loading">Loading!...</h1>
        <img v-if="loaded" :src="result.message" >
    `
}).mount('#root')

這個(gè)栗子其實(shí)就是我們?cè)诎l(fā)請(qǐng)求時(shí)希望頁(yè)面顯示 loading ,拿到請(qǐng)求結(jié)果之后將 loading 狀態(tài)取消然后展示我們拿到的結(jié)果數(shù)據(jù),代碼很簡(jiǎn)單,也是將所有的邏輯代碼都抽離到一個(gè)單獨(dú)的函數(shù)中,setup 函數(shù)只負(fù)責(zé)流程控制和數(shù)據(jù)導(dǎo)出。

常用的一些都整理出來(lái)了,本來(lái)想繼續(xù)整理一些 vue3 中的新特性,但是篇幅太長(zhǎng)了,就準(zhǔn)備在下一篇中繼續(xù)整理了,如有錯(cuò)誤或不正確的地方歡迎指正,每天進(jìn)步一點(diǎn)點(diǎn),加油?。。?/p>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容