likes
comments
collection
share

Pinia与Vuex4的区别及【Pinia.js上手指南】

作者站长头像
站长
· 阅读数 53

2022 年什么会火?什么该学?本文正在参与“聊聊 2022 技术趋势”征文活动 」

认识Pinia

Pinia的优势

Pinia 相比 Vuex 更加简单,Pinia 是符合直觉的状态管理方式,让使用者回到了模块导入导出的原始状态,使状态的来源更加清晰可见

Vue-Devtools 支持:

  • 一个追踪动作、变化的时间轴
  • 出现在他们使用的组件中的存储Stores
  • 时间旅行和更简便的调试

模块热替换:

  • 在不要求重新加载页面条件下修改你的 stores
  • 当开发时保持所有存在的状态(state)

插件:

  • 使用插件扩展Pinia功能

为JS用户提供适当的 TypeScript 支持与自动完成功能

支持服务端渲染

区别

Pinia API 与 Vuex 4有相当大的不同即:

  • mutations 不再存在
  • 不需要创建自定义的复杂包装器来支持TypeScript,一切都是类型化的,并且API的设计方式是尽可能利用TS类型推断。
  • 不再有魔术字符串注入,导入函数,调用它们,享受自动完成
  • 不需要动态添加stores,默认都是动态的,你甚至不会注意到。注意,你仍然可以随时手动使用stores来注册它,但是因为它是自动的,所以不需要担心。
  • 不再有 modules 的嵌套结构。你仍然可以通过在另一个store中导入和 using 一个store来隐式嵌套stores,但是Pinia提供了一种平面的设计结构,同时仍然支持store之间的交叉组合方式。
  • 没有 namespaced模块 。考虑到stores的平面结构,namespacing stores是定义store的固有方式,你可以说所有的stores都是namespacing

基本介绍:

1、创建defineStore()

我打算先去说一下defineStore(),因为store是使用这个定义的,并且它需要一个唯一的名字,作为第一个参数传递:

import { defineStore } from 'pinia'

// useStore 一切例如 useUser, useCart
// 第一个参数是应用程序中store的唯一id
export const useStore = defineStore('main', {
  // 其他选项...
})

2、使用store

我们 定义 一个 store 因为直到在setup()内部调用“useStore()”之前, store并不会被创建:

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

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

    return {
      // 您可以返回整个存储实例,以便在模板中使用它
      store,
    }
  },
}

你可以定义任意多的stores, 你应该在不同的文件中定义每个store 一旦存储被实例化,您就可以直接在存储上访在“state”、“getters”和“actions”中定义的任何属性。

注意store是一个用reactive包装的对象

3、状态State

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

import { defineStore } from 'pinia'

const useStore = defineStore('storeId', {
  // 推荐用于全类型推理的箭头函数
  state: () => {
    return {
      // 所有这些属性都将自动推断出它们的类型
      counter: 0,
      name: 'Eduardo',
      isAdmin: true,
    }
  },
})

技巧:

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

4、getters

类似于组件的computed,用来封装计算属性 有缓存功能

    getters: {
        // 拥有缓存
        // 函数接受一个可选参数:state 状态对象
        // counts(state) {
        //     console.log('调用了');
        //     return state.count + 10
        // }

        // 如果再 getters中使用了 this 则必须手动指定返回值的类型 否则类型推到不出来
         counts():number {
            console.log('调用了');
            return this.count + 10
        }
    }

打印缓存(下面完整使用列子有):

Pinia与Vuex4的区别及【Pinia.js上手指南】

5、actions

封装业务逻辑,修改state数据,actions可以是异步的,你可以await在它们内部进行任何 API 调用甚至其他操作,调用动作时,一切都会自动推断

    actions: {
        // 注意:不能使用箭头函数定义 action
        // 为什么? y 很简单 因为箭头函数绑定外部的this,如果你使用箭头函数 指向就发送改变了

        // 接收前面传过来的参数
        ggState(num:number) {
            // 简单情况这样修改即可
            this.count+=num
            this.foo = 'ggg'
            this.arr.push(6)

            // 如果复杂情况  (和前面一样,只不过拿到了这里)
            // this.$patch({})
            // this.$patch(state =>{})
        }
    }

注意: 不能使用箭头函数定义 action

为什么? 很简单 因为箭头函数绑定外部的this,如果你使用箭头函数 指向就发送改变了

6、状态options API

如果您不使用 组合 API,而您正在使用computedmethods, …,则可以使用mapState()帮助器将状态属性映射为只读计算属性:

import { mapState } from 'pinia'

export default {
  computed: {
    // 在组件内允许访问 this.counter 
    // 与从 store.counter 读取一样
    ...mapState(useStore, ['counter'])
    // 与上面一样但是将注册它为 this.myOwnName
    ...mapState(useStore, {
      myOwnName: 'counter',
      // 你也可以写一个函数来访问 store
      double: store => store.counter * 2,
      // 它也能访问 `this` ,但是它不会正确地标注类型...
      magicValue(store) {
        return store.someGetter + this.counter + this.double
      },
    }),
  },
}

Piani安装

pinia使用您最喜欢的包管理器安装:

