誰偷了我的鳳梨!聊聊意外修改 Pinia 資料問題

鱈魚-avatar-img
發佈於Vue
更新 發佈閱讀 9 分鐘
鱈魚的魚缸搬家了!新家文章皆有重新修訂,歡迎來新家看看喔。(´▽`ʃ♡ƪ)
raw-image


相信大家用 Vue 3 後應該都改用 Pinia 了吧?沒用過的人趕快試試看吧。(´,,•ω•,,)


Pinia 最簡單的用法就像這樣:

counter.ts

import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useStore = defineStore('counter', () => {
const n = ref(0)

return { n }
})


接著在要使用的地方呼叫 useStore:

App.vue

<script setup lang="ts">
import { useStore } from './counter.ts'

const store = useStore()

function increment() {
store.n++;
}
</script>

<template>
<button @click="increment">
Increment {{ store.n }}
</button>
</template>

其實像這樣直接操作 store.n 的方式,方便歸方便,苓膏龜苓膏


但不是個好做法,這種方式容易讓資料流混亂,想像一下你有多個元件都使用 store.n,然後想改就改。…( ・ิω・ิ)


比較推薦的方式通常為由 store 提供一個修改資料的 function,例如:

counter.ts

import { defineStore } from 'pinia'
import { ref } from 'vue'

interface User {
name: string;
price: number;
}

export const useStore = defineStore('counter', () => {
const user = ref<User>({
name: 'cod',
price: 100,
})

function updateUser(data: Partial<User>) {
user.value = {
...user.value,
...data,
}
}

return {
user,
updateUser,
}
})


呼叫的部份改為:

App.vue

<script setup lang="ts">
import { useStore } from './counter.ts'

const store = useStore()

function increment() {
const price = store.user.price + 1;
store.updateUser({ price });
}
</script>

<template>
<button @click="increment">
price: {{ store.user.price }}
</button>
</template>

這樣如果未來要新增邏輯、權限甚至重構,都容易得多。


但是問題來了,有時候流程中會有「確認」的按鈕,也就是要按下確認後,才修改 store 的資料。


假設有一個負責修改 User 資料的元件:

UserCard.vue

<script setup lang="ts">
import { ref } from 'vue'
import { useStore } from './counter.ts'

const store = useStore();
const user = ref(store.user);

function increment() {
user.value.price++;
}
function cancel() {
user.value = store.user;
}
function submit() {
store.updateUser(user.value);
}
</script>

<template>
<div>
<button class="button" @click="increment">
遞增
</button>
<button class="button" @click="submit">
確認
</button>

<div>
current price: {{ user.price }}
</div>
</div>
</template>

<style>
.button {
margin: 0px 4px;
}
</style>


App.vue

<script setup lang="ts">
import UserCard from './UserCard.vue';

import { useStore } from './counter.ts'

const store = useStore();
</script>

<template>
<div>
store price: {{ store.user.price }}
</div>

<hr />

<UserCard />
</template>

目前畫面如下圖。

raw-image


這時候你會發現出事啦!╭(°A ,°`)╮


按下遞增的時候,不只元件內的 user 數值發生變化,連 store 的數值也一起變啦!Σ(ˊДˋ;)


熟悉 JS 的朋友們一定都知道發生甚麼事,這是因為直接指派物件是 Call by Reference,所以:

const user = ref(store.user);


這個部分的程式會讓 user 依舊指向 store 的 user,結果就意外改到鳳梨裡面的資料了。(›´ω`‹ )


這時候聰明的讀者們一定也想到解法,在 ref 的時候拷貝一次不就行了?


UserCard.vue

<script setup lang="ts">
...
const user = ref(clone(store.user));

function clone<Data>(data: Data): Data {
return JSON.parse(JSON.stringify(data));
}

...
</script>
...

這時候會發現世界恢復和平了,資料一切正常!◝( •ω• )◟


鱈魚:「但是阿。(´● ω ●`)」

