【问题标题】:Vuex: Can't change deeply nested state data inside actionsVuex:无法更改动作中深层嵌套的状态数据
【发布时间】:2020-01-14 08:28:10
【问题描述】:

在商店中,我有一个动作来更新一些数据,动作如下所示:


setRoomImage({ state }, { room, index, subIndex, image }) {
      state.fullReport.rooms[room].items[index].items[subIndex].image = image;
      console.log(state.fullReport.rooms[room].items[index].items[subIndex])
    },

因为所有这些数据都是动态的,所以我必须动态更改嵌套值并且不能直接对属性进行硬编码。 数据如下所示:

fullreport: {
    rooms: {
        abc: {
          items: [
            {
              type: "image-only",
              items: [
                {
                  label: "Main Image 1",
                  image: ""
                },
                {
                  label: "Main Image 2",
                  image: ""
                }
              ]
            }
          ]
        }
      }
}

当我调度操作时,在控制台中我可以看到子属性 image 的值已成功变异,但如果我从 Chrome 中的 Vue DevTools 访问 VueX 存储,我看到该值没有t在那里改变。这是控制台输出:

拜托,有人能说出为什么会这样吗?据我所知,数据正在成功更改,但不知何故状态没有显示它,因此我的组件不会重新呈现。

我也尝试使用Vue.set 而不是简单的赋值,但仍然没有运气:(

Vue.set(
  state.fullReport.rooms[room].items[index].items[subIndex],
  "image",
   image
 );

编辑:

按照 David Gard 的回答,我尝试了以下方法:

我也在使用 Lodash _(我知道制作对象的完整副本并不好),这是突变代码块。

let fullReportCopy = _.cloneDeep(state.fullReport);
fullReportCopy.rooms[room].items[index].items[subIndex].image = image;
Vue.set(state, "fullReport", fullReportCopy);

现在在计算属性中,state.fullReport 是一个依赖项,我有一个 console.log,只要重新计算计算属性,它就会打印出一个字符串。

每次我提交这个突变时,我都会看到计算属性记录了字符串,但是它接收的状态仍然没有改变,我猜Vue.set 只是告诉计算属性状态发生了变化,但它没有实际上并没有改变它。因此,我的组件的 UI 没有任何变化。

【问题讨论】:

  • 顺便说一句:您应该避免在商店中放置如此​​大的嵌套对象。见forum.vuejs.org/t/vuex-best-practices-for-complex-objects/10143/…
  • 实际上在你的行动中你应该提交一些突变来做到这一点
  • @MatheusValenza 会解决当前的问题还是只是遵循惯例?
  • @NadirAbbas 操作不会更新状态。突变确实如此。可能你会收到一些警告。见vuex.vuejs.org
  • 你尝试过 Vue.set 而不是简单的 assign 吗?

标签: javascript vue.js vuex


【解决方案1】:

如 cmets 中所述 - 如果您在存储中保持深度嵌套状态,它很快就会变得复杂。

问题是,您必须以两种不同的方式填充数组和对象,因此,请考虑您是否需要访问它们的本机方法。不幸的是,Vuex 还不支持响应式地图。

除此之外,我还处理需要动态设置具有多个嵌套级别的属性的项目。一种方法是递归地设置每个属性。

它不漂亮,但它有效:

function createReactiveNestedObject(rootProp, object) {
// root is your rootProperty; e.g. state.fullReport
// object is the entire nested object you want to set

  let root = rootProp;
  const isArray = root instanceof Array;
  // you need to fill Arrays with native Array methods (.push())
  // and Object with Vue.set()

  Object.keys(object).forEach((key, i) => {
    if (object[key] instanceof Array) {
      createReactiveArray(isArray, root, key, object[key])
    } else if (object[key] instanceof Object) {
      createReactiveObject(isArray, root, key, object[key]);
    } else {
      setReactiveValue(isArray, root, key, object[key])
    }
  })
}

function createReactiveArray(isArray, root, key, values) {
  if (isArray) {
    root.push([]);
  } else {
    Vue.set(root, key, []);
  }
  fillArray(root[key], values)
}

function fillArray(rootArray, arrayElements) {
  arrayElements.forEach((element, i) => {
    if (element instanceof Array) {
      rootArray.push([])
    } else if (element instanceof Object) {
      rootArray.push({});
    } else {
      rootArray.push(element);
    }
    createReactiveNestedFilterObject(rootArray[i], element);
  })
}

function createReactiveObject(isArray, obj, key, values) {
  if (isArray) {
    obj.push({});
  } else {
    Vue.set(obj, key, {});
  }
  createReactiveNestedFilterObject(obj[key], values);
}

function setValue(isArray, obj, key, value) {
  if (isArray) {
    obj.push(value);
  } else {
    Vue.set(obj, key, value);
  }
}

如果有人有更聪明的方法来做这件事,我很想听听!

编辑:

我使用上面贴出的解决方案的方式是这样的:

// in store/actions.js

export const actions = {
  ...
  async prepareReactiveObject({ commit }, rawObject) {
    commit('CREATE_REACTIVE_OBJECT', rawObject);
  },
  ...
}

// in store/mutations.js
import { helper } from './helpers';

export const mutations = {
  ...
  CREATE_REACTIVE_OBJECT(state, rawObject) {
    helper.createReactiveNestedObject(state.rootProperty, rawObject);
  },
  ...
}

// in store/helper.js

// the above functions and

export const helper = {
  createReactiveNestedObject
}

【讨论】:

  • 我正在尝试使用它,但是遇到了麻烦,如果您能帮助我在我的代码中使用它,那就太好了。如何在 Store 中将其用作操作,并且这些函数是操作的本地函数?
  • @NadirAbbas 我已经用更多的实现细节更新了我的答案,如果您需要进一步的帮助,请告诉我您遇到了哪些错误。 - 关于您问题中的编辑:它不起作用,因为您需要像我在回答中所写的那样递归设置属性:)
【解决方案2】:

排除关于 cmets 的良好做法。

你需要的是:当对象改变时指示 Vue(复杂对象不是响应式的)。使用 Vue.set。您需要设置整个对象:

   Vue.set(
     state,
     "fullReport",
     state.fullReport
   );

文档:https://vuejs.org/v2/api/#Vue-set

【讨论】:

  • Vue.set() 向对象添加反应属性。您不能使用它设置嵌套对象。如果要设置嵌套对象,则需要递归设置每个属性。
猜你喜欢
  • 2021-11-15
  • 1970-01-01
  • 2018-01-21
  • 2020-02-13
  • 2016-06-27
  • 2018-04-16
  • 2015-03-03
  • 2021-07-26
  • 2019-01-13
相关资源
最近更新 更多