Electron通信流程

前言       

 今天讲Electron框架的通信流程,首先我们需要知道为什么需要通信。这得益于Electron的多进程模型,它主要模仿chrome的多进程模型如下图:

chrome多进程模型

作为应用开发者,我们将控制两种类型的进程:主进程和渲染器进程 。

主进程、渲染进程和Preload脚本

主进程

每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。

除此之外,主进程还可通过BrowserWindow类的每个实例创建一个应用程序窗口,且在单独的渲染器进程中加载一个网页。

const { BrowserWindow } = require('electron')const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')const contents = win.webContents
console.log(contents)

渲染进程

每个 Electron 应用都会为每个打开的 BrowserWindow ( 与每个网页嵌入 ) 生成一个单独的渲染器进程。 洽如其名,渲染器负责 渲染 网页内容。 所以实际上,运行于渲染器进程中的代码是须遵照网页标准的 (至少就目前使用的 Chromium 而言是如此) 。

因此,一个浏览器窗口中的所有的用户界面和应用功能,都应与您在网页开发上使用相同的工具和规范来进行攥写。

此外,这也意味着渲染器无权直接访问 require 或其他 Node.js API。 为了在渲染器中直接包含 NPM 模块,您必须使用与在 web 开发时相同的打包工具 (例如 webpack 或 parcel)

既然如此,那渲染器进程用户界面怎样才能与 Node.js 和 Electron 的原生桌面功能进行交互呢?这用到了Preload脚本

Preload脚本

预加载(preload)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。 这些脚本虽运行于渲染器的环境中,却因能访问 Node.js API 而拥有了更多的权限。

预加载脚本可以在 BrowserWindow 构造方法中的 webPreferences 选项里被附加到主进程。

const { BrowserWindow } = require('electron')
// ...
const win = new BrowserWindow({webPreferences: {preload: 'path/to/preload.js'}
})
// ...

 因为预加载脚本与浏览器共享同一个全局 Window 接口,并且可以访问 Node.js API,所以它通过在全局 window 中暴露任意 API 来增强渲染器,以便你的网页内容使用。

// preload.js
window.myAPI = {desktop: true
}// renderer.js
console.log(window.myAPI)
// => undefined

语境隔离(Context Isolation)意味着预加载脚本与渲染器的主要运行环境是隔离开来的,以避免泄漏任何具特权的 API 到您的网页内容代码中。我们可以使用contextBridge方法安全的暴露api

// preload.js 
const { contextBridge } = require('electron')contextBridge.exposeInMainWorld('myAPI', {desktop: true
})// renderer.js
console.log(window.myAPI)
// => { desktop: true }

IPC通道

通过ipcMain和ipcRenderer模块可以创建任意 (您可以随意命名它们)和 双向 (您可以在两个模块中使用相同的通道名称)的通道。

模式 1:渲染器进程到主进程(单向)​

通常使用此模式从 Web 内容调用主进程 API。 我们将通过创建一个简单的应用来演示此模式,可以通过编程方式更改它的窗口标题。

对于此演示,您需要将代码添加到主进程、渲染器进程和预加载脚本。 完整代码如下:

// main.js
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')function createWindow () {const mainWindow = new BrowserWindow({webPreferences: {preload: path.join(__dirname, 'preload.js')}})// 使用ipcMain.on监听事件ipcMain.on('set-title', (event, title) => {const webContents = event.senderconst win = BrowserWindow.fromWebContents(webContents)win.setTitle(title)})mainWindow.loadFile('index.html')
}app.whenReady().then(() => {createWindow()app.on('activate', function () {if (BrowserWindow.getAllWindows().length === 0) createWindow()})
})app.on('window-all-closed', function () {if (process.platform !== 'darwin') app.quit()
})// preload.js
const { contextBridge, ipcRenderer } = require('electron/renderer')//通过预加载脚本暴露ipcRenderer.send
contextBridge.exposeInMainWorld('electronAPI', {setTitle: (title) => ipcRenderer.send('set-title', title)
})// renderer.js
const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {const title = titleInput.valuewindow.electronAPI.setTitle(title)
})
// index.html
// 构建渲染器进程UI
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --><meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"><title>Hello World!</title></head><body>Title: <input id="title"/><button id="btn" type="button">Set</button><script src="./renderer.js"></script></body>
</html>