yarn add pinia
# or with npm
npm install pinia

创建一个 pinia 并将其传递给应用程序:

import { createPinia } from 'pinia'
​
app.use(createPinia())

如果您使用的是 Vue 2,您还需要安装一个插件并pinia在应用程序的根目录注入创建的插件:

import { createPinia, PiniaVuePlugin } from 'pinia'Vue.use(PiniaVuePlugin)
const pinia = createPinia()
​
new Vue({
  el: '#app',
  // other options...
  // ...
  // note the same `pinia` instance can be used across multiple Vue apps on
  // the same page
  pinia,
})

注意:Vue3和Vue2使用起来是差不多的。

完整使用

项目结构:

Pinia与Vuex4的区别及【Pinia.js上手指南】

首先,我们需要进行安装Pinaia

yarn add pinia
# or with npm
npm install pinia

然后再main.ts注册:

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

// 创建Pinia实列
const Pinia = createPinia()

const app = createApp(App)

// 挂载
app.use(Pinia)

createApp(App).mount('#app')
新建了一个store:
import { defineStore } from "pinia";

// 参数1:容器的ID,必须唯一,将来的Pinia 会把所有的容器挂载到跟容器上面
// 参数2:选项对象
// 返回参数:一个函数,调用得到容器实列
export const useMainStore = defineStore('main', {
    /**
     * 
     * @state 类似于组件的data,用来储存全局状态的
     * 1、必须是函数:这样是为了在服务端渲染的时候,避免交叉请求导致数据状态污染
     * 2、必须是箭头函数,这是为了更好的TS类型推导
     * 
     */
    state: () => {
        return {
            count: 100,
            foo: 'lc',
            arr: [1, 2, 3]
        }
    },

    /**
     * 
     * @getters 类似于组件的computed,用来封装计算属性 有缓存功能
     * 
     */
    getters: {
        // 拥有缓存
        // 函数接受一个可选参数:state 状态对象
        // counts(state) {
        //     console.log('调用了');
        //     return state.count + 10
        // }

        // 如果再 getters中使用了 this 则必须手动指定返回值的类型 否则类型推到不出来
         counts():number {
            console.log('调用了');
            return this.count + 10
        }
    },


    /**
    * 
    * @actions 封装业务逻辑,修改state数据
    * 
    */
    //相当于组件中方法
    // actions可以是异步的,你可以 await 在它们内部进行任何 API 调用甚至其他操作,调用动作(Actions)时,一切都会自动推断
    actions: {
        // 注意:不能使用箭头函数定义 action
        // 为什么? 很简单 因为箭头函数绑定外部的this,如果你使用箭头函数 指向就发送改变了

        // 接收前面传过来的参数
        ggState(num:number) {
            // 简单情况这样修改即可
            this.count+=num
            this.foo = 'ggg'
            this.arr.push(6)

            // 如果复杂情况  (和前面一样,只不过拿到了这里)
            // this.$patch({})
            // this.$patch(state =>{})


            // patch 和 普通多次修改的区别再原理上面的区别是上面,按理来说多次修改也是批量提交的吧
            // 普通多次修改:没改一次都需要进行一个视图更新
            // patch 一次性把数据修改好

        }
    }
})

直接再组件中使用:

<template>
  <div>
    <p> {{mainStore.count}}</p>
    <p> {{mainStore.foo}}</p>
    <p> {{mainStore.counts}}</p>
    <p> {{mainStore.counts}}</p>
    <p> {{mainStore.counts}}</p>
  </div>
  <hr>
  <p>
    {{count}}
  </p>
  <p>
    {{foo}}
  </p>
  <hr>
  <p>
    <button @click="handleChangState">修改数据</button>
  </p>

</template>
<script setup lang="ts">
import { ref } from "vue";
import { storeToRefs } from "pinia";
import { useMainStore } from "../store";
const mainStore = useMainStore();
console.log(mainStore.counts);

// 这是有问题的,因为这样拿到的数据不是响应式的,是一次性的。
// Pinia 其实就是把state 数据都做了 reactive 处理了
// const {count,foo} = mainStore

// 解决办法就是使用
// 把解构出来的数据做 ref 响应式代理
const { count, foo } = storeToRefs(mainStore);


const handleChangState = () => {
  // 方式一:数据修改最简单的方式
  // mainStore.count++;

  // 方式二: 如果修改多个数据,建议使$patch 批量更新
  // 这里传了一个对象
  mainStore.$patch({
    count: mainStore.count + 1,
    foo: "gg",
  });

  // 方式三: $patch  一个函数,批量更新 (建议使用方式)
  // 这里传了一个函数
  // mainStore.$patch(state=>{
  //   state.count++
  //   state.foo = 'GG'
  //   state.arr.push(6)
  // })

  // 方法四:逻辑比较多的时候 可以封装到 actions 做处理
  mainStore.ggState(5);
  
};
</script>

Vue-Devtools 支持:

Pinia与Vuex4的区别及【Pinia.js上手指南】

Pinia与Vuex4的区别及【Pinia.js上手指南】

与君共勉,感谢阅读!

转载自:https://juejin.cn/post/7056082767372615687
评论
请登录