IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 【Pinia】第二章 核心概念 -> 正文阅读

[JavaScript知识库]【Pinia】第二章 核心概念

1、定义Store

在进入核心概念之前,我们需要知道Store是使用defineStore()定义的,并且它需要一个唯一的名称,作为第一个参数传递:

import { defineStore } from 'pinia'

// useStore could be anything like useUser, useCart
// the first argument is a unique id of the store across your application
export const useStore = defineStore('main', {
  // other options...
})

这个名称(也称为id)是必需的,Pania使用它来将store连接到devtools。将返回的函数命名为use...是可组合项之间的约定,以使其用法符合使用习惯。

1.1 使用Store

我们定义了一个Store,因为只有在setup()中调用了useStore()store才会被创建:

import { useStore } from '@/stores/counter'

export default {
  setup() {
    const store = useStore()

    return {
      // you can return the whole store instance to use it in the template
      store,
    }
  },
}

您可以根据需要定义任意数量的store,并且应该在不同的文件中定义每个store以充分利用 Pinia(例如自动允许您的bundle进行代码拆分和TypeScript推理)。

如果您还没有使用setup组件,您仍然可以将Pinia与辅助函数一起使用

一旦Store被实例化,您就可以直接在store上访问在stategettersactions中定义的任何属性。我们将在下一章中看到这些细节,自动补全功能也将帮助你。

请注意,Store是一个用reactive包装的对象,这意味着不需要在getter后面写 .value,但是,就像setup中的props一样,我们不能对它进行解构:

export default defineComponent({
  setup() {
    const store = useStore()
    // ? This won't work because it breaks reactivity
    // it's the same as destructuring from `props`
    const { name, doubleCount } = store

    name // "eduardo"
    doubleCount // 2

    return {
      // will always be "eduardo"
      name,
      // will always be 2
      doubleCount,
      // this one will be reactive
      doubleValue: computed(() => store.doubleCount),
      }
  },
})

为了从store中提取属性,同时保持其响应性,您需要使用storeToRefs()。它将为任何响应性属性创建引用。当您仅使用store中的state,且不调用任何操作时,这很有用:

import { storeToRefs } from 'pinia'

export default defineComponent({
  setup() {
    const store = useStore()
    // `name` and `doubleCount` are reactive refs
    // This will also create refs for properties added by plugins
    // but skip any action or non reactive (non ref/reactive) property
    const { name, doubleCount } = storeToRefs(store)

    return {
      name,
      doubleCount
    }
  },
})

2、State

大多数时候,stateStore的中心部分。人们通常从定义应用程序的state开始。在Pinia 中,state被定义为一个返回初始state的函数。这保证了Pinia在服务器端和客户端都能使用。

import { defineStore } from 'pinia'

const useStore = defineStore('storeId', {
  // arrow function recommended for full type inference
  state: () => {
    return {
      // all these properties will have their type inferred automatically
      counter: 0,
      name: 'Eduardo',
      isAdmin: true,
    }
  },
})

TIP
如果您使用Vue 2,您在state中创建的数据应遵循与Vue实例中data相同的规则,即 state对象必须是普通的,并且在向其添加新属性时需要调用Vue.set()。另请参阅:Vue#data

2.1 访问State

默认情况下,你可以通过Store实例直接读写state:

const store = useStore()

store.counter++

2.2 重置State

您可以通过调用store上的$reset()方法将state重置为初始值:

const store = useStore()
store.$reset()

2.2.1 使用Options API

对于以下示例,您可以假设创建了以下store:

// Example File Path:
// ./src/stores/counterStore.js

import { defineStore } from 'pinia'

const useCounterStore = defineStore('counterStore', {
  state: () => ({
    counter: 0
  })
})

2.2.2 使用setup()

虽然Composition API并不适合所有人,但是setup()钩子可以让Pinia更容易在Options API中使用。不需要额外的辅助函数!

import { useCounterStore } from '../stores/counterStore'

export default {
  setup() {
    const counterStore = useCounterStore()

    return { counterStore }
  },
  computed: {
    tripleCounter() {
      return counterStore.counter * 3
    },
  },
}

