【问题标题】:Strange behavior of Vuex mutations in Electron app based on electron-vue基于 electron-vue 的 Electron 应用中 Vuex 突变的奇怪行为
【发布时间】:2020-03-21 22:28:25
【问题描述】:

我正在尝试使用 Vue 和 Electron 构建一个简单的任务管理应用程序。我的设置基于带有 Vuex 商店的 electron-vue boilerplate。 用户可以通过模式向列表中添加新项目(并编辑现有项目)。 modal 将信息发送到 store 操作,然后调用突变来更新 store 并将新项目推送到列表项数组。

这是设置:LayerItemLayer 的子代LayerMap 的子代。数据从父 LayerMap 组件中的 store 接收,然后通过 props 提供给子组件。

重现问题:通过showEditItemDialog 中的Layer 组件创建一个新项目。在 SAVE_LAYER_ITEM 突变中,将创建一个新 ID 并将其分配给该新项目。之后,新项目将被推送到layer.items 数组。 UI 将被更新并且创建的项目是可见的。 item.text 显示正确。然而item.id 是不同的。我在突变中包含了console.log。记录的 id 与LayerItem 组件中的 UI 中显示的 id 不匹配,此处为 <p>{{ item.id }}</p>。因此,当在创建新项目后尝试编辑/更新新项目时,突变将创建一个新项目,而不是更新现有项目,因为在存储数组中找不到模式接收的 ID。

我知道代码很多,我尝试尽可能多地删除不必要的代码。在下面的示例中,我创建了一个新项目“test”,您可以看到存储的 ID 与 UI 中显示的 ID 不匹配。

终端日志的屏幕截图

来自 DevTools 控制台的屏幕截图

Vue DevTools 商店的屏幕截图

用户界面截图

LayerMap.vue

// 'layers' is a computed property and gets data from the store
        <draggable
          v-model="layers"
          v-bind="getDragOptions"
        >
          <Layer v-for="(layer, index) in layers" :key="index" :layer="layer"></Layer>
        </draggable>
        <DetailsModal></DetailsModal>

// Inside computed
  computed: {
    layers() {
      return this.$store.getters.allLayers
    }
  }

图层.vue

// 'layer' gets passed from parent as prop
     <span primary-focus @click="showEditItemDialog">Add Item</span> 
     <draggable v-model="items" v-bind="dragOptions" class="items">
        <LayerItem v-for="item in items" :item="item" :layer="layer" :key="item.id"></LayerItem>
      </draggable>

// 'items' is a computed property
    items: {
      get() {
        return this.layer.items
      }
    }

// Function to handle 'Add Item' click and send event which will be handled by DetailsModal.vue
  methods: {
    showEditItemDialog() {
      let payload = {
        layer: this.layer,
        item: {
          id: '',
          text: ''
        }
      }
      this.$bus.$emit('item-editing', payload)
    }
  }

LayerItem.vue

// Layer Item Component
  <div class="layer-item" @click.prevent="startEditing">
    <div class="item-body">
      <p>{{ this.item.text }}</p>
      <p>{{ item.id }}</p>
    </div>
  </div>

// Event will be sent on click with layer item details as parameter
  methods: {
    startEditing() {
      let payload = {
        layer: this.layer,
        item: {
          id: this.item.id,
          text: this.item.text
        }
      }
      this.$bus.$emit('item-editing', payload)
    }
  }
}

DetailsModal.vue

// 'editLayerForm' contains layer item id and text
      <p>{{editLayerForm.id}}</p>
      <div class="bx--form-item">
        <input
          type="text"
          v-model="editLayerForm.text"
        />
      </div>

// Inside <script>, event is received and handled, 'editLayerForm' will be updated with payload information
  mounted() {
    this.$bus.$on('item-editing', this.handleModalOpen)
  },
  methods: {
    handleModalOpen(payload) {
      this.layer = payload.layer
      this.editLayerForm.id = payload.item.id
      this.editLayerForm.text = payload.item.text
      this.visible = true
      console.log('editing', payload)
    },
    handleModalSave() {
      let payload = {
        layerId: this.layer.id,
        item: {
          id: this.editLayerForm.id,
          text: this.editLayerForm.text
        }
      }
      console.log('save', payload)
      this.$store.dispatch('saveLayerItem', payload)
    }
  }

Store.js

const actions = {
  saveLayerItem: ({ commit }, payload) => {
    console.log('action item id', payload.item.id)
    commit('SAVE_LAYER_ITEM', payload)
  }
}

