Skip to content

Provide/Inject の実装

Provide/Inject を実装しよう

Provide と Inject の実装です.こちらも実装は至ってシンプルです.
基本的なコンセプトとしては, ComponentInternalInstance に provides (provide されたデータを格納する場所)と,親コンポーネントのインスタンスを保持しておいて,データを受け継ぐだけです.

一点,注意する点としては,provide のエントリポイントは 2 種類あるということで,一つはコンポーネントの setup 時という想像のつきやすいものですが,
もう一つは App の provide を呼ぶケースです.

ts
const app = createApp({
  setup() {
    //.
    //.
    //.
    provide('key', someValue) // これはコンポーネントから provide するケース
    //.
    //.
  },
})

app.provide('key2', someValue2) // App に provide

さて,app で provide したものはどこに保持しておきましょう ? app はコンポーネントではないですからね.困りました.

答えを言ってしまうと,app のインスタンスに AppContext というオブジェクトを持つことにして,provides というオブジェクトをこの中で保持するようにします.

この,AppContext には将来的にグローバルコンポーネントやカスタムディレクティブの設定を持たせます.

さて,ここまでで説明するべきことは揃ったので,概ね以下のようなコードが動くように実装してみましょう!

※ 想定しているシグネチャ

ts
export interface InjectionKey<_T> extends Symbol {}

export function provide<T, K = InjectionKey<T> | string | number>(
  key: K,
  value: K extends InjectionKey<infer V> ? V : T,
)

export function inject<T>(key: InjectionKey<T> | string): T | undefined
export function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
ts
const Child = {
  setup() {
    const rootState = inject<{ count: number }>('RootState')
    const logger = inject(LoggerKey)

    const action = () => {
      rootState && rootState.count++
      logger?.('Hello from Child.')
    }

    return () => h('button', { onClick: action }, ['action'])
  },
}

const app = createApp({
  setup() {
    const state = reactive({ count: 1 })
    provide('RootState', state)

    return () =>
      h('div', {}, [h('p', {}, [`${state.count}`]), h(Child, {}, [])])
  },
})

type Logger = (...args: any) => void
const LoggerKey = Symbol() as InjectionKey<Logger>

app.provide(LoggerKey, window.console.log)

ここまでのソースコード:
chibivue (GitHub)

Released under the MIT License.