Pinia 学习笔记

安装

npm install pinia

实例创建

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

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

app.use(pinia)
app.mount('#app')

定义store (选项式) 建议这种方式

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0, name: 'Eduardo' }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

定义store (setup)

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const name = ref('Eduardo')
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, name, doubleCount, increment }
})

In Setup Stores:
• ref()s become state properties
• computed()s become getters
• function()s become actions

在组件中使用store

import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'

export default defineComponent({
  setup() {
    const store = useCounterStore()
    // `name` and `doubleCount` 是响应式数据
    // 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) 
    // the increment action can be just extracted
    const { increment } = store

    return {
      name,
      doubleCount,
      increment,
    }
  },
})

实践:console.log了下store对象,发现在state和getter定义的变量都会转化成ref响应式数据
在组件中使用(setup方法中),会自动解包,结合结构解析,可以使用storeToRefs方法来保持数据的响应性

定义state (typescript方式)

//建议这种方式
interface State {
  userList: UserInfo[]
  user: UserInfo | null
}

export const useUserStore = defineStore('user', {
  state: (): State => {
    return {
      userList: [],
      user: null,
    }
  },
})

interface UserInfo {
  name: string
  age: number
}

//重置state
const store = useStore()
store.$reset()

//状态提交
store.$patch({
  count: store.count + 1,
  age: 120,
  name: 'DIO',
})

//$patch接受一个函数
cartStore.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

其实状态运用响应式数据的特性修改,更为方便一些,特殊情况下也可以使用$patch。

订阅状态

当数据变更时(直接修改state、通过patch对象修改、通过patch函数修改),会触发该回调。

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))
})

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

整个状态的检测

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

不建议这么用,状态过多时,开销太大。

定义getter

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

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  getters: {
    // automatically infers the return type as a number
    doubleCount(state) {
      return state.count * 2
    },
    // the return type **must** be explicitly set
    doublePlusOne(): number {
      // autocompletion and typings for the whole store  这里可以使用this,但要注意如果是typescript需要定义返回值类型
      return this.doubleCount + 1
    },
    otherGetter(state) {
      const otherStore = useOtherStore() //使用其他store 引入后 直接使用即可
      return state.localData + otherStore.data
    },
  },
})

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>

这里的getter不会缓存,类似于方法调用

选项式 setup中使用


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

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

actions使用

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
      }
    },
  },
})

action方法的调用

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

    return {}
  },
})

访问其他store actions,同getter
在选项式api中调用,同getter

订阅actions

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()

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

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

    // ...
  },
}

外部使用,比如vue-router

import { createRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'

const router = createRouter({
  // ...
})

router.beforeEach((to) => {
  const store = useStore()
  if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
})

总结:

整体思路跟vuex是有区别的,pinia完全是小模块的按需引用,不像vuex会有store一个入口文件,会在初始化时将相关内容初始化完毕。而且在使用中访问其他store的getter、action更加方便,结合vue3的响应性,不依赖于commit(patch)的提交,直接修改即可,更加方便。

待学习内容:Plugins的使用
参考文档:https://pinia.vuejs.org/introduction.html

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容