Home > Article > Web Front-end > Understanding Vue Reactivity with Pinia Stores
At my workplace I was being tasked with creating a mock chat store for internal local dev work, and while doing so I made few notes about Vue (I had some experience, but not with hooks), So this is just my obsidian notes, I hope its useful to you :)
ref is Vue's way of making primitive values reactive. It wraps the value in a reactive object with a .value property.
import { ref } from 'vue' // Inside Pinia Store export const useMyStore = defineStore('my-store', () => { // Creates a reactive reference const count = ref<number>(0) // To access or modify: function increment() { count.value++ // Need .value for refs } return { count, // When exposed, components can use it without .value increment } })
// Simple ref const isLoading = ref<boolean>(false) // Array ref const messages = ref<Message[]>([]) // Complex object ref const currentUser = ref<User | null>(null) // Ref with undefined const selectedId = ref<string | undefined>(undefined)
import { watch, ref } from 'vue' export const useMyStore = defineStore('my-store', () => { const messages = ref<Message[]>([]) // Simple watch watch(messages, (newMessages, oldMessages) => { console.log('Messages changed:', newMessages) }) })
// Immediate execution watch(messages, (newMessages) => { // This runs immediately and on changes }, { immediate: true }) // Deep watching watch(messages, (newMessages) => { // Detects deep object changes }, { deep: true }) // Multiple sources watch( [messages, selectedId], ([newMessages, newId], [oldMessages, oldId]) => { // Triggers when either changes } )
export const useMyStore = defineStore('my-store', () => { // State const items = ref<Item[]>([]) const isLoading = ref(false) const error = ref<Error | null>(null) // Computed const itemCount = computed(() => items.value.length) // Actions const fetchItems = async () => { isLoading.value = true try { items.value = await api.getItems() } catch (e) { error.value = e as Error } finally { isLoading.value = false } } return { items, isLoading, error, itemCount, fetchItems } })
export const useMainStore = defineStore('main-store', () => { // Using another store const otherStore = useOtherStore() // Watching other store's state watch( () => otherStore.someState, (newValue) => { // React to other store's changes } ) })
export const useChatStore = defineStore('chat-store', () => { const messages = ref<Message[]>([]) const refreshInterval = ref<number | null>(null) const isRefreshing = ref(false) // Watch for auto-refresh state watch(isRefreshing, (shouldRefresh) => { if (shouldRefresh) { startAutoRefresh() } else { stopAutoRefresh() } }) const startAutoRefresh = () => { refreshInterval.value = window.setInterval(() => { fetchNewMessages() }, 5000) } const stopAutoRefresh = () => { if (refreshInterval.value) { clearInterval(refreshInterval.value) refreshInterval.value = null } } return { messages, isRefreshing, startAutoRefresh, stopAutoRefresh } })
export const useDataStore = defineStore('data-store', () => { const data = ref<Data[]>([]) const isLoading = ref(false) const error = ref<Error | null>(null) // Watch loading state for side effects watch(isLoading, (loading) => { if (loading) { // Show loading indicator } else { // Hide loading indicator } }) // Watch for errors watch(error, (newError) => { if (newError) { // Handle error (show notification, etc.) } }) })
// ❌ Bad const data = ref() // Type is 'any' // ✅ Good const data = ref<string[]>([]) // Explicitly typed
// ❌ Bad - No cleanup watch(source, () => { const timer = setInterval(() => {}, 1000) }) // ✅ Good - With cleanup watch(source, () => { const timer = setInterval(() => {}, 1000) return () => clearInterval(timer) // Cleanup function })
// ❌ Bad - Using watch for derived state watch(items, (newItems) => { itemCount.value = newItems.length }) // ✅ Good - Using computed for derived state const itemCount = computed(() => items.value.length)
// ✅ Good store organization export const useStore = defineStore('store', () => { // State refs const data = ref<Data[]>([]) const isLoading = ref(false) // Computed properties const isEmpty = computed(() => data.value.length === 0) // Watchers watch(data, () => { // Handle data changes }) // Actions const fetchData = async () => { // Implementation } // Return public interface return { data, isLoading, isEmpty, fetchData } })
// ❌ Bad const count = ref(0) count++ // Won't work // ✅ Good count.value++
// ❌ Bad - Might miss initial state watch(source, () => {}) // ✅ Good - Catches initial state watch(source, () => {}, { immediate: true })
// ❌ Bad - No cleanup const store = useStore() setInterval(() => { store.refresh() }, 1000) // ✅ Good - With cleanup const intervalId = setInterval(() => { store.refresh() }, 1000) onBeforeUnmount(() => clearInterval(intervalId))
Remember: Always consider cleanup, type safety, and proper organization when working with refs and watches in Pinia stores
The above is the detailed content of Understanding Vue Reactivity with Pinia Stores. For more information, please follow other related articles on the PHP Chinese website!