Sharing reactive state between components
Sharing Reactive State Between Components
Vue3's reactivity system is based on Proxy. The core of sharing state between components lies in managing the reference relationships of reactive data. When reactive
and ref
create reactive objects that reference the same object across different components, state changes are automatically synchronized. This sharing mechanism is implemented through Vue's dependency tracking and update triggering system.
// store.js
import { reactive } from 'vue'
export const store = reactive({
count: 0,
user: { name: 'Alice' }
})
Implementation Principles of Shared State
Vue3's reactivity system intercepts object operations through Proxy. When a component accesses reactive properties, dependency relationships are established. The key to shared state lies in:
- All components reference the same Proxy object.
- Property access triggers
track
operations to record dependencies. - Property modifications trigger
trigger
operations to notify updates.
// ComponentA.vue
import { store } from './store'
const increment = () => {
store.count++ // Triggers updates in all components using store.count
}
Comparison of Different Sharing Methods
1. Props Drilling
The traditional method passes props layer by layer, suitable for shallow component trees:
// Parent.vue
<Child :count="count" />
// Child.vue
props: ['count']
2. Provide/Inject
The official solution for cross-level sharing:
// Ancestor component
import { provide, ref } from 'vue'
provide('count', ref(0))
// Descendant component
import { inject } from 'vue'
const count = inject('count')
3. Global State Management
Centralized state management provided by Pinia or Vuex:
// store/counter.js
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
}
}
})
// Usage in components
const store = useCounterStore()
store.increment()
Considerations for Reactive Sharing
1. Destructuring Loses Reactivity
Direct destructuring breaks the Proxy's reactive connection:
const { count } = store // Wrong! count is no longer reactive
const count = computed(() => store.count) // Correct approach
2. Special Handling for Arrays and Collections
Vue overrides array methods to maintain reactivity:
const list = reactive([1, 2, 3])
list.push(4) // Maintains reactivity
list = [...list, 4] // Wrong! Reassignment loses reactivity
3. Performance Optimization Strategies
For large-scale state sharing, consider:
// Use shallowRef to reduce reactivity overhead
const largeList = shallowRef([])
// Use markRaw to skip reactivity conversion
const staticData = markRaw({...})
Underlying Implementation of the Reactivity System
The core of Vue3's reactivity system is in the @vue/reactivity
package, with key implementations including:
- The
targetMap
structure for dependency tracking:
type TargetMap = WeakMap<object, DepsMap>
type DepsMap = Map<string | symbol, Dep>
type Dep = Set<ReactiveEffect>
- The execution flow for triggering updates:
// Simplified trigger implementation
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
effects && effects.forEach(effect => effect.run())
}
Best Practices for State Sharing
1. Encapsulation with Composable Functions
Encapsulate shared logic into reusable composable functions:
// useCounter.js
export function useCounter() {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, double, increment }
}
2. Managing State with Custom Hooks
Create domain model hooks for complex scenarios:
// useUserStore.js
export function useUserStore() {
const state = reactive({
users: [],
loading: false
})
async function fetchUsers() {
state.loading = true
state.users = await api.getUsers()
state.loading = false
}
return { ...toRefs(state), fetchUsers }
}
3. State Partitioning Strategy
Divide state by feature modules in large applications:
// stores/
import { useAuthStore } from './auth'
import { useCartStore } from './cart'
export function useStore() {
return {
auth: useAuthStore(),
cart: useCartStore()
}
}
Edge Cases in Reactive Sharing
1. Cross-Window State Synchronization
Use BroadcastChannel for multi-tab synchronization:
const channel = new BroadcastChannel('app-state')
watch(store, (newValue) => {
channel.postMessage(newValue)
})
channel.onmessage = (evt) => {
Object.assign(store, evt.data)
}
2. Web Worker Communication
Serialization is required for sharing state with Workers:
// Main thread
const sharedState = useSharedState()
worker.postMessage({ type: 'STATE_UPDATE', payload: toRaw(sharedState) })
// Worker thread
self.onmessage = ({ data }) => {
if (data.type === 'STATE_SYNC') {
reactiveState = reactive(data.payload)
}
}
3. SSR Environment Handling
Avoid state pollution in server-side rendering:
// Create state instances unique to each request
export function createAppState() {
return reactive({ /*...*/ })
}
// In request handling
app.use((req, res) => {
const state = createAppState()
// ...rendering logic
})
Extension Mechanisms of the Reactivity System
Vue3 allows extending the behavior of the reactivity system:
1. Custom Refs
Implement refs with special logic:
function customRef(factory) {
let value
return {
get() {
track(this, 'value')
return value
},
set(newVal) {
value = factory(newVal)
trigger(this, 'value')
}
}
}
2. Reactive Utility Functions
Create reactive utilities for specific scenarios:
function reactiveInterval(interval) {
const counter = ref(0)
const timer = setInterval(() => counter.value++, interval)
onScopeDispose(() => clearInterval(timer))
return counter
}
3. Deep Observation Control
Precisely control reactivity depth:
function shallowReactive(obj) {
const observed = reactive({})
Object.keys(obj).forEach(key => {
observed[key] = obj[key]
})
return observed
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn