Pinia Composition API(defineStore,storeToRefs)

安裝pinia

1
npm install --save pinia

在main.ts引入pinia

1
2
3
4
5
6
7
8

import { createPinia } from 'pinia'

const pinia = createPinia()

const app = createApp(App)
app.use(pinia)
app.mount('#app')

Vite Ssr 專案 main.ts引入方法

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createSSRApp } from 'vue'
+ import { createPinia } from 'pinia'
import App from './App.vue'
+ const pinia = createPinia()

// SSR requires a fresh app instance per request, therefore we export a function
// that creates a fresh app instance. If using Vuex, we'd also be creating a
// fresh store here.
export function createApp() {
const app = createSSRApp(App);
+ app.use(pinia)
return { app }
}

官方寫法:

stores/counter.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//引入defineStore
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
//state如同data
state: () => {
return {
count: 0,
cardLists:[],
forms: {
email: '',
password:'',
}
}
},
//如同computed
getters: {
doubleCount: (state) => state.count * 2,
},
//如同method=>

actions: {
addCount() {
this.count++
},
async fetchApi() {
try {
const api = 'https://api.coindesk.com/v1/bpi/currentprice.json';
/* let headers = {
"Content-Type": "application/json",
"Accept": "application/json",
// Authentication: 'secret'
// "Authorization": `Bearer ${token}`,
}*/
const res = await fetch(api, { headers: headers, method: "GET" });
// this方法
this.cardLists = await res.json();
console.log('lists.value', this.cardLists)

} catch (error) {
console.log(error)
}

},
async loginIn () {
try {

const api = `${import.meta.env.VITE_API_DEFAULT_URL}/auth/login`

let headers = {
"Content-Type": "application/json",
"Accept": "application/json",
// "Authorization": `Bearer ${token}`,
}
//以下是API文件中提及必寫的主體参數
let body = {
email: this.forms.email,
password:this.forms.password,
}
fetch(api, {
method: "POST",
headers: headers,
body: JSON.stringify(body)//****重點 */
})
.then(res => res.json())
.then(json => {
console.log('login',json)
});
}
catch (error) {
console.log(error)
}
}
},
})

在頁面使用

  • 引入counter的useCounterStore
  • 宣告useCounterStore()

引入

宣告

解構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<button @click="addCount" class="add_btn">add count</button>
<div>{{counter.count}}</div>
{{ counter.cardLists }}
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
//引入Store
import { useCounterStore } from '../stores/counter'
//宣告
const counter = useCounterStore();
const { addCount,fetchApi } =counter;
onMounted(() => {
fetchApi();
})
</script>

pinia 選項寫法 gitHub
pinia 選項寫法 gitHub

改為 Composition API

*重點:
1.引入vue Composition API
2.改為Function 寫法與Composition API
3.必須 return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { defineStore } from 'pinia';
//引入vue Composition API
import { ref,computed } from 'vue';
//改為Function 寫法與Composition API
export const useCounterStore = defineStore('counter', ()=>
{
const count = ref(0);

//computed
const doubleCount = computed(() =>
{
return count.value * 2
})

const addCount = () =>
{
count.value ++
}
const cardLists=ref([])
const fetchApi = async() =>
{
try {
const res = await axios.get('https://api.coindesk.com/v1/bpi/currentprice.json');
cardLists.value = res.data;
} catch (error) {
console.log(error)
}

}
//必須輸出 return
return {
count,addCount,doubleCount,
cardLists,fetchApi
}
})

在頁面使用

  • 引入counter的useCounterStore
  • 宣告useCounterStore()

html 加入 store.命名

使用 store action時 store.addCount()

*重點:
1.store.名稱
2.store.function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<p>{{store.count}}</p>
<pre>
{{ store.cardLists }}
</pre>
<button @click="clickAdd">Add</button>
<button @click="store.fetchApi">fetchApi</button>
</div>
</template>

<script setup lang="ts">
//引入Store
import { useCounterStore } from '@/stores/counter.js';

//宣告store = useCounterStore()
const store = useCounterStore();

const clickAdd = () => {
//使用 store action時 store.addCount()
store.addCount()
}
</script>

counter pinia寫法引入Store 方式寫法

解構方式引入

