You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Electron + React 项目集成 Serialport 包报错无法运行问题咨询

React + Electron + Serialport 可行集成方案

错误根源说明

你遇到的exists is not a function报错,本质是在React运行的Electron渲染进程上下文中直接引入了serialport原生模块。渲染进程默认没有Node.js原生API访问权限,React构建工具也会对Node原生模块做打包处理,导致serialport依赖的fs模块方法缺失,和serialport本身的兼容性无关。

核心设计原则

所有和串口操作相关的逻辑全部放在Electron主进程执行,React渲染进程仅通过IPC通道和主进程通信收发串口数据,不要在React代码中直接引入或调用serialport。

具体操作步骤

  • 第一步:配置Electron预加载脚本与基础权限
    关闭渲染进程的Node.js集成,开启上下文隔离(Electron官方推荐的安全配置),同时在预加载脚本(preload.js)里暴露IPC通信的方法给渲染进程:
    // preload.js 示例
    const { contextBridge, ipcRenderer } = require('electron')
    contextBridge.exposeInMainWorld('serialApi', {
      // 获取串口列表
      listPorts: () => ipcRenderer.invoke('serial:list-ports'),
      // 打开指定串口
      openPort: (path, options) => ipcRenderer.invoke('serial:open', path, options),
      // 监听串口返回数据
      onPortData: (callback) => ipcRenderer.on('serial:data', (_, data) => callback(data)),
      // 向串口写入数据
      writePort: (data) => ipcRenderer.invoke('serial:write', data),
      // 关闭当前串口
      closePort: () => ipcRenderer.invoke('serial:close')
    })
    
  • 第二步:主进程实现串口操作逻辑
    直接在主进程代码中引入serialport,注册对应的IPC监听事件:
    // 主进程 main.js 示例
    const path = require('path')
    const { app, BrowserWindow, ipcMain } = require('electron')
    const { SerialPort } = require('serialport')
    let mainWindow
    let activePort = null
    function createWindow() {
      mainWindow = new BrowserWindow({
        width: 1000,
        height: 700,
        webPreferences: {
          preload: path.join(__dirname, 'preload.js'),
          contextIsolation: true,
          nodeIntegration: false
        }
      })
      // 开发环境加载React本地开发服务,生产环境加载构建后的静态页面
      const loadUrl = process.env.NODE_ENV === 'development' 
        ? 'http://localhost:3000' 
        : `file://${path.join(__dirname, '../build/index.html')}`
      mainWindow.loadURL(loadUrl)
    }
    app.whenReady().then(createWindow)
    // 注册串口相关IPC处理事件
    ipcMain.handle('serial:list-ports', async () => {
      return await SerialPort.list()
    })
    ipcMain.handle('serial:open', async (_, path, options) => {
      if (activePort?.isOpen) await activePort.close()
      activePort = new SerialPort({ path, ...options })
      activePort.on('data', (data) => {
        mainWindow.webContents.send('serial:data', data.toString())
      })
      return new Promise((resolve, reject) => {
        activePort.on('open', resolve)
        activePort.on('error', reject)
      })
    })
    ipcMain.handle('serial:write', async (_, data) => {
      if (!activePort?.isOpen) throw new Error('串口未打开')
      return activePort.write(data)
    })
    ipcMain.handle('serial:close', async () => {
      if (activePort?.isOpen) await activePort.close()
      activePort = null
      return true
    })
    
  • 第三步:React代码调用暴露的串口API
    不需要在React项目依赖中安装serialport,直接调用预加载脚本暴露的window.serialApi即可:
    // React组件示例
    import { useEffect, useState } from 'react'
    function App() {
      const [portList, setPortList] = useState([])
      const [receivedData, setReceivedData] = useState('')
      const [sendContent, setSendContent] = useState('')
      useEffect(() => {
        // 页面初始化加载串口列表
        window.serialApi.listPorts().then(setPortList)
        // 注册串口数据监听
        window.serialApi.onPortData((data) => {
          setReceivedData(prev => prev + data)
        })
      }, [])
      const handleOpenPort = async (portPath) => {
        try {
          await window.serialApi.openPort(portPath, { baudRate: 9600 })
          alert('串口打开成功')
        } catch (e) {
          alert(`串口打开失败:${e.message}`)
        }
      }
      const handleSend = async () => {
        try {
          await window.serialApi.writePort(sendContent)
          setSendContent('')
        } catch (e) {
          alert(`发送失败:${e.message}`)
        }
      }
      return (
        <div style={{ padding: '20px' }}>
          <h2>串口列表</h2>
          {portList.map(port => (
            <button 
              key={port.path} 
              onClick={() => handleOpenPort(port.path)}
              style={{ margin: '0 10px 10px 0', padding: '8px 12px' }}
            >
              打开{port.path}
            </button>
          ))}
          <div style={{ margin: '20px 0' }}>
            <h3>收到的数据</h3>
            <div style={{ border: '1px solid #ccc', padding: '10px', minHeight: '100px' }}>
              {receivedData}
            </div>
          </div>
          <div>
            <h3>发送数据</h3>
            <input 
              value={sendContent} 
              onChange={(e) => setSendContent(e.target.value)}
              style={{ padding: '8px', width: '300px', marginRight: '10px' }}
            />
            <button onClick={handleSend} style={{ padding: '8px 12px' }}>发送</button>
          </div>
        </div>
      )
    }
    export default App
    

额外注意事项

  • 如果serialport安装后运行报错需要重建,使用electron-rebuild工具重建即可,重建针对主进程依赖模块生效,不需要修改React的构建配置
  • 不要修改React构建配置强行把serialport打包到渲染进程,既不符合安全规范也会引发各类兼容问题
  • TypeScript项目可以给window.serialApi添加全局类型声明,避免类型报错

内容的提问来源于stack exchange,提问作者Peter Kusza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 06:15:07