【Electron-vue】构建桌面应用(9)-electron中的this指向问题

1. this指向问题的出现

我记得在刚使用elecctron-vue进行开发的时候是可以绑定的,通过一些事件触发也可以变更视图。于是重新构建了一个应用,当我没有引入主进程渲染进程时,发现一切都是好的。然而添加过主进程渲染进程通信后,发现在进程中无法使用this进行绑定。

在上一篇在中发现了electron-vue中的this指向出现问题,在主进程渲染进程之间通信的时候,尝试了不管使用何种方式,在ipRendererthis永远指向窗口即electron,而不是指向我们的Vue

更正一个问题,昨天说到我在登陆页面也无法通过this绑定,发现是错误的,只有在进程中才无法通过this绑定数据到我们的视图中,也就是说在进程中通过this去改变vue-data中的数据,是无法去改变的,也是无法变更视图的。而在其他位置(非进程通信中)是完全可以通过this去变更视图。

这就会很蛋疼,也就是说在进程里面我用不了this了?那我在进程中通过this绑定数据的时候也会出现问题,我所有绑定的数据都绑定到了EventEmitter这个对象中了。

在函数中我尝试对各处的this进行打印输出,得到的结果如下

created() {
    console.log("vue this:",this)
    this.$electron.ipcRenderer.on("start-exe", function (event, result) {
      console.log("进程通信中this:",this)
      if (result && result.type === "err") {
        notification.error({
          message: "Notification Title",
          description: result.msg,
        });
      } else if (result && result.type === "success") {
        notification.success({
          message: "Notification Title",
          description: result.msg,
        });
      }
    });
    setTimeout(() => {
      console.log('进程通信后this:', this)
    }, 5000);
  },

【Electron-vue】构建桌面应用(9)-electron中的this指向问题_第1张图片

对于1的打印,可以看出this确实指向Vue
对于2的打印,可以看出this却指向了node的事假监听对象EventEmitter
对于3的打印,可以看出this指向了Vue

这样,导致我们无法在通信的时候通过vue去绑定变量或者属性,今天又调研了一下怎样改变this的指向?,却发现实现不了。

后来又想了想既然进程通信间的数据不能绑定到vue上,那能不能存起来呢?

最开始,我想到了vuex去保存数据,但是想了想,应该不行。因为在使用vuex的时候也会使用到this,无法改变this指向,就无法去搞这个东西。

既然无法使用vuex那只能使用其他的方式去保存进程中的东西。在进程通信的时候,可以将数据保存到本地或者窗口,比如使用window对象或者localstorage等去保存这些数据。

那保存的数据怎么去变更我们的视图呢?

目前想到的办法:

将渲染进程接收到的数据通过window保存下来,即window.resut = result这种方式。然后在进程外使用定时器去给出个时间去控制,在一定时间后将window中的值赋值给this.result,那么就能去变更视图,就像下面的代码一样。但是这种方式的缺点就是定时的间隔时间无法确定。我曾调研进程的通信是不是异步的,按照常理来说应该是,因为他的输出结果会在同步任务之后输出,那异步任务应该会存放到js的事件队列中,而setTimeout是宏事件,也会放到事件队列中,而且会在其他异步实践执行之后才会执行。

created() {
    console.log("vue this:",this)
    this.$electron.ipcRenderer.on("start-exe", function (event, result) {
      console.log("进程通信中this:",this)
      window.result = result
      if (result && result.type === "err") {
        notification.error({
          message: "Notification Title",
          description: result.msg,
        });
      } else if (result && result.type === "success") {
        notification.success({
          message: "Notification Title",
          description: result.msg,
        });
      }
    });
    setTimeout(() => {
      console.log('进程通信后this:', this)
      this.result = window.result
    }, 3000);
  },

经测试,确实是可以变更视图,但这种方式并不是最好的,但也是目前能想到解决这种办法的。但是突然想到,以后一旦碰到主进程和渲染进程之间通信都要通过setTimeout的方式去更新视图就会崩溃!!!

不知道你们有没有碰到这种问题,有没有什么更好的解决办法。如果有的话,欢迎留言!

2. this指向问题的解决办法

在前几天做这个项目的时候,牵扯到主进程渲染进程之间的通信的时候,如果想要获取到进程通信间的数据,尤其是在渲染进程中监听主进程传递的数据时,会出现this指向的问题。

在偶然的一次修改代码时,发现有的地方是能用this去指向我们的vue,而不是窗口即EventEmitter

对比之下发现,原来并非是主进程渲染进程之间的问题,而是我们在使用this时,存在错误的用法。

我们知道全局this默认指向的是window,并且在使用的applycall的时候也可以去改变this的指向。

但是,我忽略了一点,那就是在es6+ 箭头函数也可以改变this的指向

于是便尝试在主进程渲染进程通信时,将渲染进程中的所涉及的函数全部使用箭头函数来改变其this指向

为什么要在渲染进程中去做这件事情呢?