1.引入 import {storeToRefs } from ‘pinia’
2.//解構store,數據取出counter, cardLists
const { counter, cardLists } = storeToRefs(store);
3.function 解構 const { fetchApi } = store;

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div>
<button @click="clickAdd">Add</button>
<h1>{{ counter }}</h1>

<div> {{ cardLists }}</div>
<pre>
{{ cardLists }}
</pre>
</div>
</template>

<script setup lang="ts">
import { ref,onMounted } from 'vue';

//引入 import {storeToRefs } from 'pinia'
import {storeToRefs } from 'pinia';
// @ts-ignore
import { useCounterStore } from '../stores/counter.js';
//宣告store = useCounterStore()
const store = useCounterStore();
//解構store,數據取出counter, cardLists
const { counter, cardLists } = storeToRefs(store);
const { fetchApi, addCount } = store;

const clickAdd = () => {
addCount()
}
onMounted(() => {
fetchApi();
});
</script>


Pinia Composition API

範例

stores/user.ts

  • acceptHMRUpdatePinia 支援熱模組替換,因此您可以直接在您的應用程式中編輯您的商店並與它們進行交互,而無需重新加載頁面,從而允許您保留現有狀態、添加甚至刪除狀態、操作和 getter。
  • 使用$patch修改state 數據
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// @ts-check 引入pinia的defineStore與acceptHMRUpdate
import { defineStore, acceptHMRUpdate } from 'pinia'

/**模擬登入*/
function apiLogin(a: string, p: string) {
if (a === 'ed' && p === 'ed') return Promise.resolve({ isAdmin: true })
if (p === 'ed') return Promise.resolve({ isAdmin: false })
return Promise.reject(new Error('invalid credentials'))
}

export const useUserStore = defineStore({
id: 'user',
state: () => ({
name: 'Eduardo',
isAdmin: true,
}),

actions: {
logout() {
// 登出以後 =>使用$patch修改state 數據
this.$patch({
name: '',
isAdmin: false,
})

// 我們可以做其他事情,例如重定向用戶
},

/**
* 嘗試登入用戶
*/
async login(user: string, password: string) {
const userData = await apiLogin(user, password)
// 登入以後 =>使用$patch修改state 數據
this.$patch({
name: user,
...userData,

})
console.log(userData,)
},
},
})

if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useUserStore, import.meta.hot))
}


stores/cart.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// @ts-check 引入pinia的defineStore與acceptHMRUpdate
import { defineStore, acceptHMRUpdate } from 'pinia'
//引入useUserStore
import { useUserStore } from './user'

export const useCartStore = defineStore({
id: 'cart',
state: () => ({
rawItems: [] as string[],
}),
getters: {
items: (state): Array<{ name: string; amount: number }> =>
//https://medium.com/@davelin18yufan/js%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3reduce%E7%9A%84%E7%94%A8%E6%B3%95-95738c3f9114
state.rawItems.reduce((items, item) => {
//尋找
const existingItem = items.find((it) => it.name === item)
//console.log('現有項目existingItem',existingItem)
if (!existingItem) {
//console.log('19',existingItem)
items.push({ name: item, amount: 1 })
} else {
//console.log('22',existingItem.amount++)
existingItem.amount++
}
//console.log('items',items)
return items
}, [] as Array<{ name: string; amount: number }>),
},
actions: {
//新增Item
addItem(name: string) {
this.rawItems.push(name)
},
//刪除
removeItem(name: string) {
//lastIndexOf搜尋最後一次出現的
//https://www.w3schools.com/jsref/jsref_lastindexof.asp
const i = this.rawItems.lastIndexOf(name)
//splice() 方法可以藉由刪除既有元素並
if (i > -1) this.rawItems.splice(i, 1)
},

async purchaseItems() {
const user = useUserStore()
if (!user.name) return

console.log('購買', this.items)
const n = this.items.length
this.rawItems = []

return n
},
},
})

if (import.meta.hot) {
console.log('import.meta.hot', import.meta.hot);
import.meta.hot.accept(acceptHMRUpdate(useCartStore, import.meta.hot))
}