模式 2:渲染器进程到主进程(双向)​

双向 IPC 的一个常见应用是从渲染器进程代码调用主进程模块并等待结果。 可以通过 ipcRenderer.invoke和  ipcMain.handle.实现。

在下面的示例中,我们将从渲染器进程打开一个原生的文件对话框,并返回所选文件的路径。完整代码如下:

// main.js 
const { app, BrowserWindow, ipcMain, dialog } = require('electron/main')
const path = require('node:path')async function handleFileOpen () {const { canceled, filePaths } = await dialog.showOpenDialog()if (!canceled) {return filePaths[0]}
}function createWindow () {const mainWindow = new BrowserWindow({webPreferences: {preload: path.join(__dirname, 'preload.js')}})mainWindow.loadFile('index.html')
}app.whenReady().then(() => {// 使用ipcMain.handle监听事件ipcMain.handle('dialog:openFile', handleFileOpen)createWindow()app.on('activate', function () {if (BrowserWindow.getAllWindows().length === 0) createWindow()})
})app.on('window-all-closed', function () {if (process.platform !== 'darwin') app.quit()
})// preload.js
const { contextBridge, ipcRenderer } = require('electron/renderer')
// 预加载脚本暴露ipcRenderer.invoke
contextBridge.exposeInMainWorld('electronAPI', {openFile: () => ipcRenderer.invoke('dialog:openFile')
})// renderer.js 
const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')btn.addEventListener('click', async () => {const filePath = await window.electronAPI.openFile()filePathElement.innerText = filePath
})
// index.html
// 构建渲染器进程UI
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --><meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"><title>Dialog</title></head><body><button type="button" id="btn">Open a File</button>File path: <strong id="filePath"></strong><script src='./renderer.js'></script></body>
</html>

模式 3:主进程到渲染器进程​

将消息从主进程发送到渲染器进程时,需要指定是哪一个渲染器接收消息。主要通过WebContents实例和send方法实现

为了演示此模式,我们将构建一个由原生操作系统菜单控制的数字计数器。 

对于此演示,您需要将代码添加到主进程、渲染器进程和预加载脚本。 完整代码如下:

