如何订阅/取消订阅Zustand嵌套对象Store中的指定评分?
问题:如何订阅Zustand Store中特定评分的变化并取消订阅?
现有如下Zustand Store用于管理可变数量的评分:
import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; const initialRating = { id: 'rating1', stars: 5, score: 3, size: '24px', title: 'Food' }; export const useRatingStore = create()( immer((set) => ({ ratings: { [initialRating.id]: initialRating }, removeAllRatings: () => set((state) => { state.ratings = {}; }), newRating: (payload) => set((state) => { if (state.ratings[payload.id]) { console.error('Rating already exists', payload); } else { state.ratings[payload.id] = payload; if (payload.score > payload.stars) { state.ratings[payload.id].score = payload.stars; } } }), updateScore: (payload) => set((state) => { const toUpdate = payload; if (Array.isArray(toUpdate)) { toUpdate.forEach((rating) => { if (state.ratings[rating.id]) { const proxyState = state.ratings[rating.id]; proxyState.score = Math.min(rating.score, proxyState.stars); } else { console.error('updateScore not found', rating.id); } }); } else if (state.ratings[toUpdate?.id]) { const proxyState = state.ratings[toUpdate.id]; proxyState.score = Math.min(toUpdate.score, proxyState.stars); } else { console.error('Invalid updateScore payload', payload); } }), updateStars: (payload) => set((state) => { const toUpdate = payload; if (Array.isArray(toUpdate)) { toUpdate.forEach((rating) => { if (state.ratings[rating.id]) { const proxyState = state.ratings[rating.id]; proxyState.stars = rating.stars; } else { console.error('updateStars not found', rating.id); } }); } else if (state.ratings[toUpdate?.id]) { const proxyState = state.ratings[toUpdate.id]; proxyState.stars = toUpdate.stars; } else { console.error('Invalid updateStars payload', payload); } }), updateSize: (payload) => set((state) => { const toUpdate = payload; if (Array.isArray(toUpdate)) { toUpdate.forEach((rating) => { if (state.ratings[rating.id]) { state.ratings[rating.id].size = rating.size; } else { console.error('updateSize not found', rating.id); } }); } else if (state.ratings[toUpdate?.id]) { state.ratings[toUpdate.id].size = toUpdate.size; } else { console.error('Invalid updateSize payload', payload); } }) })) );
当前可以通过全局订阅获取所有评分变化:
useRatingStore.subscribe((state, oldState) => console.log('state', state, 'oldState', oldState));
需求是在应用非UI部分仅订阅特定评分的变化,并了解如何取消该订阅。
解决方法
1. 订阅特定评分的变化
Zustand的subscribe方法支持传入选择器函数,该函数会从全局状态中提取你关心的特定片段,只有当该片段发生变化时,订阅回调才会触发。
示例代码(订阅ID为rating1的评分):
// 订阅指定ID的评分变化 const unsubscribe = useRatingStore.subscribe( (newRating, oldRating) => { console.log('目标评分更新:', newRating, '旧值:', oldRating); // 在这里处理评分变化的逻辑 }, // 选择器:仅提取ID为rating1的评分 (state) => state.ratings['rating1'] );
注意事项:
- 即使使用了Immer中间件,选择器会自动返回状态的快照,无需手动处理代理对象
- 如果目标评分被删除(比如调用
removeAllRatings),选择器会返回undefined,此时回调也会触发
2. 取消订阅
调用subscribe方法时会返回一个取消订阅函数,执行该函数即可停止监听目标状态的变化。
示例:
// 保存取消订阅函数 const unsubscribe = useRatingStore.subscribe(/* 回调函数,选择器 */); // 在需要停止监听时调用 unsubscribe();
在非UI场景中,务必在合适的时机(比如任务完成、资源释放)调用取消订阅,避免内存泄漏。
内容的提问来源于stack exchange,提问作者stwissel
相关产品推荐
相关产品推荐

