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

閱讀時間約 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 這類保證資料不變性的套件也是沒問題。


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


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

17會員
14Content count
各種鱈魚滾鍵盤的雜紀
留言0
查看全部
發表第一個留言支持創作者!
鱈魚的魚缸 的其他內容
到底要用 ref 還是 reactive 是一個很常見的問題,不過現在官方文檔推薦使用 ref 就行,所以也不是甚麼大問題就是了。( •̀ ω •́ )✧ 所以 reactive 真的沒用用途了嗎?這篇文章來記錄一下 reactive 的用法。
watch 不是不能用,而是在使用 watch 之前,先想想有沒有其他方案,真的沒有才用 watch。 千萬不要為了一時方便讓元件裡滿滿的 watch,因為容易產生難以追蹤的副作用,會讓資料流更加複雜。
如果 watch 沒有放在元件最外層,可能會導致元件 onUnmounted 後watch不會自動解除,至於該怎麼辦,就讓我們娓娓道來。( ´ ▽ ` )ノ
大家好,我是鱈魚。(^∀^●)ノシ Vue 3.3 終於新增了泛型元件(Generic Component),這讓我們可以在 TypeScript 環境中得到更準確的型別提示。( •̀ ω •́ )✧ 讓我們一起來看看吧!
大家好,我是鱈魚。(。・∀・)ノ゙ 最近看到大家討論取得 API 時機,有許多人都提到「一定要」或者「習慣」,在 onMounted 這個 hook 內呼叫 API 取得資料。 其實這也沒不好,但是也沒什麼好處就是了。(。・ω・。)
到底要用 ref 還是 reactive 是一個很常見的問題,不過現在官方文檔推薦使用 ref 就行,所以也不是甚麼大問題就是了。( •̀ ω •́ )✧ 所以 reactive 真的沒用用途了嗎?這篇文章來記錄一下 reactive 的用法。
watch 不是不能用,而是在使用 watch 之前,先想想有沒有其他方案,真的沒有才用 watch。 千萬不要為了一時方便讓元件裡滿滿的 watch,因為容易產生難以追蹤的副作用,會讓資料流更加複雜。
如果 watch 沒有放在元件最外層,可能會導致元件 onUnmounted 後watch不會自動解除,至於該怎麼辦,就讓我們娓娓道來。( ´ ▽ ` )ノ
大家好,我是鱈魚。(^∀^●)ノシ Vue 3.3 終於新增了泛型元件(Generic Component),這讓我們可以在 TypeScript 環境中得到更準確的型別提示。( •̀ ω •́ )✧ 讓我們一起來看看吧!
大家好,我是鱈魚。(。・∀・)ノ゙ 最近看到大家討論取得 API 時機,有許多人都提到「一定要」或者「習慣」,在 onMounted 這個 hook 內呼叫 API 取得資料。 其實這也沒不好,但是也沒什麼好處就是了。(。・ω・。)
你可能也想看
Thumbnail
重點摘要: 1.9 月降息 2 碼、進一步暗示年內還有 50 bp 降息 2.SEP 上修失業率預期,但快速的降息速率將有助失業率觸頂 3.未來幾個月經濟數據將繼續轉弱,經濟復甦的時點或是 1Q25 季底附近
Thumbnail
近期的「貼文發佈流程 & 版型大更新」功能大家使用了嗎? 新版式整體視覺上「更加凸顯圖片」,為了搭配這次的更新,我們推出首次貼文策展 ❤️ 使用貼文功能並完成這次的指定任務,還有機會獲得富士即可拍,讓你的美好回憶都可以用即可拍珍藏!
Thumbnail
週末兩天連綿的雨,掩飾了小偷的行蹤,周一澆花的時候發現整整齊齊被砍頭的黃金萬年草,看到她禿頭的剎那,一秒入戲,我左右張望了一下,想是哪位路過貪小便宜的路人摘了我家的菜。但是隨即清醒,這是四樓陽台啊! 上個月多肉石頭玉跟玉綴被鳥啄了,蝴蝶蘭的葉子也被切葉蜂切得坑坑巴巴,這
Thumbnail
我們明明工作了很久,為什麼覺得自己存不了錢呢?到底錢都花在哪裡呢?我們可以安穩工作到退休嗎?退休能夠不為錢所困擾嗎?以上問題也許都曾是我們心中疑問,但是藉著這本書的六個主題,也許可以提供給我們一個檢視的方向,比較正確,且適合自己的方式,去累積自己的資產,讓自己擁有富足的生活!
Thumbnail
賺了一輩子的錢,卻存不到養老金。 錢怎麼賺,知道;怎麼流失,不知道。 這本書是吳淡如,淡如姐所寫的,他們念的蠻多個商學院,也很努力不斷積極的進修自己,那投資自己很多也想把這些經驗分享出來,所以才有podcast和書的問世。 有fire學姐分享要踩在巨人的肩膀上,那閱讀有經驗成功人士的書籍
Thumbnail
吳淡如本人因為自己過去不會理財的慘痛經驗 後來去又去念了商學院 把自己所學分享出來 而且是用很簡單的說法讓大家瞬間理解
Thumbnail
青蛙先生一直以來都有失眠的問題,這天,清爽的夜晚,青蛙先生工作到了深夜,眼皮幾乎隨時都要垂下來似的,所以他慢慢地走到家。「嗯?我的床!!」他最心愛的「荷花床」不見了!!!他把一坨泥巴灑在臉上:「清醒點,清醒點!」,他火冒三丈的去找小偷。 他想:「嗯!光靠眼睛不好找!」,所以他拿出「超級放大鏡」,這樣
Thumbnail
請問有人看到我爸爸的內褲嗎?看到請通知我,感恩!
Thumbnail
這是一本必須要看兩次而且筆記下來的好書,想想我們用短短一本書的時間就能汲取作者多年的經驗與心法,真的是賺很大啊! (不劇透本書內容,誠摯推薦大家親自去閱讀)
Thumbnail
《誰偷了班克西?》是一部相當雜談且政治意味相當重的紀錄電影,片中以班克西在伯利恆的西岸地區的牆面留下驢子對以色列軍官出示通行證的塗鴉,引起許多政治爭議。而之後某天這面畫著驢子的牆卻被割下來準備出售給競標的藝術收藏家,這讓街頭藝術的公眾性和和過去藝術一樣淪為私人收藏兩個極端,開始了一系列論戰。
Thumbnail
與小說《偷書賊》同名之作,乘載著一個承重的歷史,書籍帶來的不只是知識,還包含著難以抹列的記憶與情感,在那大屠殺的年代中,如何建立起對人的仇恨,透過拼裝出來的舉證,讓人無法明瞭真相,何謂真?何謂假?並不是這麼容易分辨的事情,若是無法有足夠的資訊作為判斷依歸,人們依然是很容易被煽動。
Thumbnail
重點摘要: 1.9 月降息 2 碼、進一步暗示年內還有 50 bp 降息 2.SEP 上修失業率預期,但快速的降息速率將有助失業率觸頂 3.未來幾個月經濟數據將繼續轉弱,經濟復甦的時點或是 1Q25 季底附近
Thumbnail
近期的「貼文發佈流程 & 版型大更新」功能大家使用了嗎? 新版式整體視覺上「更加凸顯圖片」,為了搭配這次的更新,我們推出首次貼文策展 ❤️ 使用貼文功能並完成這次的指定任務,還有機會獲得富士即可拍,讓你的美好回憶都可以用即可拍珍藏!
Thumbnail
週末兩天連綿的雨,掩飾了小偷的行蹤,周一澆花的時候發現整整齊齊被砍頭的黃金萬年草,看到她禿頭的剎那,一秒入戲,我左右張望了一下,想是哪位路過貪小便宜的路人摘了我家的菜。但是隨即清醒,這是四樓陽台啊! 上個月多肉石頭玉跟玉綴被鳥啄了,蝴蝶蘭的葉子也被切葉蜂切得坑坑巴巴,這
Thumbnail
我們明明工作了很久,為什麼覺得自己存不了錢呢?到底錢都花在哪裡呢?我們可以安穩工作到退休嗎?退休能夠不為錢所困擾嗎?以上問題也許都曾是我們心中疑問,但是藉著這本書的六個主題,也許可以提供給我們一個檢視的方向,比較正確,且適合自己的方式,去累積自己的資產,讓自己擁有富足的生活!
Thumbnail
賺了一輩子的錢,卻存不到養老金。 錢怎麼賺,知道;怎麼流失,不知道。 這本書是吳淡如,淡如姐所寫的,他們念的蠻多個商學院,也很努力不斷積極的進修自己,那投資自己很多也想把這些經驗分享出來,所以才有podcast和書的問世。 有fire學姐分享要踩在巨人的肩膀上,那閱讀有經驗成功人士的書籍
Thumbnail
吳淡如本人因為自己過去不會理財的慘痛經驗 後來去又去念了商學院 把自己所學分享出來 而且是用很簡單的說法讓大家瞬間理解
Thumbnail
青蛙先生一直以來都有失眠的問題,這天,清爽的夜晚,青蛙先生工作到了深夜,眼皮幾乎隨時都要垂下來似的,所以他慢慢地走到家。「嗯?我的床!!」他最心愛的「荷花床」不見了!!!他把一坨泥巴灑在臉上:「清醒點,清醒點!」,他火冒三丈的去找小偷。 他想:「嗯!光靠眼睛不好找!」,所以他拿出「超級放大鏡」,這樣
Thumbnail
請問有人看到我爸爸的內褲嗎?看到請通知我,感恩!
Thumbnail
這是一本必須要看兩次而且筆記下來的好書,想想我們用短短一本書的時間就能汲取作者多年的經驗與心法,真的是賺很大啊! (不劇透本書內容,誠摯推薦大家親自去閱讀)
Thumbnail
《誰偷了班克西?》是一部相當雜談且政治意味相當重的紀錄電影,片中以班克西在伯利恆的西岸地區的牆面留下驢子對以色列軍官出示通行證的塗鴉,引起許多政治爭議。而之後某天這面畫著驢子的牆卻被割下來準備出售給競標的藝術收藏家,這讓街頭藝術的公眾性和和過去藝術一樣淪為私人收藏兩個極端,開始了一系列論戰。
Thumbnail
與小說《偷書賊》同名之作,乘載著一個承重的歷史,書籍帶來的不只是知識,還包含著難以抹列的記憶與情感,在那大屠殺的年代中,如何建立起對人的仇恨,透過拼裝出來的舉證,讓人無法明瞭真相,何謂真?何謂假?並不是這麼容易分辨的事情,若是無法有足夠的資訊作為判斷依歸,人們依然是很容易被煽動。