플러그인 API
본 문서에서는 플러그인의 API를 정의하고, 사용하는 방법을 설명합니다. 플러그인의 API는 플러그인이 외부에 제공할 기능을 공개하는 영역입니다.
API 정의하기 (plugin.ts)
플러그인 API는 definePlugin<Api, State>(...)의 Api 타입과 api 객체로 정의합니다.
import { AppContext, definePlugin } from "@muvel/core"
import { pluginId } from "./constants"
export interface ExampleApi {
open: (ctx: AppContext, id: string) => void
close: (ctx: AppContext) => void
}
type ExampleState = {
openedId: string | null
}
const plugin = definePlugin<ExampleApi, ExampleState>(pluginId, {
name: "example",
version: __VERSION__,
dependencies: [],
api: {
open: (ctx, id) => {
const state = ctx.get(plugin.id)
state.openedId = id
},
close: (ctx) => {
const state = ctx.get(plugin.id)
state.openedId = null
},
},
setup: async () => ({ openedId: null }),
dispose: async () => {},
})- API 메서드 시그니처는
interface로 먼저 선언해두면 재사용/추론이 쉽습니다. - 상태를 함께 쓸 경우
ctx.get(plugin.id)로setup반환 state에 접근합니다. - API가 다른 플러그인을 호출해야 하면, 해당 플러그인 ID를
dependencies에 선언하세요.
다른 플러그인 API 호출하기
다른 플러그인 API는 보통 아래 두 방식으로 호출합니다.
1) setup에서 바인딩해서 사용 (mgr.use)
setup: async (mgr, ctx) => {
const modalApi = mgr.use(ctx, modalPluginId)
const disposer = modalApi.register(novelSearchModal)
return { disposer }
}novel-search 플러그인처럼 초기화 시점 등록 작업에 주로 사용합니다.
2) API 함수 내부에서 즉시 사용 (ctx.api)
api: {
open: (ctx, novel, options = {}) => {
const modalApi = ctx.api(modalPluginId)
modalApi.open(novelSearchModal.id, { novel, ...options })
},
}런타임 호출 시점에 다른 플러그인 기능을 위임할 때 유용합니다.
React에서 API 사용하기
클라이언트 React 컴포넌트에서는 usePluginApi(pluginId)를 사용합니다.
import { episodePluginId } from "@muvel-plugins/episode"
import { usePluginApi } from "@muvel/core"
const MyComponent = () => {
const episodeApi = usePluginApi(episodePluginId)
const episodeAtom = episodeApi.getEpisodeAtom()
return null
}EpisodeTitleInput.tsx도 동일하게usePluginApi(episodePluginId)패턴을 사용합니다.- 플러그인별 API가 다르므로, 반드시 해당 플러그인
plugin.ts의api정의를 먼저 확인하세요. - 환경에 따라 플러그인이 없을 수 있으면
useOptionalPluginApi사용을 고려하세요.
API 타이핑 (Api / State / BoundApi)
definePlugin<Api, State, BoundApi> 형태로 제네릭을 지정할 수 있습니다.
Api: 플러그인 원본 API 타입 (ctx를 첫 인자로 받는 형태)State:setup이 반환하는 상태 타입BoundApi: 외부에서 사용할 API 타입.Api에서 첫 번째 인자인ctx를 제거한 버전
BoundApi는 언제 쓰나?
- 기본적으로는 직접 넣지 않아도 충분히 추론됩니다.
- 다만 제네릭 메서드가 많거나, 함수 오버로드/복잡한 타입 조합이 있는 경우 추론이 불안정할 수 있습니다.
- 이때
BoundApi를 명시적으로 선언하면mgr.use(...),ctx.api(...),usePluginApi(...)에서 타입이 더 정확하게 잡힙니다.
예시: Api와 BoundApi를 분리해 명시
import { AppContext, definePlugin } from "@muvel/core"
interface ModalApi {
open: <T>(ctx: AppContext, id: ModalId<T>, props: T) => void
close: (ctx: AppContext, id: ModalId) => void
}
interface BoundModalApi {
open: <T>(id: ModalId<T>, props: T) => void
close: (id: ModalId) => void
}
const plugin = definePlugin<
ModalApi,
{ store: StoreApi<ModalStore>; disposer: () => void },
BoundModalApi
>(pluginId, {
// ...
})핵심은 아래처럼 대응됩니다.
Api.open(ctx, id, props)->BoundApi.open(id, props)Api.close(ctx, id)->BoundApi.close(id)
타이핑 실무 팁
- API 시그니처는
interface로 먼저 분리하고definePlugin에 제네릭으로 연결하세요. BoundApi를 직접 선언할 때는Api와 파라미터 순서를 정확히 맞추고,ctx만 제거하세요.- 제네릭 API는
open: <T>(...)처럼 선언해 호출부 타입 안정성을 유지하세요. - 가능한 한
as단언보다 타입 파라미터/인터페이스 설계로 해결하는 것을 권장합니다.
체크리스트
api에 공개할 기능이 정말 외부 사용 대상인지 확인- 다른 플러그인 API 호출 시
dependencies누락 여부 확인 setup등록 로직과dispose정리 로직 짝 맞추기- React 사용부에서
usePluginApi/useOptionalPluginApi선택이 맞는지 확인
Last updated on