javascript 使用组件外的Pinia不会引起活动Pinia错误

qacovj5a  于 2023-03-11  发布在  Java
关注(0)|答案(2)|浏览(293)

我正在使用ViteSse模板构建Vue.js3项目,我想在安装组件之外使用我的一个名为notificationStore的Pinia存储,但在运行dev命令时收到getActivePinia was called with no active Pinia. Did you forget to install pinia错误。它的行为非常奇怪,因为它如果我在加载开发服务器后添加useNotificationStore,则不会发生错误,它运行良好,但当useNotificationStore存在于我的ts文件中并运行开发命令时,它会引发错误错误。

下面是我设置:
main.ts

import { ViteSSG } from 'vite-ssg'
import { setupLayouts } from 'virtual:generated-layouts'
import { useAuth, useOidcStore } from 'vue3-oidc'
import type { UserModule } from './types'
import App from './app.vue'
import generatedRoutes from '~pages'
import './assets/styles/style.scss'
import '@/config/oidc'
const routes = setupLayouts(generatedRoutes)
const { autoAuthenticate } = useAuth()
const { state } = useOidcStore()
const isMocking = import.meta.env.VITE_API_MOCKING_ENABLED
if (isMocking === 'true') {
  const browser = await import('~/mocks/browser') as any
  browser.worker.start({ onUnhandledRequest: 'bypass' })
}

export const createApp = ViteSSG(
  App,
  { routes, base: import.meta.env.BASE_URL },
  async (ctx) => {
    Object.values(import.meta.glob<{ install: UserModule }>('./modules/*.ts', { eager: true }))
      .forEach(i => {
        i.install?.(ctx)
      })

    const DEFAULT_TITLE = 'Dashboard'
    ctx.router.beforeEach((to) => {
      if (!state.value.user) {
        autoAuthenticate()
      }
      let title = DEFAULT_TITLE
      if (to.meta.title)
        title = `${to.meta.title} - ${title}`

      document.title = title
    })
  },
)

notification.store.ts

import { acceptHMRUpdate, defineStore } from 'pinia'

interface NotificationState {
  messages: Array<Notification>
}
export interface Notification {
  type?: 'error' | 'info' | 'success' | 'warning'
  timeout?: number
  permanent?: boolean
  message: string
  hideAfterRouting?: boolean
  validationErrors?: Array<string>
  shown?: boolean
}

export const useNotificationsStore = defineStore('notification', {
  state: (): NotificationState => ({
    messages: [],
  }),
  actions: {
    addNotification(message: Notification) {
      if (isDuplicatedMessage(this.messages, message))
        return

      message.timeout = message.timeout || 3000
      message.shown = true
      this.messages.push(message)
    },
    remove(index: number) {
      this.messages.splice(index, 1)
    },
    clearAfterRoute() {
      const visibleNotification = this.messages.filter(
        item => item.hideAfterRouting === false,
      )
      this.messages = visibleNotification
    },
  },
})

function isDuplicatedMessage(messages: Notification[], message: Notification): boolean {
  if (!messages.length)
    return false
  return (messages[messages.length - 1].message === message.message)
}

if (import.meta.hot)
  import.meta.hot.accept(acceptHMRUpdate(useNotificationsStore, import.meta.hot))

http-client.ts

import type { AxiosInstance, AxiosRequestConfig } from 'axios'
import axios from 'axios'
import tokenService from './token.service'
import { useNotificationsStore } from '~/store/notification.store'
const notification = useNotificationsStore()
const httpClient: AxiosInstance
  = axios.create({
    baseURL: `${import.meta.env.VITE_APP_API_URL}/`,
    headers: {
      'Content-Type': 'application/json',
    },
  })

httpClient.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    const token = tokenService.getLocalAccessToken()
    if (token && config.headers)
      config.headers.Authorization = `Bearer ${token}`

    return config
  },
  (error) => {
    return Promise.reject(error)
  },
)
httpClient.interceptors.response.use(
  (response) => {
    if (response.config.method !== 'get') {
      const successMsg = 'operation successfully completed'
      notification.addNotification({ type: 'success', message: successMsg })
    }
    return response
  },
  (error) => {
    if (error.message === 'Network Error') {
      notification.addNotification({ type: 'error', message: 'Network Error' })
      return Promise.reject(error)
    }
    let errorMessage = ''
    const validationErrors = getValidationErrors(error)
    errorMessage = getErrorMessage(error)
    notification.addNotification({ type: 'error', message: errorMessage, validationErrors, hideAfterRouting: false })
  }
)

Vite.config.ts

// Plugins

// Utilities
import path from 'path'
import { URL, fileURLToPath } from 'node:url'
import { defineConfig } from 'vite'

import vuetify from 'vite-plugin-vuetify'
import Vue from '@vitejs/plugin-vue'
import Pages from 'vite-plugin-pages'
import Layouts from 'vite-plugin-vue-layouts'
import Components from 'unplugin-vue-components/vite'
import AutoImport from 'unplugin-auto-import/vite'
import { VitePWA } from 'vite-plugin-pwa'
import VueI18n from '@intlify/vite-plugin-vue-i18n'
import Inspect from 'vite-plugin-inspect'

