首页 > 解决方案 > Vuex 状态未完全更新兄弟控件

问题描述

我在使用 Vuex 和将状态更新为兄弟组件时遇到问题。我知道:key绑定的必要性并拥有它。

在我的状态下,我有:

    filters: {
      clientId: 0,
      projectId: 0,
      projectRoleId: 0,
      subProjectId: 0,
    },

我有 4 个组成这个页面的组件:

Month:这个组件有子组件,但是有过滤器的 v-for 在这个级别

DailyDetail:此组件显示一天的时间条目,并提供更多详细信息

议程:这是日历中显示的同一天的垂直视图(月份控制)

过滤器:在这里我们可以过滤 4 个字段中的 1 个(客户、项目、子项目或项目角色)

当我单击并添加过滤器时,所有 3 个组件(月、每日详细信息和议程)都仅显示过滤后的结果。这就是我要的。但是,当我单击 Filters 控件上的Clear Filters按钮时,似乎只有 Month 组件会注意到它。其他的只是保持不变。除了当getter 更改filtered时该类被正确应用和删除的事实。hasFilters

如果有帮助,这里是动作/突变和吸气剂:

过滤器的设置直接从计算的 setter 转到 Mutation:

    // Computed properties on the Filter component:
    filteredClientId: {
      get() { return this.$store.state.time.filters.clientId; },
      set(value) { this.$store.commit('time/SET_FILTERED_CLIENT', value); },
    },
    filteredProjectId: {
      get() { return this.$store.state.time.filters.projectId; },
      set(value) { this.$store.commit('time/SET_FILTERED_PROJECT', value); },
    },
    filteredProjectRoleId: {
      get() { return this.$store.state.time.filters.projectRoleId; },
      set(value) { this.$store.commit('time/SET_FILTERED_PROJECT_ROLE', value); },
    },
    filteredSubProjectId: {
      get() { return this.$store.state.time.filters.subProjectId; },
      set(value) { this.$store.commit('time/SET_FILTERED_SUB_PROJECT', value); },
    },

    // Here are the corresponding mutations
    SET_FILTERED_CLIENT(state, value) {
      state.filters.clientId = value;
    },
    SET_FILTERED_PROJECT(state, value) {
      state.filters.projectId = value;
    },
    SET_FILTERED_PROJECT_ROLE(state, value) {
      state.filters.projectRoleId = value;
    },
    SET_FILTERED_SUB_PROJECT(state, value) {
      state.filters.subProjectId = value;
    },

这是月份组件(此组件始终可以设置和清除过滤器):

    <template>

      <div class="month"
           :class="{ 'filtered': hasFilters }"
      >
        <weekDays />
        <week v-for="(week, index) in weeks"
              :key="index"
              :week="week"
        />
      </div>

    </template>

    <script>
    import { mapGetters } from 'vuex';
    import week from './week.vue';
    import weekDays from './weekDays.vue';

    export default {
      components: {
        week,
        weekDays,
      },
      computed: {
        ...mapGetters('time', ['hasFilters', 'weeks']),
      },
    };
    </script>

