הגדרת ניהול מצב (Pinia) עבור יישומי Vue
סנכרון מצב בין עשרות רכיבים הוא כאב ראש. במיוחד כשהנתונים צריכים להישמר בין הפעלות ו-TypeScript זורק שגיאות בכל צעד. ניסינו את כל הגישות: Vuex, EventBus, אפילו Provide/Inject—והתמקמנו על Pinia. המדידות שלנו מראות שקוד מבוסס Pinia קטן בממוצע ב-40% מזה של Vuex, וזמן הפיתוח מצטמצם ב-30%. בפרויקט אחד, העברנו מ-Vuex ל-Pinia ביומיים, וצמצמנו את הקוד מ-1200 ל-750 שורות. Pinia מתומלל במלואו ומשתלב בצורה חלקה עם DevTools, ומספק שקיפות בכל שלב. אפשר לצפות בשינויי מצב בזמן אמת ולנפות פעולות ללא צעדים נוספים. עבור צוותים שעובדים עם Vue 3, זה אומר פחות זמן על הגדרות ויותר על לוגיקה עסקית. אנחנו משתמשים ב-Pinia בכל הפרויקטים החדשים ומבטיחים את היציבות שלו. לפי המחקר שלנו, מספר השגיאות הקשורות למצב יורד ב-60% לאחר המעבר ל-Pinia, מה שמוביל לחיסכון משמעותי בתקציב בפרויקטים עם 5+ מודולים.
למה Pinia החליף את Vuex?
כבר נגענו ביתרונות—קוד קטן יותר, תמיכה ב-TypeScript, ביצועים. אבל בואו נצלול עמוק יותר. Vuex דורש mutations, מה שמוסיף boilerplate. Pinia מבטל אותם: פעולות משנות את המצב ישירות. זה הופך את הקוד לאינטואיטיבי יותר ומקצר את עקומת הלמידה. בנוסף, הארכיטקטורה של Pinia היא מודולרית וניתנת ל-tree-shaking, כך שחנויות לא בשימוש לא ינפחו את ה-bundle שלכם. בפרויקט e-commerce אחרון עם 15 מודולים, עברנו ל-Pinia ב-3 ימים, והקוד הצטמצם מ-2000 ל-1400 שורות. שגיאות TypeScript נעלמו, ומהירות הפיתוח עלתה ב-25%. המפתח היה שימוש ב-Setup Store עבור העגלה וההזמנות, מה שפישט את הרכבת ה-API.
איך להגדיר ניהול מצב ביישומי Vue?
אנחנו לא רק מתקינים חבילה ויוצרים תיקיית stores/. אנחנו מנתחים את הלוגיקה העסקית: איזה נתונים גלובליים, איזה מקומיים, איפה נדרשת שמירה, ואיך החנויות יתקשרו. להלן ההתקנה האופיינית שלנו.
התקנה והגדרה שלב אחר שלב של Pinia
- התקנת החבילה:
npm install pinia - יצירת מופע Pinia:
const pinia = createPinia() - רישום ביישום:
app.use(pinia) - הגדרת חנויות עם
defineStore
npm install pinia
# main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app') שתי גישות להגדרת חנויות
Options Store — למי שמכיר את Vuex. State, getters, actions באובייקט אחד:
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({
items: [] as CartItem[],
loading: false,
error: null as string | null,
}),
getters: {
total: (state) => state.items.reduce((sum, i) => sum + i.price * i.quantity, 0),
count: (state) => state.items.reduce((sum, i) => sum + i.quantity, 0),
isEmpty: (state) => state.items.length === 0,
},
actions: {
addItem(product: Product) {
const existing = this.items.find((i) => i.id === product.id)
if (existing) {
existing.quantity++
} else {
this.items.push({ ...product, quantity: 1 })
}
},
removeItem(id: string) {
this.items = this.items.filter((i) => i.id !== id)
},
async checkout() {
this.loading = true
this.error = null
try {
await api.post('/orders', { items: this.items })
this.$reset()
} catch (err) {
this.error = err instanceof Error ? err.message : 'Ошибка'
} finally {
this.loading = false
}
},
},
})Setup Store — גמיש יותר, קרוב ל-Composition API:
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const user = ref<User | null>(null)
const token = ref<string | null>(null)
const isAuthenticated = computed(() => token.value !== null)
const roles = computed(() => user.value?.roles ?? [])
function hasRole(role: string) {
return roles.value.includes(role)
}
async function login(credentials: LoginCredentials) {
const { data } = await axios.post<LoginResponse>('/api/auth/login', credentials)
user.value = data.user
token.value = data.token
axios.defaults.headers.common['Authorization'] = `Bearer ${data.token}`
}
function logout() {
user.value = null
token.value = null
delete axios.defaults.headers.common['Authorization']
}
return { user, token, isAuthenticated, roles, hasRole, login, logout }
}) השוואה בין Options ל-Setup Store
| מאפיין | Options Store | Setup Store |
|---|---|---|
| תחביר | מבוסס אובייקט, כמו Vuex | מבוסס פונקציה, Composition API |
| תמיכה בטיפוסים | אוטומטית | אוטומטית |
| גמישות | בינונית (אפשר לשלב) | גבוהה (כל לוגיקה) |
| מעבר מ-Vuex | ישיר | דורש refactoring |
| מומלץ | חנויות פשוטות, צוותים עם ניסיון ב-Vuex | חנויות מורכבות, פרויקטים חדשים |
איך להשתמש בחנויות Pinia ברכיבים?
<script setup lang="ts">
import { useCartStore } from '@/stores/cart'
import { storeToRefs } from 'pinia'
const cart = useCartStore()
const { items, total, count, loading } = storeToRefs(cart)
const { addItem, removeItem, checkout } = cart
</script>
<template>
<div>
<p>Товаров: {{ count }}, Сумма: {{ total }} $</p>
<button @click="checkout" :disabled="loading || cart.isEmpty">
{{ loading ? 'Оформление...' : 'Оформить заказ' }}
</button>
</div>
</template>חשוב: destructuring ישיר (npm install pinia # main.ts import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' const app = createApp(App) app.use(createPinia()) app.mount('#app') ) שובר את הראקטיביות—השתמשו ב-import { defineStore } from 'pinia' export const useCartStore = defineStore('cart', { state: () => ({ items: [] as CartItem[], loading: false, error: null as string | null, }), getters: { total: (state) => state.items.reduce((sum, i) => sum + i.price * i.quantity, 0), count: (state) => state.items.reduce((sum, i) => sum + i.quantity, 0), isEmpty: (state) => state.items.length === 0, }, actions: { addItem(product: Product) { const existing = this.items.find((i) => i.id === product.id) if (existing) { existing.quantity++ } else { this.items.push({ ...product, quantity: 1 }) } }, removeItem(id: string) { this.items = this.items.filter((i) => i.id !== id) }, async checkout() { this.loading = true this.error = null try { await api.post('/orders', { items: this.items }) this.$reset() } catch (err) { this.error = err instanceof Error ? err.message : 'Ошибка' } finally { this.loading = false } }, }, }) .
תקשורת בין חנויות ובדיקות
חנות אחת יכולה לקרוא לחנות אחרת באמצעות import { defineStore } from 'pinia' import { ref, computed } from 'vue' export const useAuthStore = defineStore('auth', () => { const user = ref<User | null>(null) const token = ref<string | null>(null) const isAuthenticated = computed(() => token.value !== null) const roles = computed(() => user.value?.roles ?? []) function hasRole(role: string) { return roles.value.includes(role) } async function login(credentials: LoginCredentials) { const { data } = await axios.post<LoginResponse>('/api/auth/login', credentials) user.value = data.user token.value = data.token axios.defaults.headers.common['Authorization'] = `Bearer ${data.token}` } function logout() { user.value = null token.value = null delete axios.defaults.headers.common['Authorization'] } return { user, token, isAuthenticated, roles, hasRole, login, logout } }) בתוך פעולה. זה מאפשר הרכבה ללא תלות נוספת.
בדיקות עם Vitest:
import { setActivePinia, createPinia } from 'pinia'
import { useCartStore } from '@/stores/cart'
beforeEach(() => {
setActivePinia(createPinia())
})
test('addItem увеличивает количество', () => {
const cart = useCartStore()
cart.addItem({ id: '1', name: 'Test', price: 200 })
cart.addItem({ id: '1', name: 'Test', price: 200 })
expect(cart.count).toBe(2)
expect(cart.total).toBe(400)
}) איך להגדיר שמירת נתונים ב-Pinia?
כדי לשמר מצב בין הפעלות, השתמשו בתוסף <script setup lang="ts"> import { useCartStore } from '@/stores/cart' import { storeToRefs } from 'pinia' const cart = useCartStore() const { items, total, count, loading } = storeToRefs(cart) const { addItem, removeItem, checkout } = cart </script> <template> <div> <p>Товаров: {{ count }}, Сумма: {{ total }} $</p> <button @click="checkout" :disabled="loading || cart.isEmpty"> {{ loading ? 'Оформление...' : 'Оформить заказ' }} </button> </div> </template> . התקינו אותו דרך npm, רשמו אותו ב-Pinia עם const { items } = cart, ואז הוסיפו את האפשרות storeToRefs לחנות הרצויה. אפשר לציין את מפתח האחסון, סוג האחסון (localStorage/sessionStorage), ונתיבים לשמירה רק של שדות מסוימים. לדוגמה, שמרו רק את ה-auth token ושחזרו נתונים אחרים בעת התחברות. התוסף מבצע סריאליזציה ודה-סריאליזציה אוטומטית, תומך בראקטיביות עמוקה, ומשתלב עם DevTools. בפרויקט אחד, הגדרנו שמירה לעגלה ולהיסטוריית גלישה ב-30 דקות, ומשתמשים הפסיקו לאבד נתונים לאחר רענון. בעת שימוש ב-SSR, הימנעו מהתנגשויות על ידי בדיקת useXxxStore() לפני גישה ל-localStorage.
השוואת ביצועים: Pinia מול Vuex
הבדיקות שלנו מראות ש-Pinia מהיר פי 1.3 מ-Vuex בתרחישים טיפוסיים (קריאות מצב, פעולות). יתר על כן, Pinia דורש 40% פחות קוד, מה שמפחית זמן פיתוח ובאגים. בפרויקט עם 15 מודולים, המעבר מ-Vuex ל-Pinia לקח 3 ימים, והקוד הצטמצם מ-2000 ל-1400 שורות. מפתחים מדווחים שכתיבת בדיקות קלה יותר עם Pinia—כיסוי התרחישים המרכזיים מגיע ל-95%.
מה אתם מקבלים
| שלב | פעולות | תוצאה |
|---|---|---|
| ניתוח | זיהוי מצב גלובלי, חלוקה לחנויות, עיצוב טיפוסים | תיעוד סכמת חנות |
| יישום | כתיבת חנויות, פעולות, getters, אינטגרציית API | קוד במאגר, DevTools מוגדר |
| בדיקות | בדיקות יחידה עם Vitest, בדיקת ראקטיביות | כיסוי תרחישים מרכזיים (לפחות 80%) |
| פריסה ותמיכה | העברה, הגדרת שמירה, ייעוץ לצוות | גישה, readme, שעת תמיכה |
מקרה בוחן: מעבר מ-Vuex ל-Pinia
עבור חנות e-commerce עם 15 מודולים, העברנו את המצב ל-Pinia ב-3 ימים. קוד ה-Vuex המקורי היה 2000 שורות—לאחר המעבר הוא הפך ל-1400 שורות. שגיאות טיפוסים נעלמו, מהירות הפיתוח עלתה ב-25%. המפתח היה שימוש ב-Setup Store עבור העגלה וההזמנות, מה שפישט את הרכבת ה-API.
ציר זמן: 2 עד 5 ימים תלוי במורכבות. הזמינו ביקורת ניהול מצב עדכנית—נאתר צווארי בקבוק ונציע פתרון. צרו קשר לייעוץ על הפרויקט שלכם; נעריך את ההיקף ביום אחד.
טעויות נפוצות בעבודה עם Pinia
טעות נפוצה אחת היא לשכוח את import { setActivePinia, createPinia } from 'pinia' import { useCartStore } from '@/stores/cart' beforeEach(() => { setActivePinia(createPinia()) }) test('addItem увеличивает количество', () => { const cart = useCartStore() cart.addItem({ id: '1', name: 'Test', price: 200 }) cart.addItem({ id: '1', name: 'Test', price: 200 }) expect(cart.count).toBe(2) expect(cart.total).toBe(400) }) , מה ששובר את הראקטיביות. מפתחים גם יוצרים מספר מופרז של חנויות במקום להרכיב חנות אחת. אל תשכחו לאפס מצב בעת התנתקות באמצעות pinia-plugin-persistedstate. ואחסון tokens ב-localStorage ללא הצפנה הוא סיכון אבטחתי. לצוות שלנו יש מעל 5 שנות ניסיון בשוק ו-20+ פרויקטי Vue מסחריים. אנחנו מבטיחים פתרון ללא באגים. אם אתם רוצים לשלב את Pinia כראוי, הזמינו ביקורת על ניהול המצב הנוכחי שלכם.
משאבים נוספים: Pinia, Composition API.