因为我们的渲染进程才是我们真正的vue项目,使用vue就牵扯到this的问题。无论是页面变量,还是vue自带的$router以及$store都需要使用this去绑定。所以我们主要的工作也就是在vue中去改变this指向

尝试一下修改

created() {
    this.$electron.ipcRenderer.on("start-result", (event, result) => {
      console.log("初始化this", this);
      if (result && result.type === "err") {
        notification.error({
          message: "Notification Title",
          description: result.msg,
        });
      } else if (result && result.type === "success") {
        notification.success({
          message: "Notification Title",
          description: result.msg,
        });
      }
    });
    this.$electron.ipcRenderer.send("start-exe");
  },

将之前的渲染进程中的生命周期函数中的进程监听中的函数改为箭头函数即将
function (event, result) {} 修改为(event, result) => {},然后查看一下打印输出。可以看到这次输出的确实是VueComponent而不是之前的EventEmitter,这样就可以在渲染进程中接受数据,并将数据绑定到this上,去改变我们的视图。
【Electron-vue】构建桌面应用(9)-electron中的this指向问题_第2张图片

3. 其他问题

在之前的代码中,主进程渲染进程的通信使用的是mainWindow作为主进程为渲染进程发送消息,使用ipcRenderer做渲染进程的监听和发送消息。

当时很笨拙的使用了setTimeout去做延迟,以防止在返回数据的时候,没有接收到数据。这种方法是不可取的,谁都不能保证在通信的过程中时间的快慢,你就无法去准确的定位到需要延迟多长时间去赋值去获取数据。

然后就改用了ipcMainipcRenderer的方式去通信。这种方式还是很靠谱的。他不需要做延迟,什么时间获得数据了,然后将数据发送给渲染进程,在渲染进程中再将数据绑定到vue中的this中,从而更新到视图中

而且之前的方式是在主进程中通过执行exe的命令去启动子进程,子进程返回结果后,将子进程的数据通过setTimeout的方式延迟发送给渲染进程。

看一下之前的主进程处理方式

const cp = require('child_process')
// 执行exe程序并获取输出值
function execPrograme() {
  log.info("开始执行-----------------------------")
  let message
  let child = cp.spawn(`${__dirname}/main.exe`)
  child.on('error',console.error)
  child.stdout.on('data',(data)=>{
   log.info('data=',data)
  })
  child.stderr.on('data',(data)=>{
    log.info('data=',data)
  })
  // log.info(child.stdout)

  setTimeout(()=>{
    mainWindow.webContents.send('asynchronous-message',JSON.stringify(message))
  },5000);
  log.info("结束执行-----------------------------")
}

修改之后使用ipcMain的方式处理主进程中的方式

import { ipcMain } from 'electron'
const cp = require('child_process')
let startMsg
// 启动agent exe
function execPrograme() {
  log.info("开始执行-----------------------------")
  let child = cp.spawn(`${__dirname}/main.exe`)
  child.on('error', console.error)
  child.stdout.on('data', (data) => {
    let logs = data.toString().split('\n').filter(x => x);
    logs.forEach(el => {
      startMsg = `${el}\n\n`
    });
  })
  child.stderr.on('data', (data) => {
    let logs = data.toString().split('\n').filter(x => x);
    logs.forEach(el => {
      startMsg = `${el}\n\n`
    });
  })
  log.info("结束执行-----------------------------")
}
// 监听渲染进程事件
ipcMain.on('start-exe', (event, arg)=>{
  log.info('arg=', arg)
  event.sender.send('start-result', JSON.parse(startMsg))
})

渲染进程中只要这么处理就行了

  created() {
    // 页面渲染后,渲染进程发送事件到主进程中
    this.$electron.ipcRenderer.send("start-exe");
    // 监听主进程事件,并获取返回数据,将数据绑定到this中
    this.$electron.ipcRenderer.on("start-result", (event, result) => {
      this.result = result
      console.log("初始化this", this);
      if (result && result.type === "err") {
        notification.error({
          message: "Notification Title",
          description: result.msg,
        });
      } else if (result && result.type === "success") {
        notification.success({
          message: "Notification Title",
          description: result.msg,
        });
      }
    });
  },

这样看起来结构就更清晰了

  1. 将监听事件放到之前的启动函数外面
  2. 当启动函数执行完,将子进程返回的数据保存到变量中
  3. 然后当页面加载到created方法时,渲染进程发送事件到主进程中
  4. 主进程接收到渲染进程的请求,并将变量返回给渲染进程
  5. 渲染进程再去监听主进程的事件,将数据绑定到vue的this中

至此就算解决了this的指向问题,应该一开始就想到箭头函数this指向的问题,做了这么久才发现这点,属实有点low了。

这样的话,ant.d组件就可以正确的绑定到vue上,也就可以正常的使用messagenotification等组件了。这部分代码还待修改,就不放上去了。按照官网给出的使用方式就可以了。

你可能感兴趣的:(Electron)