以下是这些组件的 getter:

    daysWithinPayPeriod: (state, getters) => {
      if (!state.currentDay) // This is simply to gracefully exit when no data is loaded yet
        return [];

      let payPeriodSerial = state.currentDay.PayPeriodSerial;

      // Find all of the entries within the pay period 
      let validDays = state.monthlyData.AllDays
        .filter(x => x.PayPeriodSerial === payPeriodSerial);

      if (getters.hasFilters) {
        for (let day of validDays) {
          day.ProjectTimes = day.ProjectTimes.filter(x => {
            return (
              x.Project.Client.ID === (state.filters.clientId === 0 ? x.Project.Client.ID : state.filters.clientId) &&
                x.Project.ID === (state.filters.projectId === 0 ? x.Project.ID : state.filters.projectId) &&
                x.ProjectRole.ID === (state.filters.projectRoleId === 0 ? x.ProjectRole.ID : state.filters.projectRoleId) &&
                x.SubProject.ID === (state.filters.subProjectId === 0 ? x.SubProject.ID : state.filters.subProjectId)
            );
          });
        }
      }

      return validDays;
    },
    hasFilters: state => {
      return state.filters.clientId !== 0 ||
        state.filters.projectId !== 0 ||
        state.filters.projectRoleId !== 0 ||
        state.filters.subProjectId !== 0;
    },
    weeks: (state, getters) => {
      if (getters.hasFilters) {
        let result = JSON.parse(JSON.stringify(state.monthlyData.Weeks));

        for (let week of result) {
          for (let day of week.Days) {
            day.ProjectTimes = day.ProjectTimes.filter(x => {
              return (
                x.Project.Client.ID === (state.filters.clientId === 0 ? x.Project.Client.ID : state.filters.clientId) &&
                x.Project.ID === (state.filters.projectId === 0 ? x.Project.ID : state.filters.projectId) &&
                x.ProjectRole.ID === (state.filters.projectRoleId === 0 ? x.ProjectRole.ID : state.filters.projectRoleId) &&
                x.SubProject.ID === (state.filters.subProjectId === 0 ? x.SubProject.ID : state.filters.subProjectId)
              );
            });
          }
        }

        return result;
      } else {
        return state.monthlyData.Weeks
          ? state.monthlyData.Weeks
          : [];
      }
    },

下面是 Agenda 组件,它被修剪了一点,只显示有用的信息:

    <template>

      <div class="agenda">
        <table class="table table-hover mb-0 table-bordered"
                     :class="{ 'filtered': hasFilters }"
        >
          ...
          <tbody>
            <tr v-for="day in daysWithinPayPeriod"
                    :key="day.DayOfYear"
                    :class="{ 'is-today': day.IsToday, 'weekend': day.IsWeekend, 'focused': isDaySelected(day) }"
                    class="day-row"
                    @click="selectDay(day)"
            >
              ...
              <td colspan="4"
                      class="breakout"
              >
                <table v-if="day.ProjectTimes.length > 0"
                             class="table table-sm table-striped table-hover table-borderless mb-0"
                >
                  <tbody>
                    <tr v-for="entry in day.ProjectTimes"
                            :key="entry.ID"
                    >
                    ...
                    </tr>
                  </tbody>
                </table>
              </td>
            </tr>
          </tbody>
        </table>
      </div>

    </template>
    <script>
    import { mapGetters, mapState } from 'vuex';
    import store from '../../store';

    export default {
      store: store,
      mixins: [ formattingFilters ],
      computed: {
        ...mapGetters('time', ['daysWithinPayPeriod', 'hasFilters']),
        ...mapState('time', {
        }),
      },
      methods: {
        selectDay(day) {
          this.$store.dispatch('time/dateSelected', day);
        },
      },
    };
    </script>

对于议程组件,我假设它是嵌套v-for的,但是 DailyDetail 组件(要遵循)没有嵌套v-for并且不会“清除”过滤器。

这是 DailyDetail 组件:

    <template>

      <div class="daily-detail">
        <table class="table table-sm table-striped table-hover mb-0"
                     :class="{ 'filtered': hasFilters }"
        >
          ...
          <tbody>
            <tr v-for="entry of currentDaysTimeEntries"
                    :key="entry.ID"
                    :class="{ 'italic': entry.Project.IsUniversal }"
            >
              <td class="text-center"><i class="far fa-pencil" /></td>
              <td><div class="text-right">{{ entry.Time | formatNumber }}</div></td>
              <td>
                <div class="truncate">{{ entry.Project.Name }}</div>
                <div v-if="entry.SubProject.ID > 0"
                         class="subproject"
                >
                  {{ entry.SubProject.Name}}
                </div>
                <small>{{ entry.Project.Client.Name }}</small>
              </td>
              ...
            </tr>
          </tbody>
        </table>
      </div>

    </template>

    <script>
    import { mapGetters } from 'vuex';
    import store from '../store';
    import formattingFilters from '../mixins/formatting';

    export default {
      store: store,
      computed: {
        ...mapGetters('time', ['currentDaysTotalTime', 'currentDaysTimeEntries', 'hasFilters', 'selectedDate']),
      },
    };
    </script>