2.2.3 不使用setup()

如果您不使用Composition API,而您使用的是computed, methods,…,则你可以使用mapState()辅助函数将状态属性映射为只读计算属性:

import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  computed: {
    // gives access to this.counter inside the component
    // same as reading from store.counter
    ...mapState(useCounterStore, ['counter']),
    // same as above but registers it as this.myOwnName
    ...mapState(useCounterStore, {
      myOwnName: 'counter',
      // you can also write a function that gets access to the store
      double: store => store.counter * 2,
      // it can have access to `this` but it won't be typed correctly...
      magicValue(store) {
        return store.someGetter + this.counter + this.double
      },
    }),
  },
}
可修改的 State

如果您希望能够写入这些状态属性(例如,如果您有一个表单),您可以使用mapWritableState()代替。请注意,您不能像mapState()那样传递函数:

import { mapWritableState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  computed: {
    // gives access to this.counter inside the component and allows setting it
    // this.counter++
    // same as reading from store.counter
    ...mapWritableState(useCounterStore, ['counter']),
    // same as above but registers it as this.myOwnName
    ...mapWritableState(useCounterStore, {
      myOwnName: 'counter',
    }),
  },
}

TIP
您不需要mapWritableState()来处理像数组这样的集合,除非你用cartItems = []来替换整个数组,mapState()仍然允许你在你的集合上调用方法。

2.3 改变 State

除了直接使用store.counter++ 改变store之外,你也可以调用$patch方法。它允许您使用部分state对象同时应用到多个改变:

store.$patch({
  counter: store.counter + 1,
  name: 'Abalam',
})

然而,使用这种语法应用某些改变确实很难或代价高昂:任何集合修改(例如,从数组中添加、删除、修改元素)都需要您创建一个新集合。正因为如此,$patch方法也接受一个函数来对这种难以应用于patch对象的改变进行分组:

cartStore.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

这里的主要区别是$patch()允许您在devtools中将多个改变分组到一个条目中。注意,对**state$patch()**的直接更改将呈现在devtools中,并且需要花费些时间(在Vue 3中还没出现)。

2.4 替换 State

您可以通过将store$state属性设置一个新对象来替换整个store的状态:

store.$state = { counter: 666, name: 'Paimon' }

您还可以通过更改 pinia实例的state来替换应用程序的整个状态。这在SSR激活中使用。

pinia.state.value = {}

2.5 订阅 State

您可以通过store$subscribe()方法查看状态及其变化,这与Vuexsubscribe 方法类似。与常规的watch()相比,使用$subscribe()的优势在于,订阅只会在patches之后触发一次(例如,当使用上面的函数版本时)。

cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  // same as cartStore.$id
  mutation.storeId // 'cart'
  // only available with mutation.type === 'patch object'
  mutation.payload // patch object passed to cartStore.$patch()

  // persist the whole state to the local storage whenever it changes
  localStorage.setItem('cart', JSON.stringify(state))
})

默认情况下,状态订阅被绑定到添加它们的组件上(如果store在组件的setup()中)。这意味着,当组件被卸载时,它们将被自动删除。如果你想在组件卸载后保留它们,传递{ detached: true } 作为第二个参数来从当前组件中分离状态订阅:

export default {
  setup() {
    const someStore = useSomeStore()

    // this subscription will be kept after the component is unmounted
    someStore.$subscribe(callback, { detached: true })

    // ...
  },
}

TIP
您可以查看`Pinia```实例上的整个状态:

watch(
  pinia.state,
  (state) => {
    // persist the whole state to the local storage whenever it changes
    localStorage.setItem('piniaState', JSON.stringify(state))
  },
  { deep: true }
)

3、Getters

Gettersstore状态的计算值完全相同。它们可以用defineStore()中的getters属性来定义。它们接收state作为第一个参数,鼓励使用箭头函数:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    doubleCount: (state) => state.counter * 2,
  },
})

