You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何订阅/取消订阅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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 10:02:36