过滤器的清除通过一个动作:

    // Action
    clearFilters(context) {
      context.commit('CLEAR_FILTERS');
    },

    // Mutation
    CLEAR_FILTERS(state) {
      state.filters.clientId = 0;
      state.filters.projectId = 0;
      state.filters.projectRoleId = 0;
      state.filters.subProjectId = 0;
    },

所以重申一下,问题在于所有 3 个组件都使用hasFiltersgetter 来应用一个调用filtered其组件的类,以表示有过滤器在起作用。这部分适用于设置和清除过滤器。问题是,当过滤器在(该部分有效)时被过滤掉的值在过滤器被清除时不会被清除。Month 组件完美运行,但 Agenda 和 DailyDetails 只是保持过滤状态。

标签: vuejs2vuex

解决方案


好吧,睡了一夜好觉后,我想我会再戳一下它。这是我的错(正如预期的那样。)我注意到两件事,一是DailyDetail组件filtered正确显示了类(如上所述)但是,它没有过滤数据。所以修复就在这里:

原来的吸气剂DailyDetail没有过滤

currentDaysTimeEntries: state => {
  return state.currentDay
    ? state.currentDay.ProjectTimes
    : [];
},

修复DailyDetail了过滤的吸气剂

currentDaysTimeEntries: (state, getters) => {
  if (state.currentDay) {
    if (getters.hasFilters) {
      return state.currentDay.ProjectTimes.filter(x => {
        return (
          x.Project.Client.ID === (state.filters.clientId === 0 ? x.Project.Client.ID : state.filters.clientId) &&
          x.Project.ID === (state.filters.projectId === 0 ? x.Project.ID : state.filters.projectId) &&
          x.ProjectRole.ID === (state.filters.projectRoleId === 0 ? x.ProjectRole.ID : state.filters.projectRoleId) &&
          x.SubProject.ID === (state.filters.subProjectId === 0 ? x.SubProject.ID : state.filters.subProjectId)
        );
      });
    } else {
      return state.currentDay.ProjectTimes;
    }
  } else {
    return [];
  }
},

然后,一旦解决了这个问题并且 3 个组件中的 2 个运行良好,我意识到该Agenda组件(那个不工作的)不仅仅是使用 JavaScriptfilter方法,它必须做更多的事情并提取出它不需要的东西。

回答它是filter从数组中删除不需要的东西,覆盖原始数组,因此当过滤被关闭时,没有什么可回头了。所以我制作了一个数组的副本,现在所有 3 个都可以工作了!

议程的原始吸气剂,WAS 过滤并完全删除

daysWithinPayPeriod: (state, getters) => {
  if (!state.currentDay)
    return [];

  let payPeriodSerial = state.currentDay.PayPeriodSerial;

  // Find all of the entries within the pay period 
  let validDays = state.monthlyData.AllDays
    .filter(x => x.PayPeriodSerial === payPeriodSerial);

  if (getters.hasFilters) {
    for (let day of validDays) {
      day.ProjectTimes = day.ProjectTimes.filter(x => {
        return (
          x.Project.Client.ID === (state.filters.clientId === 0 ? x.Project.Client.ID : state.filters.clientId) &&
            x.Project.ID === (state.filters.projectId === 0 ? x.Project.ID : state.filters.projectId) &&
            x.ProjectRole.ID === (state.filters.projectRoleId === 0 ? x.ProjectRole.ID : state.filters.projectRoleId) &&
            x.SubProject.ID === (state.filters.subProjectId === 0 ? x.SubProject.ID : state.filters.subProjectId)
        );
      });
    }
  }

  return validDays;
},

更新了在过滤器之前复制数组的议程 getter(只是更改)

// Find all of the entries within the pay period and make a copy
let validDays = JSON.parse(JSON.stringify(
  state.monthlyData.AllDays
    .filter(x => x.PayPeriodSerial === payPeriodSerial)
));

这是问题所在的特定行,需要数组的副本:

day.ProjectTimes = day.ProjectTimes.filter(x => {


推荐阅读