const mutations = {
  SAVE_LAYER_ITEM: (state, payload) => {
    let layer = state.map.layers.find(l => l.id === payload.layerId)
    let itemIdx = layer.items.findIndex(item => item.id === payload.item.id)
    console.log('mutation item id', payload.item.id)

    if (itemIdx > -1) {
      // For existing item
      console.log('update item', payload.item)
      Vue.set(layer.items, itemIdx, payload.item)
    } else {
      // For new item
      payload.item.id = guid()
      console.log('save new item', payload.item)
      layer.items.push(payload.item)
    }
  }
}

【问题讨论】:

  • 代码太多 :) ...您正在谈论您的 Layer Item Component 显示错误 id (顺便说一句,文本呢?)但 item 通过道具传递给组件。所以使用组件的代码(并传入项目)会更有趣(也许你的组件显示完全不同的项目?)
  • @MichalLevý 感谢您的回复。我试图更新我的问题,删除不必要的代码并包含父组件。你知道出了什么问题吗?
  • 还没有。 LayerItem.vue 中的{{ this.item.text }} 是真的吗?
  • 第一步。确认商店中id 的值 - 它是原始(记录)id 还是id,如您的LayerItem.vue 组件所示?使用 Vue 开发工具...
  • @MichalLevý 再次感谢您的帮助!非常感谢!是的,this.item.text 显示了正确的值(我更新了原始问题中的文本)。使用 Vue 开发工具,商店会显示来自 LayerItem.vue 的值,而不是商店突变记录的值。

标签: javascript vue.js vuejs2 electron vuex


【解决方案1】:

以前从未构建过 Electron 应用程序,所以我花了一些时间来深入挖掘,但我想我明白了! :)

每个电子应用程序至少有 2 个进程 - 主进程(负责打开浏览器窗口)和渲染器(您的 Vue 应用程序运行的地方)。如果您在代码中使用console.log,输出显示depends on which process called it - 从主进程调用的console.log 在终端窗口中显示(用于在开发模式下启动应用程序),从渲染器进程调用的console.log显示在开发工具中。

但是来自你的突变的日志出现在两者中!这意味着代码必须在两个进程中运行,对吗?但是怎么做呢?

看来,electron-vue 模板有一个选项(您必须在设置项目时打开)来使用vuex-electron,尤其是createSharedMutations 插件。它可用于在主进程和所有渲染器进程之间共享相同的 Vuex 存储(从技术上讲,每个进程都有自己的存储,但状态是同步的)。它的工作原理是这样的:

  1. 您触发了您的操作(在渲染器进程中)
  2. 在渲染器进程中取消了操作(这就是为什么您不会在开发工具中看到任何来自操作的日志)并通知主进程执行操作。
  3. 现在,如果该操作(在 main 中运行)提交了一个变更,则在主进程中执行变更代码(第一个屏幕截图,来自终端的日志 - id 为空),然后是有效负载(现在使用新生成的 idA)被序列化为 JSON (see ipc-renderer) 并传递给每个渲染器进程以执行相同的突变(因此保持所有存储同步)。在这里,您的突变第二次执行(来自 DevTools 的日志的第二张截图)-item 已经分配了id(A)但它不在项目列表中,因此您的代码分配了新的id(B)并推送它收藏。
  4. idB 渲染在屏幕上
  5. 现在,如果您开始编辑并调用操作进行保存,第 3 点中描述的所有内容都会再次发生,但现在在主进程中执行的突变会看到带有 id B 的项目,该项目不在其项目集合中。因此,它分配了新的id(C 覆盖 B),因此在渲染器进程中执行的突变再次看到带有id C 的项目不在集合中......等等

解决方案显然是在您的商店配置中禁用createSharedMutations 插件(应该在/renderer/store/index.js 中)。如果您确实需要跨主进程/渲染器进程同步存储,则需要重写您的突变...

【讨论】:

  • 哇,非常感谢您为此付出了如此多的努力!读完这篇文章后,我做了一些研究并检查了我的 vuex 商店的设置。我删除了createPersistedState()createSharedMutations() 插件,并将这一行添加到main.js:window.localStorage.clear()。现在它终于开始工作了 :) 再次感谢您深入挖掘。
猜你喜欢
  • 1970-01-01
  • 2019-05-02
  • 2020-08-23
  • 1970-01-01
  • 2019-07-02
  • 2019-02-19
  • 2020-04-21
  • 2018-12-21
  • 1970-01-01
相关资源
最近更新 更多