大多数时候,getters只依赖于state,但是,它们也可能需要使用其他的getters。因此,当定义一个常规函数时,我们可以通过this访问整个store实例,但需要定义返回类型的类型(在Typescript中)。这是由于TypeScript中的一个已知限制,不会影响使用箭头函数定义的 getters,也不会影响不使用thisgetters

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    // automatically infers the return type as a number
    doubleCount(state) {
      return state.counter * 2
    },
    // the return type **must** be explicitly set
    doublePlusOne(): number {
      // autocompletion and typings for the whole store ?
      return this.counter * 2 + 1
    },
  },
})

然后你可以直接访问store实例getter

<template>
  <p>Double count is {{ store.doubleCount }}</p>
</template>

<script>
export default {
  setup() {
    const store = useStore()

    return { store }
  },
}
</script>

3.1 访问其他 getters

与计算属性一样,您可以组合多个getters。通过this访问任何其他的getters。即使您不使用TypeScript,您也可以使用JSDoc提示IDE输入的类型:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    // type is automatically inferred because we are not using `this`
    doubleCount: (state) => state.counter * 2,
    // here we need to add the type ourselves (using JSDoc in JS). We can also
    // use this to document the getter
    /**
     * Returns the counter value times two plus one.
     *
     * @returns {number}
     */
    doubleCountPlusOne() {
      // autocompletion ?
      return this.doubleCount + 1
    },
  },
})

3.2 将参数传递给 getters

getters只是后台的计算属性,因此不可能向它们传递任何参数。但是,您可以从getter返回一个函数来接受任何参数:

export const useStore = defineStore('main', {
  getters: {
    getUserById: (state) => {
      return (userId) => state.users.find((user) => user.id === userId)
    },
  },
})

并在组件中使用:

<script>
export default {
  setup() {
    const store = useStore()

    return { getUserById: store.getUserById }
  },
}
</script>

<template>
  <p>User 2: {{ getUserById(2) }}</p>
</template>

请注意,执行此操作时,getters不再被缓存,它们只是您调用的普通函数。但是,您可以在 getter本身中缓存一些结果,这并不常见,但它证明性能更高:

export const useStore = defineStore('main', {
  getters: {
    getActiveUserById(state) {
      const activeUsers = state.users.filter((user) => user.active)
      return (userId) => activeUsers.find((user) => user.id === userId)
    },
  },
})

3.3 访问其他 Stores 的 getters

要使用其他storegetters,您可以直接在getter内部使用它:

import { useOtherStore } from './other-store'

export const useStore = defineStore('main', {
  state: () => ({
    // ...
  }),
  getters: {
    otherGetter(state) {
      const otherStore = useOtherStore()
      return state.localData + otherStore.data
    },
  },
})

3.4 setup() 中的用法

您可以直接访问任何getter作为store的属性(完全和state属性一样):

export default {
  setup() {
    const store = useStore()

    store.counter = 3
    store.doubleCount // 6
  },
}

3.5 Options API 中的用法

对于以下示例,您可以假设创建了以下store

// Example File Path:
// ./src/stores/counterStore.js

import { defineStore } from 'pinia'

const useCounterStore = defineStore('counterStore', {
  state: () => ({
    counter: 0
  }),
  getters: {
    doubleCounter() {
      return this.counter * 2
    }
  }
})

3.5.1 使用 setup()

虽然Composition API并不适合所有人,但是setup()钩子可以让Pinia更容易在Options API中使用。不需要额外的辅助函数!

import { useCounterStore } from '../stores/counterStore'

export default {
  setup() {
    const counterStore = useCounterStore()

    return { counterStore }
  },
  computed: {
    quadrupleCounter() {
      return counterStore.doubleCounter * 2
    },
  },
}

3.5.2 不使用 setup()

您可以像前一节的state一样使用mapState()函数来映射到getters

import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  computed: {
    // gives access to this.doubleCounter inside the component
    // same as reading from store.doubleCounter
    ...mapState(useCounterStore, ['doubleCount']),
    // same as above but registers it as this.myOwnName
    ...mapState(useCounterStore, {
      myOwnName: 'doubleCounter',
      // you can also write a function that gets access to the store
      double: store => store.doubleCount,
    }),
  },
}

