Vue 组件需要多次导入 Pinia store props 吗?

Do I need to import Pinia store props multiple times in a Vue component?

我正在处理我的第一个 Vue 项目。我习惯了 React 和 vanilla js,但只是想了解一下 Vue 中的一些概念。

特别是从 Pinia 商店导入状态和动作道具,并且似乎必须在单个 Vue 组件中多次导入这些道具(我不需要在 React 中做的事情)。

在这个例子中,我导入了一个简单的计数值和一个增量函数,并尝试在几个不同的地方使用它们:

<script setup>
// I import everything initially in setup, which works fine,
// and these props (currentCount and incrementCount)
// can be used in my template:
import { storeToRefs } from 'pinia';
import { useStore } from '@/stores/store';
const { currentCount } = storeToRefs(useStore());
const { incrementCount } = useStore();
</script>

<template>
  <main>
    Current count: {{ currentCount }}
    <button @click="incrementCount">Increment</button>
  </main>
</template>

<script>
// I can't use store values from setup here.
// This doesn't work:
// console.log(currentCount);

// I also can't import store values here.
// I get the following error:
// "getActivePinia was called with no active Pinia"
// const { currentCount } = storeToRefs(useStore());

export default {
  mounted() {
    // I have to import store values here for them to work:
    const { currentCount } = storeToRefs(useStore());
    console.log(currentCount);
  },
  watch: {
    // weirdly, this reference to watching "currentCount" works:
    currentCount() {
      // I also have to import store values here for them to work:
      const { currentCount } = storeToRefs(useStore());
      console.log(currentCount);
    },
  },
};
</script>

如您所见,如果我想在我的模板、挂载和观察程序中使用存储值(我将使用 React 的 useEffect 挂钩),我总共必须导入存储道具 3 次.

这是正确/正常的吗?有没有更简单的方法来实现我正在做的事情,我只导入一次道具?我想确定我没有错过任何事情,也没有以不寻常的方式做某事。

感谢您的帮助和建议!

Pinia 的设计考虑到了构图 API。
所以它的预期用途是在 setup() 函数中,你只需要导入一次。

要在 setup() 函数之外使用它,您有两个主要途径:

  • 在组件内部,您只需从 setup() return 它就可以在任何 hook/method/getter 中使用它。作为 this.store 或传播:
import { useStore } from '@/store'
import { toRefs } from 'vue'
            // or from '@vue/composition-api' in Vue2

export default {
  setup: () => ({ ...toRefs(useStore()) })
}
/* this makes every state prop, getter or action directly available 
   on current component instance. In your case, `this.currentCount`.
   Obviously, you can also make the entire store available as `this.someStore`:

  setup: () => ({ someStore: useSomeStore() })
  // now you can use `this.someStore` anywhere 
 */
  • 一种更通用的方法是从 main.(js|ts) 导出 pinia 实例(return 由 createPinia() 编辑),将其导入到您需要存储的位置,然后调用 useStore() 将 pinia 实例作为参数传递。
    这可以在任何地方完成,甚至在组件之外。
    通用示例:
import { pinia } from 'main.js'
import { useSomeStore } from '@/store'

const someStore = useSomeStore(pinia);

我可能还应该提到 pinia 提供的 mapState 助手。它允许您 select 仅暴露给当前实例的少数键。示例:

import { mapState } from 'pinia'
// ...
   computed: {
    ...mapState(useSomeStore, [ 'currentCount'])
  }
// Now `this.currentCount` is available

注意:mapState 的名字很奇怪,因为它允许您访问的不仅仅是 state 道具(还有 gettersactions)。它被命名为 mapState 以匹配来自 vuex.

的类似助手

一种更通用的方法是将您的商店添加为全局商店,使用 Vue2 中的插件注册 API:

import { useSomeStore } from '@/store';
import { createPinia } from 'pinia';

const pinia = createPinia();

const someStorePlugin = {
  install(Vue, options) {
    Vue.prototype.someStore = useSomeStore(options.pinia)
  }
};

Vue.use(someStorePlugin, { pinia });

new Vue({ pinia });

在此之后,您的 Vue 实例的每个组件都将 this.someStore 可用,而无需导入它。

注意:我还没有测试过在全局变量中添加商店(我绝对反对它——你应该避免使用全局变量),但我希望它能起作用。

如果您想将 pinia 商店与选项 API 结合使用,一种方法是使用选项中的 setup() 函数来调用 useStore :

<script>
import { useStore } from '@/stores/store';

export default {
  setup() { 
    const store = useStore();
    return {store}
  },
  watch: {
    store.currentBrightness(newVal, oldVal){
      // your code
    }
  },
  methods: {
   // inside methods use this.store
  },
  mounted() {
    console.log(this.store.currentCount);
  }
}
</script>

有些人可能认为这是组合和选项的不合需要的组合 API,但在我看来,这是 pinia 商店的一个很好的解决方案。