解决 Vue+Electron 下 Vuex 的 Dispatch 没有效果问题
问题描述:
在使用 Vue+Electron 构建桌面应用程序时,可能会遇到 Vuex 的 Dispatch 方法无法正常触发 Action 的问题。例如下面的代码:
// 在 Vue 组件中的方法里调用 Action,但没有效果
this.$store.dispatch('getUserInfo')
解决方法:
这个问题的解决方法有两个:
- 使用 IPC 将 Vuex Actions 映射到主进程调用
Electron 应用程序有两个进程:主进程和渲染进程。Vuex 存储在 Vue 组件的本地内存中,而主进程无法直接访问它。因此,我们需要通过 IPC (Inter-Process Communication,进程间通信) 将 Vuex Actions 映射到主进程,再由主进程负责调用。
首先,我们需要在主进程中创建一个新的 Vuex Store 实例。在 main.js 中添加如下代码:
import Vuex from 'vuex'
import { ipcMain } from 'electron'
// 创建主进程的 Vuex Store
const store = new Vuex.Store({
state: {
userInfo: {},
},
mutations: {
setUserInfo(state, userInfo) {
state.userInfo = userInfo
},
},
actions: {
getUserInfo({ commit }) {
const userInfo = { name: 'foo', age: 18 } // 实际操作中应该从其他地方获取
commit('setUserInfo', userInfo)
},
},
})
// 在主进程中监听 'dispatch-action' 事件
ipcMain.on('dispatch-action', (event, actionName) => {
store.dispatch(actionName)
})
上面的代码中,我们通过 ipcMain 对象在主进程中创建了一个新的 Vuex Store,并用 ipcMain.on() 监听了名为 'dispatch-action' 的事件。每当从渲染进程发送一个 'dispatch-action' 事件时,主进程就会触发对应的 Action。
接下来,在 Vue 组件中使用 ipcRenderer 对象向主进程发送 'dispatch-action' 事件。在组件中添加如下代码:
import { ipcRenderer } from 'electron'
// 发送 'dispatch-action' 事件,触发对应的 Vuex Action
ipcRenderer.send('dispatch-action', 'getUserInfo')
这样,就可以在 Electron 应用程序中使用 Vuex Actions 了。
- 修改 Vuex Store 的 dispatch 方法
另一个解决方法是修改 Vuex Store 的 dispatch 方法。默认情况下,Vuex Store 的 dispatch 方法会在当前组件所在的渲染进程中执行对应的 Action。但是,在 Vue+Electron 中,我们需要让 dispatch 方法在主进程中执行 Action。
为了实现这个目标,我们需要使用 ipcRenderer 对象向主进程发送 'dispatch-action' 事件,并在主进程中处理这个事件。在 main.js 中添加如下代码:
import { ipcMain } from 'electron'
// 在主进程中监听 'dispatch-action' 事件
ipcMain.on('dispatch-action', (event, actionName) => {
store.dispatch(actionName)
})
接着,在 root Vue 实例创建之前重写 Vuex Store 的 dispatch 方法。在 main.js 中添加如下代码:
import Vuex from 'vuex'
// 创建一个新的 Vuex Store 实例并向它的 dispatch 方法添加代码
const store = new Vuex.Store({
// 省略 state、mutations 等部分
})
const oldDispatch = store.dispatch
store.dispatch = (type, payload) => {
ipcRenderer.send('dispatch-action', type)
return oldDispatch.call(store, type, payload)
}
// 在 main.js 中将 store 实例保存为 global 对象的一个属性
global.store = store
这段代码中,我们创建了一个新的 Vuex Store 实例,并通过 ipcRenderer 向主进程发送 'dispatch-action' 事件。主进程收到事件时,会触发 Vuex Store 实例的 dispatch 方法,从而执行对应的 Action。
最后,在 Vue 组件中直接使用重写后的 dispatch 方法。例如:
// 直接调用重写后的 dispatch 方法,触发 Vuex Action
store.dispatch('getUserInfo')
这样,就可以在 Vue+Electron 中正常使用 Vuex Actions 了。
示例说明:
接下来以第二种方法为示例,详细讲解如何实现:
- 创建一个基于 Vue+Electron 的新项目
使用 Vue CLI 进行创建
vue create my-electron-app
然后使用 vue-cli-plugin-electron-builder 插件进行 Electron 打包
cd my-electron-app
vue add electron-builder
- 安装需要的依赖
npm install vue-electron
npm install vuex
npm install electron-store
其中,vue-electron 是在 Vue 组件中使用 Electron API 的库,vuex 是状态管理库,electron-store 提供了持久化存储等基本功能。
- 在 main.js 中创建一个新的 Vuex Store 并修改 dispatch 方法
import Vue from 'vue'
import Vuex from 'vuex'
import { ipcMain } from 'electron'
import { download } from './utils'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0,
},
mutations: {
increment(state) {
state.count++
},
},
actions: {
increment({ commit }) {
commit('increment')
},
async downloadFile({ commit }, url) {
const status = await download(url)
console.log(status)
},
},
})
const oldDispatch = store.dispatch
store.dispatch = (type, payload) => {
ipcMain.send('dispatch-action', type)
return oldDispatch.call(store, type, payload)
}
export default store
我们修改了 Vuex Store 的 dispatch 方法,改为使用 ipcMain 向主进程发送 'dispatch-action' 事件。
另外,我们还添加了一个异步 Action,用于下载某个文件。
- 在 App.vue 中使用新创建的 Vuex Store
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<div>{{ count }}</div>
<button @click="handleClick">Increment</button>
<div>{{ currentUrl }}</div>
<input type="text" v-model="url" />
<button @click="download">Download</button>
</div>
</template>
<script>
import store from './store'
import { ipcRenderer } from 'electron'
export default {
name: 'HelloWorld',
data() {
return {
msg: 'Welcome to Your Vue.js App',
count: null,
url: '',
currentUrl: '',
}
},
created() {
this.count = store.state.count
ipcRenderer.on('dispatch-action-reply', () => {
this.count = store.state.count
})
},
methods: {
handleClick() {
store.dispatch('increment')
},
download() {
this.currentUrl = this.url
store.dispatch('downloadFile', this.url)
},
},
}
</script>
<style scoped>
</style>
我们在 App.vue 组件中使用了新创建的 Vuex Store,有两个方法: increment 和 downloadFile,都是异步操作。
在 created 生命周期钩子函数中,我们监听了名为 dispatch-action-reply 的事件,用于接收来自主进程的计数器更新值,以及下载操作的结果。
- 在 main.js 中添加相应的代码
在 main.js 中添加如下代码:
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import { ipcRenderer, remote } from 'electron'
Vue.config.productionTip = false
Vue.prototype.$ipcRenderer = ipcRenderer
Vue.prototype.$remote = remote
new Vue({
store,
render: (h) => h(App),
}).$mount('#app')
在这里,我们将 ipcRenderer 和 remote 对象添加到 Vue 实例的原型链上,以便在其他组件中使用它们。
- 编写 download 函数以及需要的文件操作 API
createFile 和 deleteFile
const fs = require('fs')
const path = require('path')
// 创建文件
export const createFile = (filename, content) =>
new Promise((resolve, reject) => {
const filepath = path.resolve(__dirname, filename)
fs.writeFile(filepath, content, (err) => {
if (err) {
reject(err)
} else {
resolve(filepath)
}
})
})
// 删除文件
export const deleteFile = (filename) =>
new Promise((resolve, reject) => {
const filepath = path.resolve(__dirname, filename)
fs.unlink(filepath, (err) => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
download
import { createFile, deleteFile } from './file'
export const download = (url) =>
new Promise((resolve, reject) => {
const http = require('http')
http
.get(url, (res) => {
const { statusCode } = res
if (statusCode !== 200) {
reject(new Error(`Request Failed.\nStatus Code: ${statusCode}`))
res.resume()
return
}
let rawData = ''
res.on('data', (chunk) => {
rawData += chunk
})
res.on('end', async () => {
console.log(rawData)
const filepath = await createFile('test', rawData)
console.log(filepath)
await deleteFile('test')
console.log('Deleted')
resolve('Success')
})
})
.on('error', (err) => {
console.error(`Got error: ${err.message}`)
reject(err)
})
})
该函数使用 Node.js 的 http 模块执行 HTTP GET 请求,并在成功获取到响应之后将响应内容写入到本地文件中。然后,我们又调用了删除文件的方法,从而删除了刚刚下载的文件。
至此,我们已经可以在 Vue+Electron 中正常使用 Vuex Actions 了。其中,使用 ipcRenderer 发送 'dispatch-action' 事件的方法,可以用于更加复杂的场景。例如,我们可以将 Geolocation API 的结果发送回主进程,再在主进程中进行位置信息存储和分析等操作。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:解决Vue+Electron下Vuex的Dispatch没有效果问题 - Python技术站