4、Actions

Actions相当于组件中的 methods 。可以使用defineStore()中的actions属性来定义它们,并且它们非常适合定义业务逻辑:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  actions: {
    increment() {
      this.counter++
    },
    randomizeCounter() {
      this.counter = Math.round(100 * Math.random())
    },
  },
})

和getters一样,actions通过this来访问整个store实例,还有完整的类型支持(和自动补全功能)。与它们不同的是,**actions**可以是异步的,您可以在它们内部进行任何API的调用,甚至其他操作!下面是一个使用Mande的示例。请注意,只要你得到了一个Promise ,你使用什么样的库并不重要,您甚至可以使用原生的fetch函数(仅适用于浏览器端):

import { mande } from 'mande'

const api = mande('/api/users')

export const useUsers = defineStore('users', {
  state: () => ({
    userData: null,
    // ...
  }),

  actions: {
    async registerUser(login, password) {
      try {
        this.userData = await api.post({ login, password })
        showTooltip(`Welcome back ${this.userData.name}!`)
      } catch (error) {
        showTooltip(error)
        // let the form component display the error
        return error
      }
    },
  },
})

您也可以完全自由地设置任何您想要的参数并返回任何东西。当调用actions时,一切都会被自动推断出来!

actionsmethods调用类似:

export default defineComponent({
  setup() {
    const main = useMainStore()
    // call the action as a method of the store
    main.randomizeCounter()

    return {}
  },
})

4.1 访问其他 stores 的 actions

要使用另一个store,您可以直接在action内部使用它:

import { useAuthStore } from './auth-store'

export const useSettingsStore = defineStore('settings', {
  state: () => ({
    // ...
  }),
  actions: {
    async fetchUserPreferences(preferences) {
      const auth = useAuthStore()
      if (auth.isAuthenticated) {
        this.preferences = await fetchPreferences()
      } else {
        throw new Error('User must be authenticated')
      }
    },
  },
})

4.2 setup() 中的用法

您可以直接调用任何action作为store的方法:

export default {
  setup() {
    const store = useStore()

    store.randomizeCounter()
  },
}

4.3 Options API 中的用法

对于以下示例,您可以假设创建了以下store:

// Example File Path:
// ./src/stores/counterStore.js

import { defineStore } from 'pinia'

const useCounterStore = defineStore('counterStore', {
  state: () => ({
    counter: 0
  }),
  actions: {
    increment() {
      this.counter++
    }
  }
})

4.3.1 使用 setup()

虽然Composition API并不适合所有人,但setup()钩子可以让Pinia更容易在Options API中使用。不需要额外的辅助函数!

import { useCounterStore } from '../stores/counterStore'

export default {
  setup() {
    const counterStore = useCounterStore()

    return { counterStore }
  },
  methods: {
    incrementAndPrint() {
      counterStore.increment()
      console.log('New Count:', counterStore.count)
    },
  },
}

4.3.2 不使用 setup()

如果您根本不想使用Composition API,您可以使用mapActions()辅助函数将actions属性映射为组件中的methods

import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  methods: {
    // gives access to this.increment() inside the component
    // same as calling from store.increment()
    ...mapActions(useCounterStore, ['increment']),
    // same as above but registers it as this.myOwnName()
    ...mapActions(useCounterStore, { myOwnName: 'doubleCounter' }),
  },
}

4.4 订阅 actions

可以使用store.$onAction()来观察actions及其结果。传递给它的回调函数在action本身之前执行。在处理promises之后,允许您在action resolves之后执行函数。类似地,onError允许你在action抛出或rejects时执行函数。这些对于在运行时跟踪错误很有用,类似于Vue文档中的这个技巧。

下面是一个在运行actions之前和resolve/reject之后记录日志的示例。

