Skip to content

중첩 로더 ​

때로는 요청이 다른 가져온 데이터에 의존합니다(예: 추가 사용자 정보 가져오기). 이런 경우에는 다른 로더를 import해서 다른 로더 안에서 사용하면 됩니다:

필요한 로더 안에서 해당 로더를 호출하고 await 하세요. 그러면 내비게이션 중 몇 번 호출되든 한 번만 가져옵니다:

ts
import 'vue-router/auto-routes'
import { defineBasicLoader } from 'vue-router/experimental'
// ---cut---
// 사용자 정보용 로더를 import합니다
import { useUserData } from './loaders/users'
import { getCommonFriends, getCurrentUser } from './api'

export const useUserCommonFriends = defineBasicLoader(async route => {
  // 로더는 다른 로더 내부에서 await해야 합니다
  // .        ⤵
  const user = await useUserData()

  // 다른 데이터를 가져옵니다
  const me = await getCurrentUser()
  const commonFriends = await getCommonFriends(me.id, user.id)
  return { ...user, commonFriends }
})

여기서 useUserData()에는 두 가지 다른 사용 방식이 있다는 점을 알 수 있습니다:

  • 하나는 필요한 모든 정보를 동기적으로 반환하는 방식입니다(여기서는 사용하지 않음). 컴포넌트에서 사용하는 composable이 이것입니다
  • 다른 하나는 데이터 Promise만 반환하는 버전입니다. 데이터 로더 내부에서 사용되며 순차적 가져오기를 가능하게 합니다

중첩 무효화 ​

useUserCommonFriends() 로더가 useUserData()를 호출하므로, useUserData()가 어떤 식으로든 무효화되면 useUserCommonFriends()도 자동으로 무효화됩니다. 이는 로더 구현에 따라 달라지며 API의 필수 요구 사항은 아닙니다.

WARNING

두 로더가 서로를 사용할 수는 없습니다. 그렇게 하면 교착 상태 가 발생합니다.

같은 로더를 노출하는 여러 페이지가 있고, 다른 페이지가 그중 일부 이미 export된 로더를 또 다른 로더 안에서 사용하는 경우에는 구조가 복잡해질 수 있습니다. 하지만 문제는 아닙니다. 사용자가 별도로 다르게 처리할 필요는 없으며, 로더는 여전히 한 번만 호출됩니다:

ts
import 'vue-router/auto-routes'
import { defineBasicLoader } from 'vue-router/experimental'
// ---cut---
import {
  getFriends,
  getCommonFriends,
  getUserById,
  getCurrentUser,
} from './api'

export const useUserData = defineBasicLoader('/users/[id]', async route => {
  return getUserById(route.params.id)
})

export const useCurrentUserData = defineBasicLoader(
  '/users/[id]',
  async route => {
    const me = await getCurrentUser()
    // 하나의 fetch로 묶을 수 없는 레거시 API를 상상해 보세요
    const friends = await getFriends(me.id)

    return { ...me, friends }
  }
)

export const useUserCommonFriends = defineBasicLoader(
  '/users/[id]',
  async route => {
    const user = await useUserData()
    const me = await useCurrentUserData()

    const friends = await getCommonFriends(user.id, me.id)
    return { ...me, commonFriends: { with: user, friends } }
  }
)

위 예제에서는 여러 로더를 export하고 있지만, 한 번만 호출되고 데이터를 공유하므로 호출 순서를 신경 쓰거나 최적화하려고 애쓸 필요가 없습니다.

DANGER

주의: 모든 중첩 로더는 부모 로더의 맨 위에서 호출하고 await해야 합니다(useUserData()와 useCurrentUserData() 참고). 그 사이에 다른 일반 await를 넣을 수 없습니다. 정말로 그 사이에서 로더가 아닌 무언가를 await해야 한다면, 로더 컨텍스트가 올바르게 복원되도록 Promise를 withDataContext()로 감싸세요:

ts
export const useUserCommonFriends = defineBasicLoader(async (route) => {
  const user = await useUserData()
  await withContext(functionThatReturnsAPromise())
  const me = await useCurrentUserData()

  // ...
})

이렇게 하면 중첩 로더가 자신의 부모 로더 를 인식할 수 있습니다. 이는 아마 eslint 플러그인으로 lint할 수 있을 것입니다. 자동 withAsyncContext()가 도입되기 전의 <script setup> 문제와 비슷합니다. 동일한 기능을 도입할 수는 있겠지만(vite 플러그인을 통해), 성능 비용도 따릅니다. 앞으로는 async-context 제안(stage 2)으로 이 문제가 해결될 예정 입니다.

모두를 위한 문서 한글화