Pinia入门

pinia

一、Pinia介绍

Pinia的设计主要是服务于Composite API(组合式API)
因为Vuex主要是为了vue2种的选项是API服务的所以,并不适用于Vue3。当Vue3伴随着组合式API来到时,Pinia的诞生也就成为了必然,一个全新的用于 Vue的状态管理库Pinia。
官方文档:https://pinia.vuejs.org/
中文文档:https://pinia.web3doc.top/

1.1 Pinia 核心特性

  • Pinia 没有 Mutations
  • Actions 支持同步和异步
  • 没有模块的嵌套结构
    • Pinia 通过设计提供扁平结构,就是说每个 store 都是互相独立的,谁也不属于谁,也就是扁平化了,更好的代码分割且没有命名空间。当然你也可以通过在一个模块中导入另一个模块来隐式嵌套 store,甚至可以拥有 store 的循环依赖关系
  • 更好的 TypeScript 支持
    • 不需要再创建自定义的复杂包装器来支持 TypeScript 所有内容都类型化,并且 API 的设计方式也尽可能的使用 TS 类型推断
  • 不需要注入、导入函数、调用它们,享受自动补全,让我们开发更加方便
  • 无需手动添加 store,它的模块默认情况下创建就自动注册的
  • Vue2 和 Vue3 都支持
    • 除了初始化安装和SSR配置之外,两者使用上的API都是相同的
  • 支持 Vue DevTools
    • 跟踪 actions, mutations 的时间线
    • 在使用了模块的组件中就可以观察到模块本身
    • 支持 time-travel 更容易调试
    • 在 Vue2 中 Pinia 会使用 Vuex 的所有接口,所以它俩不能一起使用
    • 但是针对 Vue3 的调试工具支持还不够完美,比如还没有 time-travel 功能
  • 模块热更新
    • 无需重新加载页面就可以修改模块
    • 热更新的时候会保持任何现有状态
  • 支持使用插件扩展 Pinia 功能
  • 支持服务端渲染

1.2 Pinia和Vuex对比

Vuex和Pinia对比

二、快速入门

2.1 安装依赖

--npm init vite@latest
--npm install pinia

2.2 基础store

/**
 * 注册容器
 */

import { defineStore } from "pinia"

// 定义容器
// 参数1: 容器id, 必须唯一, 可以理解为命名容器
// 参数2: 选项对象
export const useMainStore = defineStore('main', {
    // 类似组件的data, 用来存储全局状态
    // 注意:
    // 1. 必须是一个函数【避免多个请求交叉污染】
    // 2. 必须时箭头函数,方便TS的类型推断
    state: () => {
        return {
            count: 0
        }
    },
    // 类似组件的computed, 用来计算状态,也有缓存功能
    // 参数1 就是stat而对象
    getters: {
        doubleCount: (state) => state.count * 2,
        // 可以在getters中使用this,需手动标记getter的返回值类型
        getCount() : number {
            return this.count + 10
        }
    },
    // 类似组件的methods,主要用来封装操作逻辑
    actions: {
        increment() {
            this.count++
        },
        incrementA(a: number) {
            this.count += a
        }
    }
})

<template>
    <h2>count计算</h2>
    <p>{{ mainStore.count }}</p>
    <p>
        <button @click="handleChangeState">修改state</button>
    </p>
</template>

<script setup lang="ts">
import { useMainStore } from '../store'

// 调用得到容器对象
const mainStore = useMainStore()

const handleChangeState = () => {
    // 方式一:直接修改state
    mainStore.count++
    // 重置状态
    mainStore.$reset() 

    // 方式二:批量更新
    mainStore.$patch({
        count: mainStore.count + 1
    })
    // 接收函数
    mainStore.$patch((state) => { 
     state.count += 1
    })

    // 方法三:action中修改
    // Vuex中调用mutation先commit,再dispatch
    // action同步异步都支持
    mainStore.increment()
}
</script>

2.3 Demo场景

1)案例场景

案例场景:https://github.com/vuejs/vuex/tree/main/examples
购物车场景:

购物车案例

2)业务api

export interface IProduct {
    id: number
    title: string
    price: number
    inventory: number
} 

