Как получить доступ к корневому контексту из функции композиции в Vue Composition API / Vue 3.0 + TypeScript?

Я хочу создать многоразовую функцию-оболочку, написанную на TypeScript, для запуска всплывающего уведомления с помощью функции композиции, как определено в текущей спецификации Vue 3.0: Composition API RFC.

В этом примере используется компонент тоста BootstrapVue v2.0. В Vue 2 он будет вызываться черезthis.$bvToastВнедрение экземпляра компонента Vue в корневой контекст:

this.$bvToast.toast('Error happened', {
  title: 'Oh no',
  variant: 'danger'
});

Эта служебная функция композиции будет выглядеть примерно так:

// File: @/util/notify.ts
export function useNotify() {
  const notifyError = (title: string, msg: string) => {
    // How to access context.root as in a function component, without passing it to this function?
    context.root.$bvToast.toast(msg, {
      title,
      variant: 'danger'
    });
  };

  return { notifyError};
}

export default useNotify;

И будет использоваться примерно так:

// Use in your functional component:
import { createComponent } from '@vue/composition-api';

import { useNotify} from '@/util/notify';

export default createComponent({
  name: 'MyFailingComponent',
  setup() {
    const { notifyError } = useNotify();

    notifyError('Request error', 'There was an error processing your request, please try again later.');

    return {};
  }
});

5 ответов

Решение

Что ж, вскоре я нашел подходящий пример на том же сайте RFC. Но решил поделиться здесь своими примерами.

Я полагаю, что в настоящий момент на сайте RFC нет примеров на TypeScript. Поскольку этот новый способ написания компонентов и функций композиции Vue 3.0 (как замена Mixins) требует некоторого привыкания.

Ответ: Вы можете передать объект контекста непосредственно в функцию композиции при деструктурировании необходимых частей в коде вашего компонента.

// File: @/util/notify.ts
// import { SetupContext } from '@vue/composition-api';

export function useNotify({ root }) {
  const notifyError = (title: string, msg: string) => {
    root.$bvToast.toast(msg, {
      title,
      variant: 'danger'
    });
  };

  return { notifyError };
}

export default useNotify;
// Use in your functional component:
import { createComponent, SetupContext } from '@vue/composition-api';

import { useNotify} from '@/util/notify';

export default createComponent({
  name: 'MyFailingComponent',
  setup(props: any, context: SetupContext) {
    const { notifyError } = useNotify(context);

    notifyError('Request error', 'There was an error processing your request, please try again later.');

    return {};
  }
});

То же самое с типами TypeScript со сложной деструктуризацией объекта при передаче нескольких аргументов функции в виде объекта:

// File: @/util/notify.ts
import { SetupContext } from '@vue/composition-api';

export function useNotify({ context, defaultTitle = 'Hey!' }: { context: SetupContext, defaultTitle?: string }) {
  const notifyError = (msg: string, title?: string) => {
    context.root.$bvToast.toast(msg, {
      title: title || defaultTitle,
      variant: 'danger',
    });
  };

  return {
    notifyError,
  };
}

export default useNotify;

// Usage like:
const { notifyError } = useNotify({ context });
// Or
const { notifyError } = useNotify({ context, defaultTitle: 'Hey there' });

Аккуратный синтаксис, отличное сообщество Vue!

Существует также:

import { getCurrentInstance } from 'vue'  // or from '@vue/composition-api'

Это получит вызывающий компонент root контекст из этого метода.

const root = getCurrentInstance();  // same as ctx.root in component

Вы можете в конечном итоге передать контекст каждому составному объекту, потому что их зависимости могут требовать сам контекст.

Существует альтернативное решение для предоставления корневого экземпляра без необходимости передавать его каждому компоненту, который у вас есть. Это немного упрощает их использование в компонентах:

Вы можете создать общий useRoot composable и реализовать его с помощью функции Vue provide/inject:

// File: @/components/Root.js
// This is the app root
import useRoot from '@/composables/useRoot'

export default {
  setup(props, context) {
    const { provideRoot } = useRoot()
    provideRoot(context.root)
  }
}
// File: @/composables/useFancyStuff
// This is your composable (no arguments needed!)
import useRoot from '@/composables/useRoot'

export default function useNavigation() {
  const { injectRoot } = useRoot()
  const { $router } = injectRoot() // if you want to use the router

  $router.push('/')
}
// File: @/composables/useRoot
// The implementation
import { provide, inject } from '@vue/composition-api'

const ProviderSymbol = Symbol()

export default function useRoot() {
  const provideRoot = root => provide(ProviderSymbol, root)
  const injectRoot = () => inject(ProviderSymbol)

  return {
    provideRoot,
    injectRoot
  }
}

В моем случае мне нужно было получить доступ кsetupContextчтобы добраться до того же объекта, какsetup(props, { root }.

      export default function useContentManagerPostActions() {
  const { emit, setupContext: { root } } = getCurrentInstance()

  ...

  const addPost = async () => {
    try {
      ...
    } catch (error) {
      ...
      root.$bvToast.toast('A new post could not be created.', {
        title: 'Error',
        variant: 'danger',
        solid: false
      })
    }
  }

РЕДАКТИРОВАТЬ: Это доступно только в NUXT !!!

Наclient-sideконтекст доступен по адресуwindow.$vm.$root.context

Этот подход не будет работать на SSR!

Другие вопросы по тегам