const unsubscribe = someStore.$onAction(
  ({
    name, // name of the action
    store, // store instance, same as `someStore`
    args, // array of parameters passed to the action
    after, // hook after the action returns or resolves
    onError, // hook if the action throws or rejects
  }) => {
    // a shared variable for this specific action call
    const startTime = Date.now()
    // this will trigger before an action on `store` is executed
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    // this will trigger if the action succeeds and after it has fully run.
    // it waits for any returned promised
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // this will trigger if the action throws or returns a promise that rejects
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

// manually remove the listener
unsubscribe()

默认情况下,action订阅被绑定到添加它们的组件(如果store在组件的setup()中)。这就意味着,当组件被卸载时,它们将被自动删除。如果你想在组件卸载后保留它们,传true作为第二个参数,以将操作订阅与当前组件分离:

export default {
  setup() {
    const someStore = useSomeStore()

    // this subscription will be kept after the component is unmounted
    someStore.$onAction(callback, true)

    // ...
  },
}

5、Plugins

由于低版本的APIPiniastores可以完全扩展。下面是一些你可以做的事情:

  • stores添加新的属性
  • 在定义stores时添加新选项
  • stores添加新方法
  • 包装现有的方法
  • 更改甚至取消操作
  • 实现像本地存储这样的功能
  • 只适用于特定的stores

使用pinia.use()将插件添加到pinia实例中。最简单的例子是通过返回一个对象向所有stores 添加一个静态属性:

import { createPinia } from 'pinia'

// add a property named `secret` to every store that is created after this plugin is installed
// this could be in a different file
function SecretPiniaPlugin() {
  return { secret: 'the cake is a lie' }
}

const pinia = createPinia()
// give the plugin to pinia
pinia.use(SecretPiniaPlugin)

// in another file
const store = useStore()
store.secret // 'the cake is a lie'

这对于添加全局对象(如routermodaltoast管理器)非常有用。

5.1 介绍

Pinia的插件是一个函数,可以选择返回要添加到store中的属性。它有一个可选参数 context:

export function myPiniaPlugin(context) {
  context.pinia // the pinia created with `createPinia()`
  context.app // the current app created with `createApp()` (Vue 3 only)
  context.store // the store the plugin is augmenting
  context.options // the options object defining the store passed to `defineStore()`
  // ...
}

然后将此函数传递给piniapinia.use()

pinia.use(myPiniaPlugin)

插件只应用于stores被创建在pinia传递给应用程序后 ,否则它们不会被应用。

5.2 扩展 Store

你可以通过在插件中返回一个属性对象来为每个store添加属性:

pinia.use(() => ({ hello: 'world' }))

你也可以直接在store中设置属性,如果可以的话,请返回版本,以便它们可以被devtools自动跟踪:

pinia.use(({ store }) => {
  store.hello = 'world'
})

插件返回的任何属性都将由devtools自动追踪,因此为了hellodevtools中可见,请确保仅在开发模式中添加store._customProperties属性,如果您想在devtools中调试的话:

// from the example above
pinia.use(({ store }) => {
  store.hello = 'world'
  // make sure your bundler handle this. webpack and vite should do it by default
  if (process.env.NODE_ENV === 'development') {
    // add any keys you set on the store
    store._customProperties.add('hello')
  }
})

需要注意的是,每个store都会使用reactive包装,并且会自动解包它包含的任何Ref(ref(), computed(), …)等:

const sharedRef = ref('shared')
pinia.use(({ store }) => {
  // each store has its individual `hello` property
  store.hello = ref('secret')
  // it gets automatically unwrapped
  store.hello // 'secret'

  // all stores are sharing the value `shared` property
  store.shared = sharedRef
  store.shared // 'shared'
})

这就是为什么您可以访问所有不带.value计算属性它们是响应式的原因。

5.2.1 添加新状态

如果您想在激活过程中添加新的状态属性或属性到store,您必须在两个地方添加它:

  • store中,您可以通过store.myState访问它
  • store.$state中,它可以在devtools中使用,并且在SSR期间被序列化。

请注意,这允许您共享refcomputed属性:

const globalSecret = ref('secret')
pinia.use(({ store }) => {
  // `secret` is shared among all stores
  store.$state.secret = globalSecret
  store.secret = globalSecret
  // it gets automatically unwrapped
  store.secret // 'secret'

  const hasError = ref(false)
  store.$state.hasError = hasError
  // this one must always be set
  store.hasError = toRef(store.$state, 'hasError')

  // in this case it's better not to return `hasError` since it
  // will be displayed in the `state` section in the devtools
  // anyway and if we return it, devtools will display it twice.
})

请注意,在插件中发生的状态改变或添加(包括调用store.$patch())发生在store激活之前,因此不会触发任何订阅。

WARNING
如果您使用的是Vue 2Pinia将受到与Vue相同的反应警告。当创建新的状态属性如 secrethasError时,您需要使用来自@vue/composition-apiset方法。

import { set } from '@vue/composition-api'
pinia.use(({ store }) => {
  if (!store.$state.hasOwnProperty('hello')) {
    const secretRef = ref('secret')
    // If the data is meant to be used during SSR, you should
    // set it on the `$state` property so it is serialized and
    // picked up during hydration
    set(store.$state, 'secret', secretRef)
    // set it directly on the store too so you can access it
    // both ways: `store.$state.secret` / `store.secret`
    set(store, 'secret', secretRef)
    store.secret // 'secret'
  }
})

5.3 添加新的外部属性

当添加外部属性,来自其他库的类实例或简单的非响应式对象时,应该在将对象传递给pinia 之前使用markRaw()包装该对象。下面是一个将路由添加到所有store的示例:

import { markRaw } from 'vue'
// adapt this based on where your router is
import { router } from './router'

pinia.use(({ store }) => {
  store.router = markRaw(router)
})

5.4 在插件内部调用 $subscribe

您也可以在插件中使用store.$subscribestore.$onAction

pinia.use(({ store }) => {
  store.$subscribe(() => {
    // react to store changes
  })
  store.$onAction(() => {
    // react to store actions
  })
})

5.5 添加新选项

可以在定义stores时创建新的选项,以便随后从插件中使用它们。例如,你可以创建一个debounce选项,允许你对任何操作进行debounce :

defineStore('search', {
  actions: {
    searchContacts() {
      // ...
    },
  },

  // this will be read by a plugin later on
  debounce: {
    // debounce the action searchContacts by 300ms
    searchContacts: 300,
  },
})

插件可以读取该选项来包装actions并替换原来的actions:

// use any debounce library
import debounce from 'lodash/debunce'

pinia.use(({ options, store }) => {
  if (options.debounce) {
    // we are overriding the actions with new ones
    return Object.keys(options.debounce).reduce((debouncedActions, action) => {
      debouncedActions[action] = debounce(
        store[action],
        options.debounce[action]
      )
      return debouncedActions
    }, {})
  }
})

请注意,使用setup语法时,自定义选项作为第三个参数传入:

defineStore(
  'search',
  () => {
    // ...
  },
  {
    // this will be read by a plugin later on
    debounce: {
      // debounce the action searchContacts by 300ms
      searchContacts: 300,
    },
  }
)

5.6 TypeScript

上面显示的所有内容都可以通过编写支持,因此您无需使用any@ts-ignore

5.6.1 编写插件

Pinia插件可以按如下方式编写:

import { PiniaPluginContext } from 'pinia'

export function myPiniaPlugin(context: PiniaPluginContext) {
  // ...
}

5.6.2 编写新的store属性

当向stores添加新属性时,您还应该扩展PiniaCustomProperties接口。

import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomProperties {
    // by using a setter we can allow both strings and refs
    set hello(value: string | Ref<string>)
    get hello(): string

    // you can define simpler values too
    simpleNumber: number
  }
}

然后可以安全地写入和读取:

pinia.use(({ store }) => {
  store.hello = 'Hola'
  store.hello = ref('Hola')

  store.number = Math.random()
  // @ts-expect-error: we haven't typed this correctly
  store.number = ref(Math.random())
})

PiniaCustomProperties是一个泛型类型,允许您引用store的属性。想象一下下面的示例,我们将初始选项复制为$options(这仅适用于option stores):

pinia.use(({ options }) => ({ $options: options }))

我们可以通过使用PiniaCustomProperties的4个泛型类型来正确地输入这个值:

import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomProperties<Id, S, G, A> {
    $options: {
      id: Id
      state?: () => S
      getters?: G
      actions?: A
    }
  }
}