頁面引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<script setup lang="ts">
//引入Store
import { useCartStore } from '@/stores/cart'
import { useUserStore } from '@/stores/user'
//宣告
const cart = useCartStore();
const user = useUserStore();
const itemName = ref<string>('');

// 新增Item到購物車
const addItemToCart =()=>{
if (!itemName.value) return
cart.addItem(itemName.value)
itemName.value = ''
}
// 清空購物車
const clearCart =()=>{
if (window.confirm('您確定要清空購物車嗎?')) {
cart.rawItems = []
}
}
// 購買
const buy =async()=>{
const n = await cart.purchaseItems()
console.log(`買 ${n} items`)
cart.rawItems = []
}
</script>

<template>
<div>
<input type="text" v-model="itemName" />
<button class="add_btn" @click="addItemToCart">Add</button>
<!--新增的Item-->
<ul>
<li v-for="item in cart.items" :key="item">
{{item.name}}
<!--刪除的按鈕-->
<button class="delete_btn" @click="cart.removeItem(item.name)"
type="button">X</button>
</li>
</ul>
<!--購買按鈕-->
<button class="buy_btn" :disabled="!user.name">Buy</button>
<!--清除購物車按鈕-->
<button
class="delete_btn"
:disabled="!cart.items.length"
@click="clearCart"
type="button"
data-testid="clear"
>Clear the cart</button>
</div>

</template>

github

改Composition API寫法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// @ts-check 引入pinia的defineStore與acceptHMRUpdate
import { defineStore, acceptHMRUpdate } from 'pinia'
//引入vue Composition API
import { ref,computed } from 'vue';
//引入useUserStore
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', () => {

const rawItems = ref<any[]>([]);
const items = computed(() =>{
rawItems.value.reduce((items: any[], item: any) => {
const existingItem = items.find((i:any) => i.name === item)
if (!existingItem) {
items.push({ name: item, amount: 1 })
} else {

existingItem.amount++
}
return items
},[] as Array<{ }>)
})

const addItem = (name: string) => {
console.log('新增的input 數據:', name);
rawItems.value.push(name)
}
const removeItem = (name: string) => {
console.log('刪除:',name)
const i = rawItems.value.indexOf(name)
//splice() 方法可以藉由刪除既有元素並
if (i > -1) rawItems.value.splice(i, 1)
}
const purchaseItems = () => {
const user = useUserStore()
if (!user.name) return

console.log('購買', items)
// const n = items.length
rawItems.value = []

//return n
}
return {
rawItems,items,addItem ,removeItem,purchaseItems
}
})

頁面使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<template>
<div>
{{title}}
<input type="text" v-model="itemName" />
<button class="add_btn" @click="addItemToCart">Add</button>
<ul>
<li v-for="item in items" :key="item" v-show="items.length!=0">
{{item.name}}
<button class="delete_btn" @click="removeItem(item.name)"
type="button">X</button>
</li>
</ul>
<button class="buy_btn"
@click="buy"
:disabled="!name">Buy</button>
<button
class="delete_btn"
@click="clearCart"
type="button"
data-testid="clear"
>Clear the cart</button>
</div>
</template>

<route>
{
path: '/aboutus',
name: 'AboutUs',
meta: {
layout: 'frontLayout',
}
}
</route>

<script setup lang="ts">
//引入pinia
import {storeToRefs} from 'pinia';
import {ref,onMounted} from 'vue'

//引入Store
import { useCartStore } from '../stores/cartTest'
import { useUserStore } from '../stores/userTest'
const title =ref<string>('關於我們');
//宣告
const cart = useCartStore();
const user = useUserStore();
//解構store
const { rawItems,items } = storeToRefs(cart)
const { addItem ,removeItem,purchaseItems } = cart;
const { name,isAdmin } = storeToRefs(user);
// const { logout,login } = user;

const itemName = ref<string>('');
const addItemToCart =()=>{
if (!itemName.value)
return
cart.addItem(itemName.value)
itemName.value = ''

}
const clearCart =()=>{
if (window.confirm('您確定要清空購物車嗎?')) {
rawItems.value = []
}
}
const buy =async()=>{
const n = await purchaseItems()
console.log(`買 ${n} items`)
rawItems.value = []
}
onMounted(() => {
// getItems()
});
</script>

組合式Api寫法