Добро пожаловать в мой первый пост в блоге.

Сегодня В этой статье мы узнаем, как реализовать мощные функции Electron с помощью React. К концу статьи вы сможете понять следующие реализации:

  1. Как создавать экраны десктопных приложений?
  2. Как будут работать основной процесс и процесс рендеринга?
  3. IPC (межпроцессное взаимодействие) в Electron.

Фреймворк Electron позволяет писать кроссплатформенные настольные приложения с использованием JavaScript, HTML и CSS. Он основан на Node.js и Chromium и используется редактором Atom и многими другими приложениями. Это гарантирует, что тяжелые операции ввода-вывода и операции, связанные с ЦП, будут перенесены в новые потоки, что позволит избежать блокировки пользовательского интерфейса (основной процесс).

Электрон поставляется с последней версией Chrome. Его мощные функции можно использовать для управления тяжелыми вычислениями в окне приложения (процесс рендеринга), что позволит приложению работать со скоростью 60 кадров в секунду.

Ждать! Каков основной процесс? Звучит страшно!

Каждое приложение Electron имеет один основной процесс, который действует как точка входа приложения. Основной процесс выполняется в среде Node.js, что означает, что он может require использовать модули и использовать все API-интерфейсы Node.js.

Основной процесс является основой приложения Electron. Он может порождать несколько дочерних процессов (также называемых процессом рендеринга). Как? Мы увидим это через минуту.

Давайте быстро настроим приложение для реагирования с помощью Electron.

Предполагается, что у вас установлены node и npm. Создайте папку с именем my-first-electron-app(имя соответствует нашим интересам). Откройте папку в вашем любимом редакторе кода.

Запустите терминал и запустите npx create-react-app . с точкой в ​​конце, что создаст реагирующее приложение в текущем каталоге.

Создайте файл с именем main.js в общей папке, как показано ниже.

Скопируйте приведенный ниже код в файл main.js. Это базовая настройка Electron, но я добавлю комментарии, где необходимо объяснение.

const { app, BrowserWindow } = require('electron')

function createWindow () {
  // Create the browser window.
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  })

  //load the index.html from a url
  win.loadURL('http://localhost:3000');

  // Open the DevTools.
  win.webContents.openDevTools()
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(createWindow)

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

а затем откройте файл package.json и добавьте приведенный ниже код.

  "private": true,
  "main":"public/main.js",

Теперь мы готовы установить Electron, открыть терминал и запустить:

npm i electron

Теперь давайте добавим запись в раздел сценариев в файле package.json, чтобы запустить приложение реакции на Electron.

файл package.json должен выглядеть так, как показано ниже.