// main.js
const { app, BrowserWindow, Menu, ipcMain } = require('electron/main')
const path = require('node:path')function createWindow () {const mainWindow = new BrowserWindow({webPreferences: {preload: path.join(__dirname, 'preload.js')}})const menu = Menu.buildFromTemplate([{label: app.name,submenu: [{// 使用webContents模块发送信息click: () => mainWindow.webContents.send('update-counter', 1),label: 'Increment'},{// 使用webContents模块发送信息click: () => mainWindow.webContents.send('update-counter', -1),label: 'Decrement'}]}])Menu.setApplicationMenu(menu)mainWindow.loadFile('index.html')// Open the DevTools.mainWindow.webContents.openDevTools()
}app.whenReady().then(() => {ipcMain.on('counter-value', (_event, value) => {console.log(value) // will print value to Node console})createWindow()app.on('activate', function () {if (BrowserWindow.getAllWindows().length === 0) createWindow()})
})app.on('window-all-closed', function () {if (process.platform !== 'darwin') app.quit()
})// preload.js
const { contextBridge, ipcRenderer } = require('electron/renderer')
// 通过预加载脚本暴露ipcRenderer.on
contextBridge.exposeInMainWorld('electronAPI', {onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),counterValue: (value) => ipcRenderer.send('counter-value', value)
})// renderer.js
const counter = document.getElementById('counter')window.electronAPI.onUpdateCounter((value) => {const oldValue = Number(counter.innerText)const newValue = oldValue + valuecounter.innerText = newValue.toString()window.electronAPI.counterValue(newValue)
})
// index.html
// 构建渲染器UI
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --><meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"><title>Menu Counter</title></head><body>Current value: <strong id="counter">0</strong><script src="./renderer.js"></script></body>
</html>

模式 4:渲染器进程到渲染器进程​

没有直接的方法可以使用 ipcMain 和 ipcRenderer 模块在 Electron 中的渲染器进程之间发送消息。 为此,您有两种选择:

  • 将主进程作为渲染器之间的消息代理。 这需要将消息从一个渲染器发送到主进程,然后主进程将消息转发到另一个渲染器。
  • 通过MessagePort构建通信通道, 这将允许在初始设置后渲染器之间直接进行通信。

消息端口

简介

MessagePort是一个允许在不同上下文之间传递消息的Web功能。 就像 window.postMessage, 但是在不同的通道上。

下面是 MessagePort 是什么和如何工作的一个非常简短的例子:

// renderer.js
// 消息端口是成对创建的。 连接的一对消息端口
// 被称为通道。
const channel = new MessageChannel()// port1 和 port2 之间唯一的不同是你如何使用它们。 消息
// 发送到port1 将被port2 接收,反之亦然。
const port1 = channel.port1
const port2 = channel.port2// 允许在另一端还没有注册监听器的情况下就通过通道向其发送消息
// 消息将排队等待,直到一个监听器注册为止。
port2.postMessage({ answer: 42 })// 这次我们通过 ipc 向主进程发送 port1 对象。 类似的,
// 我们也可以发送 MessagePorts 到其他 frames, 或发送到 Web Workers, 等.
ipcRenderer.postMessage('port', null, [port1])
// main.js
// 在主进程中,我们接收端口对象。
ipcMain.on('port', (event) => {// 当我们在主进程中接收到 MessagePort 对象, 它就成为了// MessagePortMain.const port = event.ports[0]// MessagePortMain 使用了 Node.js 风格的事件 API, 而不是// web 风格的事件 API. 因此使用 .on('message', ...) 而不是 .onmessage = ...port.on('message', (event) => {// 收到的数据是: { answer: 42 }const data = event.data})// MessagePortMain 阻塞消息直到 .start() 方法被调用port.start()
})

实例使用

在两个渲染进程之间建立 MessageChannel​

在这个示例中,主进程设置了一个MessageChannel,然后将每个端口发送给不同的渲染进程。 这样可以让渲染进程彼此之间发送消息,而无需使用主进程作为中转。

// main.js 
const { BrowserWindow, app, MessageChannelMain } = require('electron')app.whenReady().then(async () => {// 创建窗口const mainWindow = new BrowserWindow({show: false,webPreferences: {contextIsolation: false,preload: 'preloadMain.js'}})const secondaryWindow = new BrowserWindow({show: false,webPreferences: {contextIsolation: false,preload: 'preloadSecondary.js'}})// 建立通道const { port1, port2 } = new MessageChannelMain()// webContents准备就绪后,使用postMessage向每个webContents发送一个端口。mainWindow.once('ready-to-show', () => {mainWindow.webContents.postMessage('port', null, [port1])})secondaryWindow.once('ready-to-show', () => {secondaryWindow.webContents.postMessage('port', null, [port2])})
})

接下来,在你的预加载脚本中通过IPC接收端口,并设置相应的监听器

// preloadMain.js 和 preloadSecondary.js
const { ipcRenderer } = require('electron')ipcRenderer.on('port', e => {// 接收到端口,使其全局可用。window.electronMessagePort = e.ports[0]window.electronMessagePort.onmessage = messageEvent => {// 处理消息}
})

这意味着 window.electronMessagePort 在全局范围内可用,你可以在应用程序的任何地方调用postMessage 方法,以便向另一个渲染进程发送消息。

// renderer.js
// elsewhere in your code to send a message to the other renderers message handler
window.electronMessagePort.postMessage('ping')
Worker进程​

在这个示例中,你的应用程序有一个作为隐藏窗口存在的 Worker 进程。 你希望应用程序页面能够直接与 Worker 进程通信,而不需要通过主进程进行中继,以避免性能开销。

 

// main.js
const { BrowserWindow, app, ipcMain, MessageChannelMain } = require('electron')app.whenReady().then(async () => {// Worker 进程是一个隐藏的 BrowserWindow// 它具有访问完整的Blink上下文(包括例如 canvas、音频、fetch()等)的权限const worker = new BrowserWindow({show: false,webPreferences: { nodeIntegration: true }})await worker.loadFile('worker.html')// main window 将发送内容给 worker process 同时通过 MessagePort 接收返回值const mainWindow = new BrowserWindow({webPreferences: { nodeIntegration: true }})mainWindow.loadFile('app.html')// 在这里我们不能使用 ipcMain.handle() , 因为回复需要传输// MessagePort.// 监听从顶级 frame 发来的消息mainWindow.webContents.mainFrame.ipc.on('request-worker-channel', (event) => {// 建立新通道  ...const { port1, port2 } = new MessageChannelMain()// ... 将其中一个端口发送给 Worker ...worker.webContents.postMessage('new-client', null, [port1])// ... 将另一个端口发送给主窗口event.senderFrame.postMessage('provide-worker-channel', null, [port2])// 现在主窗口和工作进程可以直接相互通信,无需经过主进程!})
})
// worker.html
<script>
const { ipcRenderer } = require('electron')const doWork = (input) => {// 一些对CPU要求较高的任务return input * 2
}// 我们可能会得到多个 clients, 比如有多个 windows,
// 或者假如 main window 重新加载了.
ipcRenderer.on('new-client', (event) => {const [ port ] = event.portsport.onmessage = (event) => {// 事件数据可以是任何可序列化的对象 (事件甚至可以// 携带其他 MessagePorts 对象!)const result = doWork(event.data)port.postMessage(result)}
})
</script>
// app.html
<script>
const { ipcRenderer } = require('electron')// 我们请求主进程向我们发送一个通道
// 以便我们可以用它与 Worker 进程建立通信
ipcRenderer.send('request-worker-channel')ipcRenderer.once('provide-worker-channel', (event) => {// 一旦收到回复, 我们可以这样做...const [ port ] = event.ports// ... 注册一个接收结果处理器 ...port.onmessage = (event) => {console.log('received result:', event.data)}// ... 并开始发送消息给 work!port.postMessage(21)
})
</script>
 回复流

Electron的内置IPC方法只支持两种模式:即发即弃(例如, send),或请求-响应(例如, invoke)。 使用MessageChannels,你可以实现一个“响应流”,其中单个请求可以返回一串数据。

// renderer.js
const makeStreamingRequest = (element, callback) => {// MessageChannels 是轻量的// 为每个请求创建一个新的 MessageChannel 带来的开销并不大const { port1, port2 } = new MessageChannel()// 我们将端口的一端发送给主进程 ...ipcRenderer.postMessage('give-me-a-stream',{ element, count: 10 },[port2])// ... 保留另一端。 主进程将向其端口发送消息// 并在完成后关闭它port1.onmessage = (event) => {callback(event.data)}port1.onclose = () => {console.log('stream ended')}
}makeStreamingRequest(42, (data) => {console.log('got response data:', data)
})
// 我们会看到 "got response data: 42" 出现了10次
// main.js
ipcMain.on('give-me-a-stream', (event, msg) => {// 渲染进程向我们发送了一个 MessagePort// 并期望得到响应const [replyPort] = event.ports// 在这里,我们同步发送消息// 我们也可以将端口存储在某个地方,异步发送消息for (let i = 0; i < msg.count; i++) {replyPort.postMessage(msg.element)}// 当我们处理完成后,关闭端口以通知另一端// 我们不会再发送任何消息 这并不是严格要求的// 如果我们没有显式地关闭端口,它最终会被垃圾回收// 这也会触发渲染进程中的'close'事件replyPort.close()
})

总结:

  • 主进程和渲染器进程之间存在语境隔离
  • 主进程和渲染器进程可通过electron内置IPC通道进行通信
  • 渲染器之间可通过IPC通道,主进程为中间人互相通信;也可通过信息端口构建信道直接进行通信

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/84559.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uni-app项目实战笔记1--创建项目和实现首页轮播图功能

ps:本笔记来自B站咸虾米壁纸项目 一.创建项目&#xff0c;完成项目初始化搭建 1.在HBuilder X创建wallper项目&#xff0c;使用默认模块&#xff0c;选择vue&#xff1b; 2.在项目根目录下创建common目录&#xff0c;用于存放静态资源&#xff0c;创建项目时自动生成static目…

机械制造系统中 PROFINET 与 PROFIBUS-DP 的融合应用及捷米科技解决方案

在机械制造领域&#xff0c;工业通信网络的兼容性与灵活性直接影响产线的自动化水平与生产效率。当前&#xff0c;多数机械制造系统采用PROFINET 控制器构建核心网络架构&#xff0c;并通过微波无线连接实现设备互联。随着工业网络的发展&#xff0c;系统中常需同时集成PROFINE…

MCP 协议系列序言篇:开启 AI 应用融合新时代的钥匙

文章目录 序言&#xff1a;AI 应用层进入 MCP 时代为什么 MCP 开启 AI 应用融合新时代的钥匙为什么是 MCP&#xff1f;它与 Function Calling、Agent 有什么区别&#xff1f;Function CallingAI AgentMCP&#xff08;Model Context Protocol&#xff09; MCP 如何工作MCP Serve…

【threejs】每天一个小案例讲解:光照

代码仓 GitHub - TiffanyHoo/three_practices: Learning three.js together! 可自行clone&#xff0c;无需安装依赖&#xff0c;直接liver-server运行/直接打开chapter01中的html文件 运行效果图 知识要点 常见光照类型及其特点如下&#xff1a; 1. 环境光&#xff08;Ambi…

大模型在输尿管下段积水预测及临床应用的研究

目录 一、引言 1.1 研究背景与意义 1.2 研究目的 1.3 研究范围与限制 1.4 文献综述 1.5 研究方法和框架 二、相关理论与概念 2.1 大模型技术原理 2.2 输尿管下段积水病理机制 2.3 大模型在医学预测领域的应用 三、大模型预测输尿管下段积水的方法 3.1 数据收集 3.…

gitlab相关操作

2025.06.11今天我学习了如何在终端使用git相关操作&#xff1a; 一、需要修改新的仓库git地址的时候&#xff1a; &#xff08;1&#xff09;检查当前远程仓库 git remote -v 输出示例&#xff1a; origin https://github.com/old-repo.git (fetch) origin https://github.c…

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…

通过共享内存在多程序之间实现数据通信

注&#xff1a;以下内容为与 GPT-4O 共同创作完成 以共享内存的方式实现多程序之间的数据通信&#xff0c;尤其适合在一台机器上的多程序之间进行高频数据交换。 以下示例展示了 sender.py 向 receiver.py 发送数据并接收经 receiver.py 处理后的数据&#xff0c;以及如何通过…

[论文阅读] 人工智能+软件工程 | 理解GitGoodBench:评估AI代理在Git中表现的新基准

理解GitGoodBench&#xff1a;评估AI代理在Git中表现的新基准 论文信息 GitGoodBench: A Novel Benchmark For Evaluating Agentic Performance On Git Tobias Lindenbauer, Egor Bogomolov, Yaroslav Zharov Cite as: arXiv:2505.22583 [cs.SE] 研究背景&#xff1a;当AI走进…

开源 java android app 开发(十二)封库.aar

文章的目的为了记录使用java 进行android app 开发学习的经历。本职为嵌入式软件开发&#xff0c;公司安排开发app&#xff0c;临时学习&#xff0c;完成app的开发。开发流程和要点有些记忆模糊&#xff0c;赶紧记录&#xff0c;防止忘记。 相关链接&#xff1a; 开源 java an…

ubuntu + nginx 1.26 + php7.4 + mysql8.0 调优

服务器配置 8核 16G 查看内存 free -h nginx配置 worker_processes auto; # 自动检测CPU核心数 worker_rlimit_nofile 65535; # 提高文件描述符限制 ​ events {worker_connections 8192; # 每个worker的最大连接数multi_accept on; # 一次性接受…

[未验证]abaqus2022 更改内置python

如何在 Abaqus 2022 中更改内置 Python 在 Abaqus 中&#xff0c;Python 是常用的脚本语言&#xff0c;它使得用户能够自动化模型的创建、分析和后处理。可能有时候你需要更改默认的 Python 版本&#xff0c;比如使用特定库或者功能。本文将为您详细说明如何在 Abaqus 2022 中更…

RAG文档解析难点2:excel数据“大海捞针”,超大Excel解析与精准行列查询指南

写在前面 在构建检索增强生成(RAG)应用时,Excel文件是不可或缺的数据源。它们通常包含了企业运营、市场分析、科学研究等各个领域的宝贵数据。然而,当这些Excel文件变得“超大”——可能包含数十万甚至数百万行数据时,传统的解析方法和RAG数据处理流程将面临严峻的内存、…

深度掌控,智启未来 —— 基于 STM32F103RBT6 的控制板

在科技浪潮奔涌向前的时代&#xff0c;电子领域的创新发展从未停歇。对于电子工程师、科研工作者以及电子技术爱好者&#xff0c;在校电子专业学生而言&#xff0c;一款性能卓越、功能全面且稳定可靠的开发板&#xff0c;是探索电子世界奥秘、实现创意构想的关键基石。今天&…

什么样的登录方式才是最安全的?

目录 一、基础协议&#xff1a;HTTP与HTTPS HTTP协议 HTTPS协议 二、常见Web攻击与防御 2.1 XSS 常见攻击手段 针对XSS 攻击窃取 Cookie 2.2 CSRF CSRF攻击的核心特点 与XSS的区别 常见防御措施 三、疑问解答 四、登录方式演变 4.1 方案一&#x1f436;狗都不用 …

android studio底部导航栏

实现底部导航栏切换 将java文件return的xml文件赋值给页面FrameLayout控件 java文件BottomNavigationView&#xff0c;监听器setOnNavigationItemSelectedListener MainActivity.java代码 package com.example.myapplication;import android.os.Bundle;import androidx.appc…

vue-router相关理解

一、前言 随着 Vue.js 在前端开发中的广泛应用&#xff0c;Vue Router 成为了 Vue 官方推荐的路由管理器。它不仅支持单页面应用&#xff08;SPA&#xff09;中常见的路由跳转、嵌套路由、懒加载等功能&#xff0c;还提供了导航守卫、动态路由等高级特性。 本文将带你深入了解…

uni-app 自定义路由封装模块详解(附源码逐行解读)

&#x1f680;uni-app 自定义路由封装模块详解&#xff08;附源码逐行解读&#xff09; &#x1f4cc; 请收藏 点赞 关注&#xff0c;获取更多 uni-app 项目实用技巧&#xff01; 在实际 uni-app 项目中&#xff0c;我们常常需要对 uni.navigateTo、uni.switchTab 等 API 做…

QML显示图片问题解决办法

以前用qtwediget的时候&#xff0c;好像是放在qlabel或者什么组件上面&#xff0c;把图片的路径放上去就可以直接加载&#xff0c;但我用QML创建界面的时候就遇到了问题&#xff0c;哦对&#xff0c;qtwedget用qpixmap组件显示图片&#xff0c;也有image。话说回来&#xff0c;…

Vue中使用jsx

1. jsx的babel配置 1.1 在项目中使用jsx&#xff0c;需要添加对jsx的支持&#xff1a; jsx通常会通过Babel来进行转换(React编写的jsx就是通过babel转换的)Vue中&#xff0c;只需要在Babel中配置对应的插件即可以下列举需要支持转换的案例&#xff1a; template -> vue-l…