TIP

在泛型中扩展类型时,它们的命名必须与源码中的完全相同。Id不能命名为idIS也不能命名为State。以下是每个字母所代表的含义:

  • S: State
  • G: Getters
  • A: Actions
  • SS: Setup Store / Store

5.6.3 编写新的状态

当添加新的状态属性时(同时添加到storestore.$state),您需要将类型添加到PiniaCustomStateProperties。与PiniaCustomProperties不同的是,它只接收State泛型:

import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomStateProperties<S> {
    hello: string
  }
}

5.6.4 编写新的创建选项

当为defineStore()创建新选项时,您应该扩展DefineStoreOptionsBase。与PiniaCustomProperties不同的是,它只公开两种泛型:StateStore类型,允许您限制可以定义的类型。例如,你可以使用actions的名称:

import 'pinia'

declare module 'pinia' {
  export interface DefineStoreOptionsBase<S, Store> {
    // allow defining a number of ms for any of the actions
    debounce?: Partial<Record<keyof StoreActions<Store>, number>>
  }
}

提示
还有一个StoreGetters类型用于从Store类型中提取getters。您还可以分别通过DefineStoreOptionsDefineSetupStoreOptions类型来扩展设置setup storesoption stores的选项。

5.7 Nuxt.js

NuxtPinia一起使用时,您必须先创建一个Nuxt插件。这将使您可以访问该Pinia实例:

// plugins/myPiniaPlugin.js
import { PiniaPluginContext } from 'pinia'
import { Plugin } from '@nuxt/types'

function MyPiniaPlugin({ store }: PiniaPluginContext) {
  store.$subscribe((mutation) => {
    // react to store changes
    console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}.`)
  })

  return { creationTime: new Date() }
}

const myPlugin: Plugin = ({ pinia }) {
  pinia.use(MyPiniaPlugin);
}
export default myPlugin

注意上面的例子使用的是TypeScript,如果你使用的是.js文件,你必须删除PiniaPluginContext的类型注释和Plugin的引入。

6、在组件之外使用 Store

Pinia stores依赖于Pinia实例在所有调用中共享相同的store实例。大多数情况下,只需调用您的useStore()函数,就可以开箱即用了。例如,在setup()中,您不需要做任何其他事情。但是在组件之外使用的情况有点不同。在后台,useStore()会注入到你应用程序的pinia实例中。这意味着,如果pinia实例不能被自动注入,你必须手动将它提供给useStore()函数。根据所编写的应用程序的类型,可以采用不同的方法来解决这个问题。

6.1 单页应用程序

如果你不做SSR(服务端渲染),安装pinia插件并app.use(pinia)后,任何调用useStore()方法将起作用:

import { useUserStore } from '@/stores/user'
import { createApp } from 'vue'
import App from './App.vue'

// ?  fails because it's called before the pinia is created
const userStore = useUserStore()

const pinia = createPinia()
const app = createApp(App)
app.use(pinia)

// ? works because the pinia instance is now active
const userStore = useUserStore()

确保始终应用此方法的最简单方法是,通过将useStore()的调用总是放置在安装pinia之后运行的函数中,从而推迟对它们的调用。

让我们来看看这个在Vue Router的导航守卫中使用store的示例:

import { createRouter } from 'vue-router'
const router = createRouter({
  // ...
})

// ? Depending on the order of imports this will fail
const store = useStore()

router.beforeEach((to, from, next) => {
  // we wanted to use the store here
  if (store.isLoggedIn) next()
  else next('/login')
})

router.beforeEach((to) => {
  // ? This will work because the router starts its navigation after
  // the router is installed and pinia will be installed too
  const store = useStore()

  if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
})

6.2 SSR 应用

在处理服务器端渲染时,您必须将 pinia 实例传递给 useStore()。 这可以防止 pinia 在不同的应用程序实例之间共享全局状态。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-01 23:16:14  更:2022-04-01 23:16:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 6:00:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码