// https://vitejs.dev/config/
export default defineConfig({
  server: {
    port: 9080,
    host: true,
    hmr: {
      host: 'localhost',
    },
  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
      '~': fileURLToPath(new URL('./src', import.meta.url)),
    },
    extensions: [
      '.js',
      '.json',
      '.jsx',
      '.mjs',
      '.ts',
      '.tsx',
      '.vue',
    ],
  },
  plugins: [
    Vue({
      include: [/\.vue$/],
      reactivityTransform: true,
    }),
    vuetify({
      autoImport: true,
    }),
    // https://github.com/hannoeru/vite-plugin-pages
    Pages({
      extensions: ['vue'],
      dirs: [
        { dir: 'src/router/views', baseRoute: '' },
      ],
      extendRoute: (route) => {
        if (route.path === '/')
          return { ...route, redirect: '/Dashboard' }

        return route
      },

    }),

    // https://github.com/JohnCampionJr/vite-plugin-vue-layouts
    Layouts({
      layoutsDirs: ['src/router/layouts'],
    }),

    // https://github.com/antfu/unplugin-auto-import
    AutoImport({
      imports: [
        'vue',
        'vue-router',
        'vue-i18n',
        'vue/macros',
        '@vueuse/head',
        '@vueuse/core',
      ],
      dts: 'src/auto-imports.d.ts',
      dirs: [
        'src/composable',
        'src/store',
      ],
      vueTemplate: true,
    }),

    // https://github.com/antfu/unplugin-vue-components
    Components({
      extensions: ['vue'],
      include: [/\.vue$/, /\.vue\?vue/],
      dts: 'src/components.d.ts',
      deep: true,
    }),  

    // https://github.com/intlify/bundle-tools/tree/main/packages/vite-plugin-vue-i18n
    VueI18n({
      runtimeOnly: true,
      compositionOnly: true,
      include: [path.resolve(__dirname, 'locales/**')],
    }),

    // https://github.com/antfu/vite-plugin-inspect
    // Visit http://localhost:3333/__inspect/ to see the inspector
    Inspect(),
  ],

  // https://github.com/vitest-dev/vitest
  // test: {
  //   include: ['test/**/*.test.ts'],
  //   environment: 'jsdom',
  //   deps: {
  //     inline: ['@vue', '@vueuse', 'vue-demi'],
  //   },
  // },

  // https://github.com/antfu/vite-ssg
  ssgOptions: {
    script: 'sync',
    formatting: 'minify',
    // onFinished() { generateSitemap() },
  },

  ssr: {
    // TODO: workaround until they support native ESM
    noExternal: ['workbox-window', /vue-i18n/],
  },

  define: { 'process.env': {} },
})

我已经跟踪了一些日志记录的问题,发现它发生在createApp开始执行之前,初始化pinia,并且当时没有活动的pinia。我预计main.ts中的createApp已经在所有之前执行
先谢了

j13ufse2

j13ufse21#

这发生因为你还没有注册pinia与这应用程序

export const createApp = ViteSSG(
  App,
  { routes, base: import.meta.env.BASE_URL },
  async (ctx) => {
    Object.values(import.meta.glob<{ install: UserModule }>('./modules/*.ts', { eager: true }))
      .forEach(i => {
        i.install?.(ctx)
      })

// install pinia 
    const pinia = createPinia()
    ctx.app.use(pinia)

    if (import.meta.env.SSR)
      ctx.initialState.pinia = pinia.state.value
    else
      pinia.state.value = ctx.initialState.pinia || {}
// ============

    const DEFAULT_TITLE = 'Dashboard'
    ctx.router.beforeEach((to) => {
// ====== use store ====
      const { state } = useOidcStore()

      if (!store.ready)
        // perform the (user-implemented) store action to fill the store's state
        store.initialize()
// ===========
      if (!state.value.user) {
        autoAuthenticate()
      }
      let title = DEFAULT_TITLE
      if (to.meta.title)
        title = `${to.meta.title} - ${title}`

      document.title = title
    })
  },
)

请删除顶层的useOidcStore()

lmyy7pcs

lmyy7pcs2#

我通过仔细阅读文档设法解决了这个问题:
确保这一点始终适用的最简单方法是延迟调用useStore(),方法是将它们放在安装pinia后始终运行的函数中。
我只是通过从文件顶部删除useNotificationStore()来更新http-client,在需要notificaitonStore示例的地方使用useNotificationStore().addNotification(...)

import type { AxiosInstance, AxiosRequestConfig } from 'axios'
import axios from 'axios'
import tokenService from './token.service'

const httpClient: AxiosInstance
  = axios.create({
    baseURL: `${import.meta.env.VITE_APP_API_URL}/`,
    headers: {
      'Content-Type': 'application/json',
    },
  })

httpClient.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    const token = tokenService.getLocalAccessToken()
    if (token && config.headers)
      config.headers.Authorization = `Bearer ${token}`

    return config
  },
  (error) => {
    return Promise.reject(error)
  },
)
httpClient.interceptors.response.use(
  (response) => {
    if (response.config.method !== 'get') {
      const successMsg = 'operation successfully completed'
       useNotificationsStore().addNotification({ type: 'success', message: successMsg })
    }
    return response
  },
  (error) => {
    if (error.message === 'Network Error') {
       useNotificationsStore().addNotification({ type: 'error', message: 'Network Error' })
      return Promise.reject(error)
    }
    let errorMessage = ''
    const validationErrors = getValidationErrors(error)
    errorMessage = getErrorMessage(error)
    useNotificationsStore().addNotification({ type: 'error', message: errorMessage, validationErrors, hideAfterRouting: false })
  }
)

相关问题