{
  "name": "my-first-electron-app",
  "version": "0.1.0",
  "private": true,
  "main":"public/main.js",
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "electron-dev": "electron ."
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

Теперь пришло время запустить электронное приложение. Поскольку Electron эмулирует реагирующее приложение, нам сначала нужно запустить реагирующее приложение.

Откройте экземпляр терминала и запустите:

npm start

Как только приложение реакции будет запущено и запущено в браузере, откройте другой терминал и запустите:

npm run electron-dev

Вы должны увидеть собственное окно рабочего стола, в котором запущено ваше самое первое электронное приложение!

Основной процесс

Приложение Electron имеет основной процесс, который создает графический интерфейс, порождая BrowserWindows. Каждый BrowserWindow запускает свой изолированный процесс рендеринга и уничтожается при закрытии BrowserWindow. Когда мы запускаем команду npm run electron-dev (запускает electron . под капотом), запускается основной процесс и инициализирует электронную среду.

Затем основной процесс ищет основную запись в файле package.json и запускает основной файл.

В файле main.js Electron проверяет, было ли уже запущено событие ready.

Готовое событие

Первое событие, которое запускается. Он срабатывает после завершения инициализации Electron. Чтобы проверить, запущено ли событие ready, используется функция isReady() или whenReady().

Функция whenReady() возвращает выполненное обещание только тогда, когда событие готовности было запущено.

app.whenReady().then(createWindow)

Если функция whenReady() возвращает выполненное обещание, выполняется функция createWindow(). Функция createWindow() создает новый экземпляр BrowserWindow и загружает в него веб-страницу.

function createWindow () {
// Create the browser window.
   const win = new BrowserWindow({
   width: 800,
   height: 600,
   webPreferences: {
       nodeIntegration: true
      }
})
// load index.html running on the url
win.loadURL('http://localhost:3000');
// Open the DevTools.
win.webContents.openDevTools()
}

Вот и все, что касается основного процесса, давайте поговорим о процессе рендерера.

Процесс визуализации

Каждый процесс рендерера заботится о запущенной в нем веб-странице. BrowserWindowищет HTML-файл. В нашем случае файл index.html приложения-реакции работает на порту 3000. При передаче URL-адреса html-файла в loadURL(), Electron загружает ее в BrowserWindow.

win.loadURL('http://localhost:3000');

Если все BrowserWindows закрыты, сигнализируя об операции выхода из приложения, событие window-all-closed запускается сразу после закрытия последнего экземпляра BrowserWindow. Функция quit() из модуля приложения завершает работу приложения.

Событие закрытия окна

Событие запускается, когда закрываются все BrowserWindows.

app.on('window-all-closed', () => {
   if (process.platform !== 'darwin') {
      app.quit()
   }
})

Однако в macOS (darwin) приложение может оставаться активным в доке до тех пор, пока пользователь явно не закроет его, поэтому мы можем оставить приложение активным в доке.

Межпроцессное взаимодействие (IPC)

Мы узнали о основномпроцессе и обработчике.

Взаимодействие между процессами относится к механизму, предоставляемому операционной системой, который позволяет двум процессам взаимодействовать друг с другом.

Electron требует, чтобы основной процесс создавал/обрабатывал BrowserWindows, каждый из которых запускал свой собственный процесс рендеринга, который запускает веб-страницу. Таким образом, если процессу рендеринга необходимо внести некоторые изменения в графический интерфейс, используя собственный API, он может напрямую импортировать необходимые собственные модули из Electron и вызывать их по мере необходимости. Верно?

Неправильный! Процесс рендеринга запускает сторонние библиотеки/веб-страницы, поэтому вызов собственных API-интерфейсов GUI может быть фатальным и может привести к утечке данных.

Если процессу рендеринга необходимо выполнить операцию с графическим интерфейсом на веб-странице, он должен взаимодействовать с основнымпроцессом, который, в свою очередь, может работать только после проверки того, действительно ли действителен собственный запрос-операции.

Связь между основным процессом и процессом визуализации может быть синхронной или асинхронной в зависимости от требований.

Процесс рендеринга отправляет запрос основному процессу по указанному каналу на выполнение нативной операции с некоторыми аргументами, и основной процесс выполняет запрос, асинхронно выполняя операцию на нативном API с аргументами на веб-странице.

на () и один раз ()

Оба модуля ipcMain и ipcRenderer имеют методы 'on' и 'once' для прослушивания событий на определенном канале. .

//channel:string and listener:callback with event and args as function parameters// main process
ipcMain.on(channel, listener)
ipcMain.once(channel, listener)// renderer process
ipcRenderer.on(channel,listener) 
ipcRenderer.once(channel,listener)

Когда новое сообщение поступает «на» по указанному каналу, обратный вызов (прослушиватель) будет выполняться с переданными параметрами.

Аналогичным образом ведет себя и функция once, с той лишь разницей, что она удаляется после однократного прослушивания события.

отправлять()

В модуле ipcRenderer есть метод send’ для отправки сообщений в указанный канал.

// channel:string(channel name), 
// args to be sent through the channel
// to the main process.
ipcRenderer.send(channel,...args)

removeListener() и removeAllListeners()

Бывают ситуации, когда многие процессы рендеринга подписываются на канал основного процесса, вы можете захотеть удалить одного или всех слушателей сразу для повышения производительности. Модуль ipcMain поставляется с методами removeListener и removeAllListeners, которые удаляют указанного прослушивателя и всех прослушивателей из канала соответственно. .

// removes a specific listener from a specified channel
removeListener(channel,listener)// removes all listeners from a channel
removeAllListeners([channel])

Асинхронный обмен сообщениями

Предположим, что вы хотите вызвать собственный API из процесса рендеринга, вы, естественно, сделаете вызов IPC к основному процессу, поскольку собственные API недоступны в процессе рендерера. Но что, если запрошенная нативная операция тяжелая? В этом случае вызов IPC в конечном итоге заблокирует пользовательский интерфейс до тех пор, пока тяжелая нативная операция не завершит свое выполнение. Неблокирующая асинхронная функция send решает эту проблему.

// main process(main.js)
const { ipcMain } = require('electron')

ipcMain.on('anything-asynchronous', (event, arg) => {
//execute tasks on behalf of renderer process 
    console.log(arg) // prints "ping"
})

// renderer process(react-component/App.js)
const { ipcRenderer } = require('electron')
  
ipcRenderer.send('anything-asynchronous', 'ping')

Основной процесс также может отправлять ответ асинхронно с некоторыми метаданными после выполнения запроса.

// main process(main.js)
const { ipcMain } = require('electron')

ipcMain.on('anything-asynchronous', (event, arg) => {
console.log("heyyyy",arg) // prints "heyyyy ping"
    event.reply('asynchronous-reply', 'pong')
})


// renderer process(react-component/App.js)
const { ipcRenderer } = require('electron')

ipcRenderer.send('anything-asynchronous', 'ping')
ipcRenderer.on('asynchronous-reply', (event, arg) => {
    console.log("Hiii",arg) // prints "Hiii pong"
})

Синхронный обмен сообщениями

Могут быть ситуации, когда некоторые действия пользователя могут потребовать немедленного изменения пользовательского интерфейса. Для всех таких срочных триггеров в пользовательском интерфейсе можно использовать синхронный способ обмена.

Модуль ipcRenderer поставляется с функцией 'sendSync', которая ведет себя точно так же, как функция 'send', но ведет себя синхронно. . Это также означает, что любые запросы, отправляемые синхронно, должны быть легкими с точки зрения использования ресурсов, поскольку в противном случае это может заблокировать пользовательский интерфейс от беспрепятственного рендеринга.

//main process
const { ipcMain } = require('electron')

ipcMain.on('anything-synchronous', (event, arg) => {
      console.log(arg) // prints "ping"
      event.returnValue = 'pong' // returns a value to renderer process
})


//renderer process
const { ipcRenderer } = require('electron')

console.log(ipcRenderer.sendSync('anything-synchronous', 'ping')) // prints "pong"

Давайте попробуем реализовать то, что мы уже узнали, на React.

Давайте посмотрим на IPC в действии с компонентом React.

Мы будем отправлять сообщения основному процессу из реагирующего компонента по нажатию кнопки как синхронно, так и асинхронно.

Давайте создадим две кнопки в компоненте App. Каждый для синхронной и асинхронной отправки строки в основной процесс.

App.js

import React from 'react';
import './App.css';
const { ipcRenderer } = window.require('electron');

function App() {
            return (
                <div className="App">
              
                    <button onClick={()=>{
              
                        ipcRenderer.send('anything-asynchronous', 'ping')
              
                    }}>Async</button>


                    <button onClick={()=>{
                                     
                        // prints "pong"         
                        console.log(ipcRenderer.sendSync('anything-synchronous', 'pong')) 
                                     
                    
                    }}>Sync</button>

                </div>
            );
        }

export default App;

В файле main.js создайте функцию 'on' для прослушивания сообщений, отправленных на 'что-либо-синхронное» и «все-асинхронные».

main.js

ipcMain.on('anything-asynchronous', (event, arg) => {
    // gets triggered by the async button defined in the App component
    console.log("async",arg) // prints "async ping"
})

// gets triggered by the sync button defined in the App component
ipcMain.on('anything-synchronous', (event, arg) => {
    console.log("sync",arg) // prints "sync ping"
})

Вот как это будет выглядеть.

Давайте также отправим ответ процессу рендеринга (компоненту приложения) из основного процесса как синхронно, так и асинхронно.

Асинхронный ответ

Для отправки ответа на асинхронное сообщение используется метод event.reply.

Асинхронный ответ отправляется по другому каналу, чтобы метаданные, переданные в качестве ответа, не засоряли основной канал, где собственные запросы выполняются процессами средства визуализации.

main.js

ipcMain.on('anything-asynchronous', (event, arg) => {
    console.log("async",arg) // prints "async ping"
    event.reply('asynchronous-reply', 'pong')
})

App.js

Поскольку ответ отправляется основным процессом по другому каналу, процесс рендеринга должен прослушивать его, чтобы принять ответ.

Метод «on» используется с прослушивателем (обратным вызовом), который перехватывает ответ в параметре «arg», передаваемом слушателю.

<button onClick={()=>{
    ipcRenderer.send('anything-asynchronous', 'ping')
    // reply
    ipcRenderer.on('asynchronous-reply', (event, arg) => {
    console.log("Hiii",arg) // prints "Hiii pong"
    })
}}>
  
Async

</button>

Синхронный ответ

Чтобы отправить ответ синхронно, задайте для параметра event.returnValue данные, которые будут передаваться в качестве ответа.

main.js

ipcMain.on('anything-synchronous', (event, arg) => {
    console.log("sync",arg) // prints "sync ping"
    //reply
    event.returnValue = 'pong'
})

App.js

А вот как вы поймаете это в процессе рендеринга, т.е. в компоненте приложения.

<button onClick={()=>{
    console.log('sync',ipcRenderer.sendSync('anything-synchronous',   'ping')) 
    // prints "sync pong"
}}>
Sync
</button>

Вот как это будет выглядеть.

Слева — процесс рендеринга, запускающий компонент приложения, справа — основной процесс. В консоли браузера вы можете увидеть сообщения, отправленные основным процессом в ответ как на асинхронные, так и на синхронные сообщения, отправленные процессом визуализации.

Вот это Народ!!!!

Большое спасибо за ваше время для чтения этого!!!!!