중첩 로더
때로는 요청이 다른 가져온 데이터에 의존합니다(예: 추가 사용자 정보 가져오기). 이런 경우에는 다른 로더를 import해서 다른 로더 안에서 사용하면 됩니다:
필요한 로더 안에서 해당 로더를 호출하고 await 하세요. 그러면 내비게이션 중 몇 번 호출되든 한 번만 가져옵니다:
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된 로더를 또 다른 로더 안에서 사용하는 경우에는 구조가 복잡해질 수 있습니다. 하지만 문제는 아닙니다. 사용자가 별도로 다르게 처리할 필요는 없으며, 로더는 여전히 한 번만 호출됩니다:
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()로 감싸세요:
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)으로 이 문제가 해결될 예정 입니다.