Vue 第二章 Pinia

1、概述

  • Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。

1)对比 Vuex

  • 更小,只有1kb;
  • 更简单,actions 可以同时处理同步任务和异步任务;
  • 不再有命名空间模块,但是各个 store 之间相互独立并且可以互相引用。
  • 组合式 API 风格和良好的 TS 支持。

2)安装

  • Vue3 创建一个 pinia(根存储)并将其传递给应用程序

    import { createPinia } from 'pinia'
    
    app.use(createPinia())
  • Vue 2,您还需要安装一个插件并将创建的 pinia 注入应用程序的根目录
  • 需要安装组合 API:@vue/composition-api
import { createPinia, PiniaVuePlugin } from 'pinia'

Vue.use(PiniaVuePlugin)
const pinia = createPinia()

new Vue({
  el: '#app',
  // ...
  pinia,
})

2、store

1)定义

  • Store 是使用 defineStore() 定义的,并且它需要一个唯一名称,作为第一个参数传递

    • 这个 name,也称为 id,是必要的,Pinia 使用它来将 store 连接到 devtools。
    import { defineStore } from 'pinia'
    
    // useStore 可以是 useUser、useCart 之类的任何东西
    // 第一个参数是应用程序中 store 的唯一 id
    
    export const useStore = defineStore('main', {
      // other options...
    })

2)模块化

  • 可以根据需要定义任意数量的 store ,并且您应该在不同的文件中定义每个 store 以充分利用 pinia。

3、state

  • 在 Pinia 中,状态被定义为返回初始状态的函数。

    import { defineStore } from 'pinia'
    
    const useStore = defineStore('storeId', {
      // 推荐使用 完整类型推断的箭头函数
      state: () => {
        return {
          // 所有这些属性都将自动推断其类型
          counter: 0,
          name: 'Eduardo',
          isAdmin: true,
        }
      },
    })

1)访问 “state”

  • 默认情况下,您可以通过 store 实例访问状态来直接读取和写入状态
const store = useStore()

store.counter++

2)改变状态

  • 除了直接用 store.counter++ 修改 store,你还可以调用 $patch 方法。 它允许您使用部分“state”对象同时应用多个更改。

    store.$patch({
      counter: store.counter + 1,
      name: 'Abalam',
    })
  • $patch 方法也接受一个函数来批量修改集合内部分对象的情况:

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

3)重置状态

  • 可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值
const store = useStore()

store.$reset()

4)替换state

  • 可以通过将其 $state 属性设置为新对象来替换 Store 的整个状态:

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

5)订阅状态

  • 可以通过 store 的 $subscribe() 方法查看状态及其变化。
  • 与常规的 watch() 相比,使用 $subscribe() 的优点是 subscriptions 只会在 patches 之后触发一次。
  • 当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 { detached: true } 作为第二个参数传递给 detach 当前组件的 state subscription

    export default {
      setup() {
        const someStore = useSomeStore()
    
        // 此订阅将在组件卸载后保留
        someStore.$subscribe(callback, { detached: true })
    
        // ...
      },
    }

4、getters

  • Getter 完全等同于 Store 状态 的 computed

    • 箭头函数接收 state 作为第一个参数。
    • 常规函数通过 this 访问到整个 store 实例,但是需要定义返回类型。
    export const useStore = defineStore('main', {
      state: () => ({
        counter: 0,
      }),
      getters: {
        // 自动将返回类型推断为数字
        doubleCount(state) {
          return state.counter * 2
        },
        // 返回类型必须明确设置
        doublePlusOne(): number {
          return this.counter * 2 + 1
        },
      },
    })

1)访问其他 getter

  • 与计算属性一样,您可以组合多个 getter。 通过 this 访问任何其他 getter。
export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    // 类型是自动推断的,因为我们没有使用 `this`
    doubleCount: (state) => state.counter * 2,

    doubleCountPlusOne() {
      return this.doubleCount + 1
    },
  },
})

