Desmond

Desmond

An introvert who loves web programming, graphic design and guitar
github
bilibili
twitter

Vue3のsetup()内の非同期関数

問題#

非同期の setup () を使用する場合、最初の await ステートメントの前に効果とライフサイクルフックを使用する必要があります。(詳細)

import { ref, watch, onMounted, onUnmounted } from 'vue'

export default defineAsyncComponent({
  async setup() {
    const counter = ref(0)

    watch(counter, () => console.log(counter.value))

    // OK!
    onMounted(() => console.log('Mounted'))

    // the await statement
    await someAsyncFunction() // <-----------

    // does NOT work!
    onUnmounted(() => console.log('Unmounted'))

    // still works, but does not auto-dispose 
    // after the component is destroyed (memory leak!)
    watch(counter, () => console.log(counter.value * 2))
  }
})

await ステートメントの後、次の関数は制限されます(自動破棄なし):

  • watch / watchEffect
  • computed
  • effect

次の関数は機能しません:

  • onMounted / onUnmounted / onXXX
  • provide / inject
  • getCurrentInstance

制限#

非同期関数は、複数のコンポーネント間で共有されるグローバル変数を更新する際に問題を引き起こす可能性があります。setup()が待機されると、複数のコンポーネントの作成がグローバル変数を変更することができ、予測不可能な状態を作り出し、システム内で混乱を引き起こす可能性があります。setup()が await ステートメントなしで呼び出されると、最初の await の前にタスクが実行され、実行の一貫性に影響を与えます。Vue は非同期部分が外部から呼び出されるタイミングを予測することができず、したがってインスタンスをコンテキストにバインドすることはできません。

currentInstance = instance
await component.setup() // atomic lost
currentInstance = prev 
async function setup() {
  console.log(1)
  await someAsyncFunction()
  console.log(2)
}

console.log(3)
setup()
console.log(4)

// 出力:
3
1
4
(awaiting)
2

解決策#

非同期関数を「リアクティブ同期」としてラップする#

From

const data = await fetch('https://api.github.com/').then(r => r.json())

const user = data.user

To

const data = ref(null)

fetch('https://api.github.com/')
  .then(r => r.json())
  .then(res => data.value = res)

const user = computed(() => data?.user)

インスタンスを明示的にバインドする#

import { effectScope, onMounted } from 'vue'

export default defineAsyncComponent({
  async setup() {
    // `await`の前にインスタンスを取得して保持する
    const instance = getCurrentInstance()
    // `await`の前にスコープを作成し、それをインスタンスにバインドする
    const scope = effectScope()

    const data = await someAsyncFunction() // <-----------

    onMounted(
      () => {},
      instance // <--- インスタンスを渡す
    )
    
    scope.run(() => {
      /* `computed`、`watch`などを使用する... */
    })

    // ここではライフサイクルフックは利用できません
  }
})

参考文献:
Anthony Fu: https://antfu.me/posts/async-with-composition-api

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。