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

Vue3+Pinia Getter无法响应式更新Vue响应式变量问题

问题:Tauri+Vue+Pinia状态更新后子组件标签禁用状态未同步

我正在开发一个带状态控制的Tauri应用,已排除Tauri相关问题。应用通过异步进程检查系统状态并更新Pinia中的appStatus,供子组件使用。最初appStatus是对象,后来把status属性单独拆分出来,问题依然存在:子组件的标签禁用状态不会随着appStatus的更新而变化。


Pinia Store 代码(general.js)

import { defineStore } from 'pinia';

export const useGeneralStore = defineStore('general', {
  state: () => ({
    appStatus: 'loading',
    appStatusData: {
      status: 'loading',
      license_number: '',
      days_left: 0,
      valid_until: '',
    },
    showOverlay: false,
  }),
  getters: {
    getShowOverlay: (state) => state.showOverlay,
    disabledWhen: (state) => {
      return (conditions) => {
        if (conditions.includes(state.appStatus)) return true;
        return false;
      };
    },
  },
  actions: {
    setAppStatus(value) {
      this.appStatus = value;
    },
    setShowOverlay(value) {
      this.showOverlay = value;
    },
  },
});

启动状态检查代码(App.vue)

....
<script setup>
import { onMounted } from 'vue';
import { Command } from '@tauri-apps/api/shell';

import { storeToRefs } from 'pinia';
import { useGeneralStore } from './store/general';
import { ref } from 'vue';

const store = useGeneralStore();
const { setDialog } = store;
const { showOverlay, appStatus, appStatusData, fernetKey } = storeToRefs(store);

onMounted(async () => {
  showOverlay.value = true;
  const command = Command.sidecar('binaries/app', ['status', fernetKey.value]);
  let out = await command.execute();
  if (out.code !== 0) {
    setDialog(out.stderr);
    return;
  }
  out = JSON.parse(out.stdout);
  appStatus.value = out.status;
  appStatusData.value = out;
  showOverlay.value = false;
});
</script>
...

子组件标签页代码(ActionTabs.vue)

<template>
  <div class="actions_side w-12 font-montserrat-regular relative">
    <TabView :active-index="enabledIndex">
      <TabPanel
        :disabled="tab.disabled"
        v-for="tab in actionTabs"
        :key="tab.title"
        :header="tab.title"
      >
        <component :is="tab.component" />
      </TabPanel>
    </TabView>
  </div>
</template>

<script setup>
import { ref, computed, reactive } from 'vue';
import Activation from '../components/Activation.vue';
import Deactivation from '../components/Deactivation.vue';
import Install from '../components/Install.vue';

import { useGeneralStore } from '../store/general';
import { storeToRefs } from 'pinia';

const store = useGeneralStore()
const { appStatus, disabledWhen } = storeToRefs(store)

const actionTabs = reactive([
  {
    title: 'Activate',
    component: Activation,
    disabled: disabledWhen.value(['active', 'notFound', 'loading']),
  },
  {
    title: 'Deactivate',
    component: Deactivation,
    disabled: disabledWhen.value(['deactive', 'notFound', 'loading']),
  },
  {
    title: 'Install',
    component: Install,
    disabled: true,
  },
  {
    title: 'Update',
    component: Activation,
    disabled: true,
  },
]);

const enabledIndex = computed(() => {
  for (let i = 0; i < actionTabs.length; i++) {
    if (!actionTabs[i].disabled) return i;
  }
});
</script>

已尝试的解决方法

  • 将reactive改为ref
  • 将Getter替换为本地判断(注释代码仍保留)
  • 将原始值从对象中分离
  • 通过按钮修改状态值并确认状态已正确变更

解决方案

核心问题是:actionTabs中的disabled属性是组件初始化时一次性计算的,没有响应式追踪appStatus的变化。需要将标签的禁用状态改为响应式计算,确保appStatus更新时同步更新禁用状态。

修改后的ActionTabs.vue代码:

<template>
  <div class="actions_side w-12 font-montserrat-regular relative">
    <TabView :active-index="enabledIndex">
      <TabPanel
        :disabled="tab.disabled"
        v-for="tab in computedActionTabs"
        :key="tab.title"
        :header="tab.title"
      >
        <component :is="tab.component" />
      </TabPanel>
    </TabView>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import Activation from '../components/Activation.vue';
import Deactivation from '../components/Deactivation.vue';
import Install from '../components/Install.vue';

import { useGeneralStore } from '../store/general';
import { storeToRefs } from 'pinia';

const store = useGeneralStore()
const { appStatus, disabledWhen } = storeToRefs(store)

// 基础标签配置,仅存固定属性
const baseActionTabs = [
  {
    title: 'Activate',
    component: Activation,
    disableConditions: ['active', 'notFound', 'loading'],
  },
  {
    title: 'Deactivate',
    component: Deactivation,
    disableConditions: ['deactive', 'notFound', 'loading'],
  },
  {
    title: 'Install',
    component: Install,
    disableConditions: [],
  },
  {
    title: 'Update',
    component: Activation,
    disableConditions: [],
  },
];

// 通过computed生成响应式标签列表,实时计算禁用状态
const computedActionTabs = computed(() => {
  return baseActionTabs.map(tab => {
    let disabled = false;
    if (tab.disableConditions.length > 0) {
      disabled = disabledWhen.value(tab.disableConditions);
    } else {
      // 对应Install和Update的固定禁用
      disabled = true;
    }
    return {
      ...tab,
      disabled,
    };
  });
});

const enabledIndex = computed(() => {
  for (let i = 0; i < computedActionTabs.value.length; i++) {
    if (!computedActionTabs.value[i].disabled) return i;
  }
});
</script>

改动说明

  1. 将原本的reactive数组拆分为基础配置和computed响应式数组,每次appStatus变化时,computedActionTabs会重新计算每个标签的禁用状态。
  2. 避免了初始化时一次性计算disabled的问题,确保状态更新时禁用状态同步变化。

内容的提问来源于stack exchange,提问作者Safa Emre YILDIRIM

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 00:00:28