2)将参数传递给 getter

  • Getters 只是幕后的 computed 属性,因此无法向它们传递任何参数。 但是,您可以从 getter 返回一个函数以接受任何参数:

    export const useStore = defineStore('main', {
      getters: {
        getUserById: (state) => {
          return (userId) => state.users.find((user) => user.id === userId)
        },
      },
    })
    
    // App.vue
    
  • 在执行此操作时,getter 不再缓存,它们只是您调用的函数。 但是,您可以在 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)访问其他 Store 的getter

  • 要使用其他存储 getter,您可以直接在 getter 内部使用它
import { useOtherStore } from './other-store'

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

5、actions

  • Actions 相当于组件中的 methods,可以通过 this 访问到整个 store 实例。
export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  actions: {
    increment() {
      this.counter++
    },
    randomizeCounter() {
      this.counter = Math.round(100 * Math.random())
    },
  },
})
  • actions 可以是异步的,您可以在其中await 任何 API 调用甚至其他操作!
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)
        // 让表单组件显示错误
        return error
      }
    },
  },
})

1)访问其他 store 操作

  • 要使用另一个 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')
      }
    },
  },
})

2)订阅 Actions

  • 可以使用 store.$onAction() 订阅 action 及其结果。 传递给它的回调在 action 之前执行。
  • after 处理 Promise 并允许您在 action 完成后执行函数。
  • onError 允许您在处理中抛出错误。 这些对于在运行时跟踪错误很有用 。
const unsubscribe = someStore.$onAction(
  ({
    name, // action 的名字
    store, // store 实例
    args, // 调用这个 action 的参数
    after, // 在这个 action 执行完毕之后,执行这个函数
    onError, // 在这个 action 抛出异常的时候,执行这个函数
  }) => {
    // 记录开始的时间变量
    const startTime = Date.now()
    // 这将在 `store` 上的操作执行之前触发
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    // 如果 action 成功并且完全运行后,after 将触发。
    // 它将等待任何返回的 promise
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // 如果 action 抛出或返回 Promise.reject ,onError 将触发
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

// 手动移除订阅
unsubscribe()
  • 当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 true 作为第二个参数传递给当前组件的 detach action subscription

    export default {
      setup() {
        const someStore = useSomeStore()
    
        // 此订阅将在组件卸载后保留
        someStore.$onAction(callback, true)
    
        // ...
      },
    }

6、plugins

  • 使用 pinia.use() 将插件添加到 pinia 实例中。
  • 安装此插件后创建的每个store添加一个名为 secret 的属性
import { createPinia } from 'pinia'

// 这可能在不同的文件中
function SecretPiniaPlugin() {
  return { secret: 'the cake is a lie' }
}

const pinia = createPinia()

// 将插件提供给 pinia
pinia.use(SecretPiniaPlugin)

// 在另一个文件中
const store = useStore()
store.secret // 'the cake is a lie'
  • Pinia 插件是一个函数,可以选择返回要添加到 store 的属性。 它需要一个可选参数,一个 context
export function myPiniaPlugin(context) {
  context.pinia // 使用 `createPinia()` 创建的 pinia
  context.app // 使用 `createApp()` 创建的当前应用程序(仅限 Vue 3)
  context.store // 插件正在扩充的 store
  context.options // 定义存储的选项对象传递给`defineStore()`
  // ...
}

1)添加新的外部属性

  • 当添加外部属性、来自其他库的类实例或仅仅是非响应式的东西时,您应该在将对象传递给 pinia 之前使用 markRaw() 包装对象。 这是一个将路由添加到每个 store 的示例:

    import { markRaw } from 'vue'
    // 根据您的路由所在的位置进行调整
    import { router } from './router'
    
    pinia.use(({ store }) => {
      store.router = markRaw(router)
    })

2)在插件中调用 $subscribe

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

    pinia.use(({ store }) => {
        
      store.$subscribe(() => {
        // 在存储变化的时候执行
      })
        
      store.$onAction(() => {
        // 在 action 的时候执行
      })
    })

3)引入新的 store 属性

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

    import 'pinia'
    
    declare module 'pinia' {
      export interface PiniaCustomProperties {
        // 通过使用 setter,我们可以同时允许字符串和引用
        set hello(value: string | Ref)
        get hello(): string
    
        // 你也可以定义更简单的值
        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())
    })

你可能感兴趣的:(vue.js)