useI18n()은 setup 밖에서 안 되는데 Pinia store는 왜 될까?
API 모듈이나 유틸 같은 일반 .ts 파일에서 useI18n()을 호출하면 에러가 나는데, useCounterStore()는 잘 동작한다. 둘 다 use로 시작하고 둘 다 app.use()로 등록하는데 왜 다를까?
결론부터 말하면 기준은 ".ts 파일이냐 .vue 파일이냐"가 아니라 "현재 컴포넌트 인스턴스가 필요한가" 다.
useI18n(): 컴포넌트 setup 안에서만 호출 가능
useI18n()은 내부에서 getCurrentInstance()로 지금 setup 중인 컴포넌트를 찾고, 그 컴포넌트의 앱 컨텍스트에서 i18n 인스턴스를 꺼낸다(컴포넌트별 로컬 스코프 메시지도 지원하기 때문). 컴포넌트 setup 밖에서는 현재 인스턴스가 없으므로 에러가 난다.
// api/error-message.ts
import { useI18n } from 'vue-i18n';
const { t } = useI18n(); // ❌ Must be called at the top of a `setup` function
같은 코드라도 .ts 파일에 있는 composable을 컴포넌트의 setup에서 호출하면 정상 동작한다. 파일 확장자는 상관없다.
컴포넌트 밖에서 번역하기: i18n.global
i18n 인스턴스를 모듈로 내보내고 global composer를 직접 사용한다.
// i18n.ts
import { createI18n } from 'vue-i18n';
export const i18n = createI18n({
legacy: false,
locale: 'ko',
fallbackLocale: 'en',
messages: {
en: { welcome: 'Welcome' },
ko: { welcome: '환영합니다' },
},
});
// main.ts
app.use(i18n);
// api/error-message.ts
import { i18n } from '@/i18n';
export const getErrorMessage = (key: string) => i18n.global.t(key); // ✅
Pinia store: 활성 Pinia만 있으면 어디서든 호출 가능
Pinia의 useStore()는 컴포넌트 인스턴스 대신 활성 Pinia(active pinia) 를 찾는다. app.use(pinia)를 실행하면 그 Pinia가 활성 Pinia로 설정되므로, 그 이후에 실행되는 코드라면 컴포넌트 밖(라우터 가드, API 모듈 등)에서도 store를 쓸 수 있다.
// api/client.ts
import { useAuthStore } from '@/stores/auth';
export async function request(url: string) {
const auth = useAuthStore(); // ✅ 함수 실행 시점에는 app.use(pinia)가 끝나 있음
return fetch(url, { headers: { Authorization: `Bearer ${auth.token}` } });
}
다만 Pinia도 완전히 독립적인 것은 아니다. 모듈 최상단처럼 app.use(pinia) 이전에 실행되는 위치에서 호출하면 에러가 난다.
// api/client.ts
import { useAuthStore } from '@/stores/auth';
const auth = useAuthStore(); // ❌ getActivePinia() was called but there was no active Pinia
그래서 store는 모듈 최상단이 아니라 함수 안에서 호출하는 것이 안전하다. SSR에서는 요청마다 Pinia가 달라야 하므로 컴포넌트 밖 사용에 더 주의해야 한다.
커스텀 composable은?
ref, computed만 쓰는 composable은 어디서 호출해도 동작한다. 하지만 내부에서 onMounted 같은 생명주기 훅, inject, useRoute, useI18n 등 컴포넌트 인스턴스가 필요한 API를 쓰면 그 composable도 setup 안에서만 호출할 수 있다.
정리
| 함수 | 필요한 것 | 컴포넌트 밖에서 |
|---|---|---|
useI18n() | 현재 컴포넌트 인스턴스 | 불가 → i18n.global.t() 사용 |
useStore() (Pinia) | 활성 Pinia(app.use(pinia) 이후) | 가능, 단 함수 안에서 호출 |
ref/computed만 쓰는 composable | 없음 | 가능 |
생명주기 훅·inject를 쓰는 composable | 현재 컴포넌트 인스턴스 | 불가 |