const _products: IProduct[] = [
    { 'id': 1, 'title': 'iPad 4 Mini', 'price': 500.01, 'inventory': 2 },
    { 'id': 2, 'title': 'H&M T-Shirt White', 'price': 10.99, 'inventory': 10 },
    { 'id': 3, 'title': 'Charli XCX - Sucker CD', 'price': 19.99, 'inventory': 5 }
]

export const getProducts = async () => {
    await wait(100)
    return _products
}

export const buyProducts = async () => {
    await wait(100)
    return Math.random() > 0.5
}

const wait = async (delay:number) => {
    return new Promise((resolve) => setTimeout(resolve, delay))
}

3)store场景

import { defineStore } from "pinia";
import { IProduct, buyProducts } from "../api/shop";
import { useProductsStore } from "./products";

type CartProduct = { 
    quantity: number
} & Omit<IProduct, 'inventory'>

export const useCartStore = defineStore('cart', {
    state: () => {
        return {
            cartProducts: [] as CartProduct[],
            checkoutStatus: null as null | string  
        }
    },
    getters: {
        totalPrice (state) {
            return state.cartProducts.reduce((total, product) => {
                return total + product.price * product.quantity
            }, 0)
        }
    },
    actions: {
        addProductToCart(product : IProduct) {
            // 商品是否有库存
            if (product.inventory <= 0) {
                return 
            }
            // 购物车是否有该商品
            const cartProduct = this.cartProducts.find(item => item.id === product.id)
            if (cartProduct) {
                cartProduct.quantity++
            } else {
                this.cartProducts.push({
                    ...product,
                    quantity: 1
                })
            }
            // 更新下商品的库存
            const productsStore = useProductsStore()
            productsStore.decrementProductInventory(product)
        },
        async checkout() {
            this.checkoutStatus = null
            const res = await buyProducts()
            res ? this.checkoutStatus = '成功' : this.checkoutStatus = '失败'
        }
    }
})
import { defineStore } from "pinia"
import { IProduct, getProducts } from "../api/shop"

export const useProductsStore = defineStore('products', {
    state: () => {
        return {
            all: [] as IProduct[]
        }
    },
    getters: {},
    actions: {
       async loadAllProducts() {
            const res = await getProducts()
            this.all = res
       },
       async decrementProductInventory(product: IProduct) {
            const result =  this.all.find(item => item.id === product.id)
            if (result) {
                result.inventory--
            }
       }
    }
})

4)业务场景

<template>
    <ul>
        <li v-for="product in all" :key="product.id">
            {{ product.title }} - {{ product.price }}
            

            <button 
                :disabled="!product.inventory"
                @click="cartStore.addProductToCart(product)"
            >添加购物车</button>
        </li>
    </ul>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useProductsStore } from './../store/products'
import { useCartStore } from './../store/cart'
const productsStore = useProductsStore()
const cartStore = useCartStore()

// 异步加载更新数据
productsStore.loadAllProducts()
// 数据解构
// 这种写法存在问题,只会拿到第一次的数据,之后非响应式
// 提供storeToRefs api , 被ref包裹
const { all } = storeToRefs(productsStore)
console.log(all.value) // ref结构。需要.value才能拿到数据
</script>
<template>
    <div class="cart">
        <h2>购物车</h2>
        <ul>
            <li v-for="product in cartStore.cartProducts" :key="product.id">
                {{ product.title }} - {{ product.price }} x {{  product.quantity }}
                

            </li>
        </ul>
        <p> 总价:{{ cartStore.totalPrice }}</p>
        <button @click="cartStore.checkout">结算</button>
        <p v-show="cartStore.checkoutStatus"> 结算结果 {{ cartStore.checkoutStatus }}</p>
    </div>
</template>
<script setup lang="ts">
import { useCartStore } from './../store/cart'
const cartStore = useCartStore()
</script>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,312评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,578评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,337评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,134评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,161评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,303评论 1 280
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,761评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,421评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,609评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,450评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,504评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,194评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,760评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,836评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,066评论 1 257
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,612评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,178评论 2 341

推荐阅读更多精彩内容