路人:「怎麼那麼多但是?…(›´ω`‹ )」


實際上協作開發的時候難保大家都會注意到這件事情,所以保險起見,可以在 Pinia 提供資料時先拷貝一次。


counter.ts

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

interface User {
name: string;
price: number;
}

function clone<Data>(data: Data): Data {
return JSON.parse(JSON.stringify(data));
}

export const useStore = defineStore('counter', () => {
const user = ref<User>({
name: 'cod',
price: 100,
})

function updateUser(data: Partial<User>) {
user.value = {
...user.value,
...data,
}
}

return {
user: computed(() => clone(user.value)),
updateUser,
}
})


這樣即使元件中使用

const user = ref(store.user);

也不會意外修改 Pinia 中的資料了!✧*。٩(ˊᗜˋ*)و✧*。


當然如果使用 immutable.js 這類保證資料不變性的套件也是沒問題。


就看大家喜歡哪一種了。♪( ◜ω◝و(و


以上程式可以來這裡取得:範例程式

留言
avatar-img
留言分享你的想法!
avatar-img
鱈魚的魚缸
17會員
14內容數
各種鱈魚滾鍵盤的雜紀
鱈魚的魚缸的其他內容
2024/07/25
先前提到 Quasar 的 Dialog Plugin 很好用,再讓我補充一個用法。
Thumbnail
2024/07/25
先前提到 Quasar 的 Dialog Plugin 很好用,再讓我補充一個用法。
Thumbnail
2024/07/15
Quasar Dialog 的 Invoking custom component 很好用,但是有些困擾的地方,一起來看看有甚麼辦法吧。
Thumbnail
2024/07/15
Quasar Dialog 的 Invoking custom component 很好用,但是有些困擾的地方,一起來看看有甚麼辦法吧。
Thumbnail
2024/07/10
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
2024/07/10
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
看更多
你可能也想看
Thumbnail
蝦皮分潤計畫讓我在分享旅遊文章時,也能透過推薦好物累積被動收入,貼補旅行基金。這篇文章,除了介紹計畫的操作亮點與心得,也分享我最常應用的案例:「旅行必備小物 TOP5」,包含行李鎖、免洗內衣褲、分裝瓶、折疊衣架與真空壓縮袋,幫助出國打包更輕鬆。想同時記錄旅行、分享好物又創造額外收入的你,千萬別錯過!
Thumbnail
蝦皮分潤計畫讓我在分享旅遊文章時,也能透過推薦好物累積被動收入,貼補旅行基金。這篇文章,除了介紹計畫的操作亮點與心得,也分享我最常應用的案例:「旅行必備小物 TOP5」,包含行李鎖、免洗內衣褲、分裝瓶、折疊衣架與真空壓縮袋,幫助出國打包更輕鬆。想同時記錄旅行、分享好物又創造額外收入的你,千萬別錯過!
Thumbnail
想增加被動收入?加入蝦皮分潤計畫是輕鬆上手的好方法!本文提供完整教學,包含申請流程、賺取分潤技巧,以及實際使用心得分享,助你輕鬆獲得額外收入。
Thumbnail
想增加被動收入?加入蝦皮分潤計畫是輕鬆上手的好方法!本文提供完整教學,包含申請流程、賺取分潤技巧,以及實際使用心得分享,助你輕鬆獲得額外收入。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
Vue3 筆記,指令進階篇
Thumbnail
Vue3 筆記,指令進階篇
Thumbnail
Vue3 學習筆記,vue-router 篇。
Thumbnail
Vue3 學習筆記,vue-router 篇。
Thumbnail
Vue3 學習筆記,用 Pinia 管理資料與邏輯
Thumbnail
Vue3 學習筆記,用 Pinia 管理資料與邏輯
Thumbnail
Vue3 學習筆記,元件與資料傳遞篇
Thumbnail
Vue3 學習筆記,元件與資料傳遞篇
Thumbnail
前言 Vue 是一個現代開發框架,擁有完尚的生態系,讓我們可以將須多元件客製化,做出組件,並且可重複利用,高擴充性。在開發組件時,每個組件都擁有自己的生命周期,Vue 組件會偵測每個變數值,是否有變,並且更新內容,今天要一個一個了解 Vue 的生命週期,讓大家有更多認識。 Vue 的生命週期
Thumbnail
前言 Vue 是一個現代開發框架,擁有完尚的生態系,讓我們可以將須多元件客製化,做出組件,並且可重複利用,高擴充性。在開發組件時,每個組件都擁有自己的生命周期,Vue 組件會偵測每個變數值,是否有變,並且更新內容,今天要一個一個了解 Vue 的生命週期,讓大家有更多認識。 Vue 的生命週期
Thumbnail
Vue3 學習筆記,專案建立與基礎響應式篇
Thumbnail
Vue3 學習筆記,專案建立與基礎響應式篇
Thumbnail
幾天不用,剛剛發現之前的程式碼已經不能使用了,我想可能是因為html結構有所改變,之前的程式碼可以看下面這一篇文章 【程式碼教學】追蹤自己 Vocus文章每日流量,第二版 1027 更新後的程式碼 import csv from bs4 import BeautifulSoup
Thumbnail
幾天不用,剛剛發現之前的程式碼已經不能使用了,我想可能是因為html結構有所改變,之前的程式碼可以看下面這一篇文章 【程式碼教學】追蹤自己 Vocus文章每日流量,第二版 1027 更新後的程式碼 import csv from bs4 import BeautifulSoup
Thumbnail
程式碼第二版 1027 剛剛發現之前的程式碼已經不能使用了,我想可能是因為html結構有所改變,另外也想順便處理一下數字如果是含有"K"的數字時,順便轉化一下,時間有限,所以想知道來龍去脈請看下面文章。 【教學】如何用程式碼追蹤Vocus文章每日流量?第一版 【教學】如何用程式碼追蹤V
Thumbnail
程式碼第二版 1027 剛剛發現之前的程式碼已經不能使用了,我想可能是因為html結構有所改變,另外也想順便處理一下數字如果是含有"K"的數字時,順便轉化一下,時間有限,所以想知道來龍去脈請看下面文章。 【教學】如何用程式碼追蹤Vocus文章每日流量?第一版 【教學】如何用程式碼追蹤V
Thumbnail
我們在「【💎Python 軍火庫 - devpi】pip install…等太久了嗎🤔? 您需要來點緩存機制」有介紹過pypi套件緩存的架設方式, 那架設好了之後, 我們在下載的部份會有一層快取及代理的前哨站, 但假如我們的套件不在pypi平台時怎麼辦呢? 就像torch的套件就必須仰賴外部的來
Thumbnail
我們在「【💎Python 軍火庫 - devpi】pip install…等太久了嗎🤔? 您需要來點緩存機制」有介紹過pypi套件緩存的架設方式, 那架設好了之後, 我們在下載的部份會有一層快取及代理的前哨站, 但假如我們的套件不在pypi平台時怎麼辦呢? 就像torch的套件就必須仰賴外部的來
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News