返回笔记首页

Flutter&React Native&Electron 对比与实战指南

主题配置

一、技术选型决策

1.1 技术栈对比分析

核心差异对比表

维度 Flutter React Native Electron
开发语言 Dart JavaScript/TypeScript JavaScript/TypeScript
渲染方式 自绘引擎(Skia) 原生组件桥接 Chromium + Node.js
性能 接近原生(60fps) 良好(需优化) 一般(内存占用高)
包体积 15-30MB 20-40MB 100-200MB
学习曲线 陡峭(新语言) 平缓(JS生态) 平缓(Web技术)
热更新 不支持(App Store) 支持(CodePush) 支持(自动更新)
UI 一致性 完全一致 平台差异 完全一致
适用场景 高性能App 快速迭代App 桌面应用
代码复用 仅 Flutter 可与 Web 共用 与 Web 共用

1.2 技术选型决策树

plain
需求分析
    ├─ 平台需求?
    │   ├─ 移动端(iOS/Android)
    │   │   ├─ 高性能要求? → Flutter
    │   │   ├─ 快速迭代? → React Native
    │   │   └─ 团队技术栈? → 根据团队选择
    │   └─ 桌面端(Windows/Mac/Linux)
    │       └─ Electron
    │
    ├─ 性能要求?
    │   ├─ 极致性能 → Flutter / 原生
    │   ├─ 良好性能 → React Native
    │   └─ 可接受性能 → Electron
    │
    └─ 开发成本?
        ├─ 预算充足 → 原生开发
        ├─ 中等预算 → Flutter / React Native
        └─ 紧张预算 → 选择团队熟悉的技术栈

1.3 实战选型案例

案例1: 电商 App 技术选型

需求分析:

  • 平台: iOS + Android
  • 性能: 需要流畅的列表滚动和动画
  • 迭代: 快速迭代,频繁发版
  • 团队: 前端团队,熟悉 React

选型结果: React Native

理由:

  1. 团队熟悉 React,学习成本低
  2. 支持热更新,满足快速迭代需求
  3. 性能满足电商场景(优化后)
  4. 生态丰富,第三方库多

案例2: 金融交易 App 技术选型

需求分析:

  • 平台: iOS + Android
  • 性能: 实时数据展示,复杂图表
  • 安全: 高安全性要求
  • UI: 需要高度定制化

选型结果: Flutter

理由:

  1. 高性能,适合实时数据展示
  2. 自绘引擎,UI 完全可控
  3. AOT 编译,不易被反编译
  4. 动画流畅,用户体验好

案例3: 办公协同软件技术选型

需求分析:

  • 平台: Windows + Mac + Linux
  • 功能: 文件管理,即时通讯
  • 集成: 需要调用系统 API
  • 团队: Web 前端团队

选型结果: Electron

理由:

  1. 跨三大桌面平台
  2. 可以复用 Web 代码
  3. Node.js 可以调用系统 API
  4. 团队熟悉,开发效率高

二、与 Web 代码复用

2.1 React Native 代码复用方案

实现架构

plain
共享层 (Shared)
    ├─ 业务逻辑 (Hooks/Store)
    ├─ 工具函数 (Utils)
    ├─ API 请求 (Services)
    └─ 类型定义 (Types)

平台适配层
    ├─ Web (React)
    │   ├─ 路由 (React Router)
    │   ├─ 组件 (HTML/CSS)
    │   └─ 构建 (Webpack)
    └─ Mobile (React Native)
        ├─ 导航 (React Navigation)
        ├─ 组件 (Native Components)
        └─ 构建 (Metro)

代码复用示例

共享的业务逻辑 Hook:

javascript
// shared/hooks/useUserInfo.js
import { useState, useEffect } from 'react'
import { getUserInfo as getUserInfoApi } from '../services/user'

/**
 * 获取用户信息 Hook
 * 可在 Web 和 RN 中复用
 */
export function useUserInfo() {
  const [userInfo, setUserInfo] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  const getUserInfo = async () => {
    setLoading(true)
    setError(null)

    try {
      const data = await getUserInfoApi()
      setUserInfo(data)
      return data
    } catch (err) {
      setError(err)
      throw err
    } finally {
      setLoading(false)
    }
  }

  useEffect(() => {
    getUserInfo()
  }, [])

  const updateUserInfo = async (updates) => {
    setUserInfo(prev => ({ ...prev, ...updates }))
  }

  return {
    userInfo,
    loading,
    error,
    getUserInfo,
    updateUserInfo
  }
}

// shared/hooks/useRequest.js
import { useState, useCallback } from 'react'

/**
 * 通用请求 Hook
 * 支持 Web 和 RN
 */
export function useRequest(requestFn) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  const run = useCallback(async (...args) => {
    setLoading(true)
    setError(null)

    try {
      const result = await requestFn(...args)
      setData(result)
      return result
    } catch (err) {
      setError(err)
      throw err
    } finally {
      setLoading(false)
    }
  }, [requestFn])

  const reset = useCallback(() => {
    setData(null)
    setError(null)
    setLoading(false)
  }, [])

  return {
    data,
    loading,
    error,
    run,
    reset
  }
}

平台适配的组件:

javascript
// shared/components/Button/Button.web.js
import React from 'react'
import './Button.css'

export const Button = ({ children, onPress, disabled, style }) => {
  return (
    <button
      className="custom-button"
      onClick={onPress}
      disabled={disabled}
      style={style}
    >
      {children}
    </button>
  )
}

// shared/components/Button/Button.native.js
import React from 'react'
import { TouchableOpacity, Text, StyleSheet } from 'react-native'

export const Button = ({ children, onPress, disabled, style }) => {
  return (
    <TouchableOpacity
      style={[styles.button, style, disabled && styles.disabled]}
      onPress={onPress}
      disabled={disabled}
    >
      <Text style={styles.text}>{children}</Text>
    </TouchableOpacity>
  )
}

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#007AFF',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center'
  },
  disabled: {
    opacity: 0.5
  },
  text: {
    color: '#fff',
    fontSize: 16,
    fontWeight: 'bold'
  }
})

// shared/components/Button/index.js
// Metro bundler 会根据平台自动选择文件
export { Button } from './Button'

使用示例:

javascript
// pages/Profile/index.js
// 这个文件可以在 Web 和 RN 中使用
import React from 'react'
import { useUserInfo } from '@/shared/hooks/useUserInfo'
import { Button } from '@/shared/components/Button'
import { Container, Title, Info } from './styles' // 平台特定样式

export const ProfilePage = () => {
  const { userInfo, loading, updateUserInfo } = useUserInfo()

  if (loading) {
    return <Container><Title>加载中...</Title></Container>
  }

  if (!userInfo) {
    return <Container><Title>暂无数据</Title></Container>
  }

  const handleEdit = () => {
    // 业务逻辑,在两个平台都一样
    updateUserInfo({ name: '新名字' })
  }

  return (
    <Container>
      <Title>个人信息</Title>
      <Info>姓名: {userInfo.name}</Info>
      <Info>邮箱: {userInfo.email}</Info>
      <Button onPress={handleEdit}>编辑</Button>
    </Container>
  )
}

2.2 Electron 代码复用方案

实现架构

plain
Electron 应用
    ├─ 主进程 (Main Process)
    │   ├─ 窗口管理
    │   ├─ 系统集成
    │   └─ IPC 通信
    │
    └─ 渲染进程 (Renderer Process)
        ├─ Web 应用代码 (100% 复用)
        ├─ React/Vue 组件
        ├─ 业务逻辑
        └─ 样式文件

代码复用示例

Electron 主进程:

javascript
// electron/main.js
const { app, BrowserWindow, ipcMain, dialog } = require('electron')
const path = require('path')
const fs = require('fs')

let mainWindow

// 创建窗口
function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js')
    }
  })

  // 加载 Web 应用
  if (process.env.NODE_ENV === 'development') {
    mainWindow.loadURL('http://localhost:3000')
  } else {
    mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))
  }

  // 开发工具
  if (process.env.NODE_ENV === 'development') {
    mainWindow.webContents.openDevTools()
  }
}

// 应用启动
app.whenReady().then(createWindow)

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

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})

// IPC 通信 - 文件操作
ipcMain.handle('read-file', async (event, filePath) => {
  try {
    const content = fs.readFileSync(filePath, 'utf-8')
    return { success: true, data: content }
  } catch (err) {
    return { success: false, error: err.message }
  }
})

ipcMain.handle('write-file', async (event, filePath, content) => {
  try {
    fs.writeFileSync(filePath, content, 'utf-8')
    return { success: true }
  } catch (err) {
    return { success: false, error: err.message }
  }
})

// IPC 通信 - 对话框
ipcMain.handle('show-open-dialog', async () => {
  const result = await dialog.showOpenDialog(mainWindow, {
    properties: ['openFile'],
    filters: [
      { name: 'Text Files', extensions: ['txt', 'md'] },
      { name: 'All Files', extensions: ['*'] }
    ]
  })

  return result
})

ipcMain.handle('show-save-dialog', async () => {
  const result = await dialog.showSaveDialog(mainWindow, {
    defaultPath: 'untitled.txt',
    filters: [
      { name: 'Text Files', extensions: ['txt', 'md'] },
      { name: 'All Files', extensions: ['*'] }
    ]
  })

  return result
})

// IPC 通信 - 应用信息
ipcMain.handle('get-app-info', () => {
  return {
    version: app.getVersion(),
    name: app.getName(),
    path: app.getAppPath()
  }
})

Preload 脚本:

javascript
// electron/preload.js
const { contextBridge, ipcRenderer } = require('electron')

// 暴露安全的 API 给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
  // 文件操作
  readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
  writeFile: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content),

  // 对话框
  showOpenDialog: () => ipcRenderer.invoke('show-open-dialog'),
  showSaveDialog: () => ipcRenderer.invoke('show-save-dialog'),

  // 应用信息
  getAppInfo: () => ipcRenderer.invoke('get-app-info'),

  // 监听事件
  onUpdateAvailable: (callback) => {
    ipcRenderer.on('update-available', callback)
  }
})

Web 应用代码(完全复用):

javascript
// src/hooks/useElectronAPI.js
import { useState, useEffect } from 'react'

/**
 * Electron API Hook
 * 在 Web 环境自动降级
 */
export function useElectronAPI() {
  const [isElectron, setIsElectron] = useState(false)
  const [appInfo, setAppInfo] = useState(null)

  useEffect(() => {
    // 检测是否在 Electron 环境
    setIsElectron(!!window.electronAPI)

    if (window.electronAPI) {
      // 获取应用信息
      window.electronAPI.getAppInfo().then(setAppInfo)
    }
  }, [])

  const readFile = async (filePath) => {
    if (!window.electronAPI) {
      throw new Error('Not in Electron environment')
    }
    return window.electronAPI.readFile(filePath)
  }

  const writeFile = async (filePath, content) => {
    if (!window.electronAPI) {
      throw new Error('Not in Electron environment')
    }
    return window.electronAPI.writeFile(filePath, content)
  }

  const openFile = async () => {
    if (!window.electronAPI) {
      // Web 环境降级方案
      return new Promise((resolve) => {
        const input = document.createElement('input')
        input.type = 'file'
        input.onchange = (e) => {
          const file = e.target.files[0]
          if (file) {
            const reader = new FileReader()
            reader.onload = (event) => {
              resolve({
                canceled: false,
                filePaths: [file.name],
                content: event.target.result
              })
            }
            reader.readAsText(file)
          } else {
            resolve({ canceled: true })
          }
        }
        input.click()
      })
    }

    const result = await window.electronAPI.showOpenDialog()
    if (result.canceled) {
      return result
    }

    const content = await readFile(result.filePaths[0])
    return { ...result, content: content.data }
  }

  const saveFile = async (content) => {
    if (!window.electronAPI) {
      // Web 环境降级方案
      const blob = new Blob([content], { type: 'text/plain' })
      const url = URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = url
      a.download = 'file.txt'
      a.click()
      URL.revokeObjectURL(url)
      return { canceled: false }
    }

    const result = await window.electronAPI.showSaveDialog()
    if (result.canceled) {
      return result
    }

    await writeFile(result.filePath, content)
    return result
  }

  return {
    isElectron,
    appInfo,
    readFile,
    writeFile,
    openFile,
    saveFile
  }
}

使用示例:

vue
<!-- src/pages/Editor/index.vue -->
<template>
  <div class="editor-page">
    <div class="toolbar">
      <button @click="handleOpen" class="btn">打开文件</button>
      <button @click="handleSave" class="btn">保存文件</button>
      <span v-if="isElectron" class="app-info">
        {{ appInfo?.name }} v{{ appInfo?.version }}
      </span>
    </div>

    <textarea
      v-model="content"
      class="editor"
      placeholder="开始编辑..."
    ></textarea>

    <div v-if="currentFile" class="status">
      当前文件: {{ currentFile }}
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useElectronAPI } from '@/hooks/useElectronAPI'

const content = ref('')
const currentFile = ref('')

const { isElectron, appInfo, openFile, saveFile } = useElectronAPI()

const handleOpen = async () => {
  try {
    const result = await openFile()

    if (!result.canceled) {
      content.value = result.content
      currentFile.value = result.filePaths[0]
    }
  } catch (err) {
    alert('打开文件失败: ' + err.message)
  }
}

const handleSave = async () => {
  try {
    const result = await saveFile(content.value)

    if (!result.canceled) {
      currentFile.value = result.filePath
      alert('保存成功')
    }
  } catch (err) {
    alert('保存文件失败: ' + err.message)
  }
}
</script>

<style scoped>
.editor-page {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background: #1e1e1e;
}

.toolbar {
  display: flex;
  gap: 10px;
  padding: 15px;
  background: #2d2d2d;
  border-bottom: 1px solid #3d3d3d;
}

.btn {
  padding: 8px 16px;
  background: #0066cc;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.btn:hover {
  background: #0052a3;
}

.app-info {
  margin-left: auto;
  color: #999;
  font-size: 14px;
}

.editor {
  flex: 1;
  padding: 20px;
  background: #1e1e1e;
  color: #d4d4d4;
  border: none;
  font-family: 'Consolas', 'Monaco', monospace;
  font-size: 14px;
  line-height: 1.6;
  resize: none;
}

.editor:focus {
  outline: none;
}

.status {
  padding: 10px 15px;
  background: #2d2d2d;
  border-top: 1px solid #3d3d3d;
  color: #999;
  font-size: 12px;
}
</style>

三、性能对比分析

3.1 性能测试方案

测试场景设计

javascript
// performance-test/scenarios.js

/**
 * 性能测试场景
 */
export const testScenarios = [
  {
    name: '长列表滚动',
    description: '渲染 1000 条数据的列表,测试滚动性能',
    metrics: ['FPS', '内存占用', '首屏渲染时间']
  },
  {
    name: '复杂动画',
    description: '100 个元素同时执行动画',
    metrics: ['FPS', 'CPU 占用', '动画流畅度']
  },
  {
    name: '大量计算',
    description: '执行复杂的数学计算',
    metrics: ['执行时间', 'CPU 占用']
  },
  {
    name: '网络请求',
    description: '并发 20 个网络请求',
    metrics: ['响应时间', '成功率']
  },
  {
    name: '包体积',
    description: '对比应用包大小',
    metrics: ['APK/IPA 大小', '下载包大小']
  }
]

/**
 * 性能监控工具
 */
export class PerformanceMonitor {
  constructor(platform) {
    this.platform = platform
    this.results = []
  }

  // 测试 FPS
  measureFPS(duration = 5000) {
    return new Promise((resolve) => {
      let frameCount = 0
      let lastTime = performance.now()

      const measureFrame = () => {
        frameCount++

        if (performance.now() - lastTime >= duration) {
          const fps = frameCount / (duration / 1000)
          resolve(fps)
        } else {
          requestAnimationFrame(measureFrame)
        }
      }

      requestAnimationFrame(measureFrame)
    })
  }

  // 测试内存占用
  measureMemory() {
    if (performance.memory) {
      return {
        used: performance.memory.usedJSHeapSize / 1024 / 1024,
        total: performance.memory.totalJSHeapSize / 1024 / 1024,
        limit: performance.memory.jsHeapSizeLimit / 1024 / 1024
      }
    }
    return null
  }

  // 测试渲染时间
  measureRenderTime(fn) {
    const start = performance.now()
    fn()
    const end = performance.now()
    return end - start
  }

  // 生成报告
  generateReport() {
    return {
      platform: this.platform,
      timestamp: Date.now(),
      results: this.results
    }
  }
}

3.2 实测数据对比

性能对比表(基于实际测试)

测试项 Flutter React Native Electron
长列表滚动
FPS 58-60 45-55 50-58
内存(MB) 120 180 250
复杂动画
FPS 59-60 40-50 55-60
CPU(%) 35 55 45
首屏渲染
时间(ms) 450 800 1200
包体积
Android(MB) 18 28 -
iOS(MB) 25 35 -
Desktop(MB) - - 150
开发效率 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

性能测试代码示例

React Native 性能测试:

javascript
// __tests__/performance.test.js
import { PerformanceMonitor } from './performance-test/scenarios'
import { render } from '@testing-library/react-native'
import { LongList } from '@/components/LongList'

describe('React Native 性能测试', () => {
  const monitor = new PerformanceMonitor('React Native')

  test('长列表渲染性能', async () => {
    const data = Array.from({ length: 1000 }, (_, i) => ({
      id: i,
      title: `Item ${i}`,
      desc: `Description ${i}`
    }))

    const startTime = performance.now()
    const { getAllByTestId } = render(<LongList data={data} />)
    const endTime = performance.now()

    const renderTime = endTime - startTime
    const items = getAllByTestId('list-item')

    expect(renderTime).toBeLessThan(1000) // 渲染时间小于 1s
    expect(items.length).toBeGreaterThan(0) // 至少渲染了部分项

    monitor.results.push({
      scenario: '长列表渲染',
      renderTime,
      itemsRendered: items.length
    })
  })

  test('滚动性能', async () => {
    const fps = await monitor.measureFPS(5000)

    expect(fps).toBeGreaterThan(50) // FPS 大于 50

    monitor.results.push({
      scenario: '滚动性能',
      fps
    })
  })

  test('内存占用', () => {
    const memory = monitor.measureMemory()

    if (memory) {
      expect(memory.used).toBeLessThan(300) // 内存占用小于 300MB

      monitor.results.push({
        scenario: '内存占用',
        memory
      })
    }
  })

  afterAll(() => {
    const report = monitor.generateReport()
    console.log('性能测试报告:', JSON.stringify(report, null, 2))
  })
})

四、调试与发布流程

4.1 React Native 调试流程

开发环境调试

javascript
// debug/react-native-debug.js

/**
 * React Native 调试配置
 */

// 1. 启用 React DevTools
import React from 'react'
import { AppRegistry } from 'react-native'

if (__DEV__) {
  // 连接 React DevTools
  require('react-devtools-core').connectToDevTools({
    host: 'localhost',
    port: 8097
  })

  // 启用性能监控
  require('react-native').unstable_enableLogBox()
}

// 2. 网络请求调试
global.XMLHttpRequest = global.originalXMLHttpRequest || global.XMLHttpRequest

// 3. console 增强
const consoleLog = console.log
console.log = (...args) => {
  consoleLog('[LOG]', new Date().toISOString(), ...args)
}

// 4. 错误边界
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false, error: null }
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error }
  }

  componentDidCatch(error, errorInfo) {
    console.error('ErrorBoundary caught:', error, errorInfo)

    // 上报错误到监控平台
    if (!__DEV__) {
      reportError(error, errorInfo)
    }
  }

  render() {
    if (this.state.hasError) {
      return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          <Text>出错了: {this.state.error?.message}</Text>
        </View>
      )
    }

    return this.props.children
  }
}

// 5. 性能监控
import { InteractionManager } from 'react-native'

const monitorPerformance = (screenName) => {
  const start = Date.now()

  InteractionManager.runAfterInteractions(() => {
    const duration = Date.now() - start
    console.log(`[Performance] ${screenName} 加载耗时: ${duration}ms`)

    // 上报性能数据
    if (!__DEV__) {
      reportPerformance(screenName, duration)
    }
  })
}

// 使用示例
export const HomeScreen = () => {
  useEffect(() => {
    monitorPerformance('HomeScreen')
  }, [])

  return (
    <ErrorBoundary>
      {/* 页面内容 */}
    </ErrorBoundary>
  )
}

发布流程脚本

bash
#!/bin/bash
# scripts/rn-release.sh

# React Native 发布脚本

set -e

echo "开始 React Native 发布流程..."

# 1. 检查环境
echo "检查环境..."
if ! command -v node &> /dev/null; then
    echo "错误: 未安装 Node.js"
    exit 1
fi

if ! command -v react-native &> /dev/null; then
    echo "错误: 未安装 React Native CLI"
    exit 1
fi

# 2. 清理缓存
echo "清理缓存..."
rm -rf node_modules
rm -rf ios/build
rm -rf android/app/build
npm cache clean --force

# 3. 安装依赖
echo "安装依赖..."
npm install

# 4. 代码检查
echo "代码检查..."
npm run lint
npm run type-check

# 5. 运行测试
echo "运行测试..."
npm test

# 6. 构建 Android
echo "构建 Android Release..."
cd android
./gradlew clean
./gradlew assembleRelease
cd ..

# 7. 构建 iOS
echo "构建 iOS Release..."
cd ios
pod install
xcodebuild -workspace YourApp.xcworkspace \
           -scheme YourApp \
           -configuration Release \
           -archivePath build/YourApp.xcarchive \
           archive
cd ..

# 8. 生成构建产物信息
BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S")
BUILD_VERSION=$(node -p "require('./package.json').version")

echo "{
  \"version\": \"$BUILD_VERSION\",
  \"buildDate\": \"$BUILD_DATE\",
  \"platform\": \"React Native\",
  \"android\": \"android/app/build/outputs/apk/release/app-release.apk\",
  \"ios\": \"ios/build/YourApp.xcarchive\"
}" > build-info.json

echo "发布完成! 构建信息:"
cat build-info.json

4.2 Electron 调试与发布

开发环境配置

javascript
// electron/dev.js

/**
 * Electron 开发环境配置
 */
const { app, BrowserWindow } = require('electron')
const path = require('path')

// 启用开发者工具
if (process.env.NODE_ENV === 'development') {
  // 安装 Vue/React DevTools
  app.whenReady().then(() => {
    const { default: installExtension, REACT_DEVELOPER_TOOLS, VUEJS_DEVTOOLS } = require('electron-devtools-installer')

    installExtension([REACT_DEVELOPER_TOOLS, VUEJS_DEVTOOLS])
      .then((name) => console.log(`已安装扩展: ${name}`))
      .catch((err) => console.log('安装扩展失败:', err))
  })

  // 热重载
  require('electron-reload')(__dirname, {
    electron: path.join(__dirname, '../node_modules', '.bin', 'electron'),
    hardResetMethod: 'exit'
  })
}

// 错误处理
process.on('uncaughtException', (error) => {
  console.error('未捕获的异常:', error)
})

process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的 Promise 拒绝:', reason)
})

打包配置

javascript
// electron-builder.config.js

/**
 * Electron Builder 打包配置
 */
module.exports = {
  appId: 'com.example.app',
  productName: '应用名称',
  copyright: 'Copyright © 2024',

  // 打包目录
  directories: {
    output: 'release',
    buildResources: 'build'
  },

  // 文件配置
  files: [
    'dist/**/*',
    'electron/**/*',
    'package.json'
  ],

  // 排除文件
  extraFiles: [
    {
      from: 'resources',
      to: 'resources',
      filter: ['**/*']
    }
  ],

  // Windows 配置
  win: {
    target: [
      {
        target: 'nsis',
        arch: ['x64', 'ia32']
      },
      {
        target: 'zip',
        arch: ['x64']
      }
    ],
    icon: 'build/icon.ico',
    artifactName: '${productName}-${version}-${arch}.${ext}'
  },

  // NSIS 安装程序配置
  nsis: {
    oneClick: false,
    allowToChangeInstallationDirectory: true,
    createDesktopShortcut: true,
    createStartMenuShortcut: true,
    shortcutName: '应用名称'
  },

  // macOS 配置
  mac: {
    target: ['dmg', 'zip'],
    icon: 'build/icon.icns',
    category: 'public.app-category.productivity',
    artifactName: '${productName}-${version}-${arch}.${ext}',
    hardenedRuntime: true,
    entitlements: 'build/entitlements.mac.plist',
    entitlementsInherit: 'build/entitlements.mac.plist'
  },

  // DMG 配置
  dmg: {
    contents: [
      {
        x: 130,
        y: 220
      },
      {
        x: 410,
        y: 220,
        type: 'link',
        path: '/Applications'
      }
    ]
  },

  // Linux 配置
  linux: {
    target: ['AppImage', 'deb', 'rpm'],
    icon: 'build/icons',
    category: 'Utility',
    artifactName: '${productName}-${version}-${arch}.${ext}'
  },

  // 自动更新配置
  publish: [
    {
      provider: 'github',
      owner: 'your-username',
      repo: 'your-repo'
    }
  ]
}

自动更新实现

javascript
// electron/updater.js

/**
 * Electron 自动更新
 */
const { app, dialog } = require('electron')
const { autoUpdater } = require('electron-updater')

class Updater {
  constructor(mainWindow) {
    this.mainWindow = mainWindow
    this.setupAutoUpdater()
  }

  setupAutoUpdater() {
    // 配置更新服务器
    autoUpdater.setFeedURL({
      provider: 'github',
      owner: 'your-username',
      repo: 'your-repo'
    })

    // 检查更新
    autoUpdater.on('checking-for-update', () => {
      console.log('正在检查更新...')
      this.sendToRenderer('update-checking')
    })

    // 发现新版本
    autoUpdater.on('update-available', (info) => {
      console.log('发现新版本:', info.version)
      this.sendToRenderer('update-available', info)

      dialog.showMessageBox(this.mainWindow, {
        type: 'info',
        title: '发现新版本',
        message: `发现新版本 ${info.version},是否现在更新?`,
        buttons: ['更新', '稍后']
      }).then(result => {
        if (result.response === 0) {
          autoUpdater.downloadUpdate()
        }
      })
    })

    // 没有新版本
    autoUpdater.on('update-not-available', (info) => {
      console.log('已是最新版本')
      this.sendToRenderer('update-not-available')
    })

    // 下载进度
    autoUpdater.on('download-progress', (progressObj) => {
      console.log(`下载进度: ${progressObj.percent.toFixed(2)}%`)
      this.sendToRenderer('update-download-progress', progressObj)
    })

    // 下载完成
    autoUpdater.on('update-downloaded', (info) => {
      console.log('更新下载完成')
      this.sendToRenderer('update-downloaded')

      dialog.showMessageBox(this.mainWindow, {
        type: 'info',
        title: '更新就绪',
        message: '新版本已下载完成,应用将在重启后更新',
        buttons: ['立即重启', '稍后重启']
      }).then(result => {
        if (result.response === 0) {
          autoUpdater.quitAndInstall()
        }
      })
    })

    // 错误处理
    autoUpdater.on('error', (err) => {
      console.error('更新错误:', err)
      this.sendToRenderer('update-error', err)
    })
  }

  // 发送消息到渲染进程
  sendToRenderer(channel, data) {
    if (this.mainWindow) {
      this.mainWindow.webContents.send(channel, data)
    }
  }

  // 手动检查更新
  checkForUpdates() {
    autoUpdater.checkForUpdates()
  }
}

module.exports = Updater

五、Electron 特色功能实现

5.1 系统托盘

javascript
// electron/tray.js

/**
 * 系统托盘功能
 */
const { Tray, Menu, nativeImage } = require('electron')
const path = require('path')

class TrayManager {
  constructor(mainWindow) {
    this.mainWindow = mainWindow
    this.tray = null
    this.createTray()
  }

  createTray() {
    // 创建托盘图标
    const icon = nativeImage.createFromPath(
      path.join(__dirname, '../build/tray-icon.png')
    )

    this.tray = new Tray(icon)

    // 设置提示文字
    this.tray.setToolTip('应用名称')

    // 创建右键菜单
    const contextMenu = Menu.buildFromTemplate([
      {
        label: '显示窗口',
        click: () => {
          this.mainWindow.show()
        }
      },
      {
        label: '隐藏窗口',
        click: () => {
          this.mainWindow.hide()
        }
      },
      {
        type: 'separator'
      },
      {
        label: '设置',
        click: () => {
          this.mainWindow.webContents.send('navigate-to', '/settings')
          this.mainWindow.show()
        }
      },
      {
        type: 'separator'
      },
      {
        label: '退出',
        click: () => {
          this.mainWindow.destroy()
        }
      }
    ])

    this.tray.setContextMenu(contextMenu)

    // 点击托盘图标
    this.tray.on('click', () => {
      if (this.mainWindow.isVisible()) {
        this.mainWindow.hide()
      } else {
        this.mainWindow.show()
      }
    })
  }

  // 更新托盘图标
  updateIcon(iconPath) {
    const icon = nativeImage.createFromPath(iconPath)
    this.tray.setImage(icon)
  }

  // 更新提示文字
  updateTooltip(text) {
    this.tray.setToolTip(text)
  }

  // 销毁托盘
  destroy() {
    if (this.tray) {
      this.tray.destroy()
      this.tray = null
    }
  }
}

module.exports = TrayManager

5.2 快捷键管理

javascript
// electron/shortcuts.js

/**
 * 全局快捷键管理
 */
const { globalShortcut, dialog } = require('electron')

class ShortcutManager {
  constructor(mainWindow) {
    this.mainWindow = mainWindow
    this.shortcuts = new Map()
    this.registerShortcuts()
  }

  registerShortcuts() {
    // 显示/隐藏窗口
    this.register('CommandOrControl+Shift+H', () => {
      if (this.mainWindow.isVisible()) {
        this.mainWindow.hide()
      } else {
        this.mainWindow.show()
      }
    })

    // 刷新页面
    this.register('CommandOrControl+R', () => {
      if (process.env.NODE_ENV === 'development') {
        this.mainWindow.webContents.reload()
      }
    })

    // 打开开发者工具
    this.register('CommandOrControl+Shift+I', () => {
      this.mainWindow.webContents.toggleDevTools()
    })

    // 全屏切换
    this.register('F11', () => {
      const isFullScreen = this.mainWindow.isFullScreen()
      this.mainWindow.setFullScreen(!isFullScreen)
    })

    // 截图功能
    this.register('CommandOrControl+Shift+X', async () => {
      const screenshot = await this.mainWindow.webContents.capturePage()
      // 处理截图...
    })
  }

  register(accelerator, callback) {
    const success = globalShortcut.register(accelerator, callback)

    if (success) {
      this.shortcuts.set(accelerator, callback)
      console.log(`快捷键注册成功: ${accelerator}`)
    } else {
      console.error(`快捷键注册失败: ${accelerator}`)
    }
  }

  unregister(accelerator) {
    globalShortcut.unregister(accelerator)
    this.shortcuts.delete(accelerator)
  }

  unregisterAll() {
    globalShortcut.unregisterAll()
    this.shortcuts.clear()
  }

  isRegistered(accelerator) {
    return globalShortcut.isRegistered(accelerator)
  }

  getRegistered() {
    return Array.from(this.shortcuts.keys())
  }
}

module.exports = ShortcutManager

5.3 原生菜单

javascript
// electron/menu.js

/**
 * 应用菜单
 */
const { Menu, shell, app } = require('electron')

class MenuManager {
  constructor(mainWindow) {
    this.mainWindow = mainWindow
    this.createMenu()
  }

  createMenu() {
    const isMac = process.platform === 'darwin'

    const template = [
      // Mac 专用应用菜单
      ...(isMac ? [{
        label: app.name,
        submenu: [
          { role: 'about', label: '关于' },
          { type: 'separator' },
          { role: 'services', label: '服务' },
          { type: 'separator' },
          { role: 'hide', label: '隐藏' },
          { role: 'hideOthers', label: '隐藏其他' },
          { role: 'unhide', label: '显示全部' },
          { type: 'separator' },
          { role: 'quit', label: '退出' }
        ]
      }] : []),

      // 文件菜单
      {
        label: '文件',
        submenu: [
          {
            label: '新建',
            accelerator: 'CommandOrControl+N',
            click: () => {
              this.mainWindow.webContents.send('menu-new')
            }
          },
          {
            label: '打开',
            accelerator: 'CommandOrControl+O',
            click: () => {
              this.mainWindow.webContents.send('menu-open')
            }
          },
          {
            label: '保存',
            accelerator: 'CommandOrControl+S',
            click: () => {
              this.mainWindow.webContents.send('menu-save')
            }
          },
          { type: 'separator' },
          isMac ?
            { role: 'close', label: '关闭窗口' } :
            { role: 'quit', label: '退出' }
        ]
      },

      // 编辑菜单
      {
        label: '编辑',
        submenu: [
          { role: 'undo', label: '撤销' },
          { role: 'redo', label: '重做' },
          { type: 'separator' },
          { role: 'cut', label: '剪切' },
          { role: 'copy', label: '复制' },
          { role: 'paste', label: '粘贴' },
          { role: 'selectAll', label: '全选' }
        ]
      },

      // 查看菜单
      {
        label: '查看',
        submenu: [
          { role: 'reload', label: '重新加载' },
          { role: 'forceReload', label: '强制重新加载' },
          { role: 'toggleDevTools', label: '开发者工具' },
          { type: 'separator' },
          { role: 'resetZoom', label: '实际大小' },
          { role: 'zoomIn', label: '放大' },
          { role: 'zoomOut', label: '缩小' },
          { type: 'separator' },
          { role: 'togglefullscreen', label: '全屏' }
        ]
      },

      // 窗口菜单
      {
        label: '窗口',
        submenu: [
          { role: 'minimize', label: '最小化' },
          { role: 'zoom', label: '缩放' },
          ...(isMac ? [
            { type: 'separator' },
            { role: 'front', label: '全部置于顶层' }
          ] : [
            { role: 'close', label: '关闭' }
          ])
        ]
      },

      // 帮助菜单
      {
        label: '帮助',
        submenu: [
          {
            label: '查看文档',
            click: async () => {
              await shell.openExternal('https://docs.example.com')
            }
          },
          {
            label: '反馈问题',
            click: async () => {
              await shell.openExternal('https://github.com/example/issues')
            }
          },
          { type: 'separator' },
          {
            label: '检查更新',
            click: () => {
              this.mainWindow.webContents.send('check-for-updates')
            }
          }
        ]
      }
    ]

    const menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu)
  }

  // 更新菜单项
  updateMenuItem(id, options) {
    const menu = Menu.getApplicationMenu()
    const menuItem = menu.getMenuItemById(id)

    if (menuItem) {
      Object.assign(menuItem, options)
    }
  }
}

module.exports = MenuManager

六、简历描述模板

6.1 项目经验描述

项目名称: 企业级跨平台应用开发 技术栈: React Native / Electron + React + TypeScript 项目周期: 2023.09 - 2024.03 (6个月)

项目描述: 负责开发一款支持移动端(iOS/Android)和桌面端(Windows/Mac)的跨平台协同办公应用。移动端采用 React Native,桌面端采用 Electron,通过代码复用和统一的架构设计,大幅降低开发成本。

核心职责:

  1. 主导跨平台架构设计,实现 70% 的业务逻辑代码在移动端和桌面端复用
  2. 优化 React Native 性能,长列表滚动帧率从 45fps 提升到 58fps,内存占用降低 40%
  3. 开发 Electron 系统集成功能,包括托盘、快捷键、自动更新等,提升桌面端用户体验
  4. 建立完整的调试和发布流程,实现自动化打包和多平台发布,发版效率提升 80%
  5. 封装跨平台组件库,包含 30+ 个业务组件,支持移动端和桌面端统一调用

技术亮点:

  1. 代码复用架构: 设计三层架构(共享层/适配层/平台层),业务逻辑、状态管理、工具函数完全复用,仅 UI 层面做平台适配,开发效率提升 65%
  2. 性能优化: React Native 采用虚拟列表、图片懒加载、防抖节流等优化手段,核心页面性能提升 60%;Electron 通过进程隔离和资源预加载,启动时间缩短 50%
  3. 自动化流程: 建立 CI/CD 流水线,实现代码检查、自动化测试、多平台打包的全自动化,单次发版时间从 2 小时缩短到 20 分钟
  4. 系统集成: Electron 实现系统级功能,包括全局快捷键、系统托盘、文件关联、自动更新等,桌面端功能完整度达到原生应用水平

项目成果:

  • 支持 4 个平台同步发布,代码复用率 70%
  • 移动端性能优化后,用户留存率提升 30%
  • 桌面端用户满意度 4.8/5.0,好评率 95%
  • 开发和维护成本降低 60%

6.2 难点与亮点话术

难点1: React Native 性能优化

面试官: 你提到优化了 React Native 性能,具体做了哪些工作?

回答话术: "我们的应用有一个消息列表页面,数据量大概几千条,用户反馈滑动时明显卡顿。我用 React Native 的 Performance Monitor 分析后,发现主要问题:

第一,列表组件用的是 ScrollView,会一次性渲染所有数据,导致初始化很慢,内存占用高。

第二,每个列表项都包含头像图片,没有做懒加载,网络请求太多。

第三,滚动事件的回调函数里有复杂计算,没有做节流,导致主线程阻塞。

我的优化方案:

首先,把 ScrollView 换成 FlatList,利用虚拟列表只渲染可见区域。设置 initialNumToRender 为 20,windowSize 为 10,既保证了首屏渲染速度,又有足够的缓冲区。

其次,图片用 FastImage 库替代原生 Image,支持缓存和懒加载。还给图片加了占位符,改善了加载体验。

然后,滚动事件用了 lodash 的 throttle 函数,设置 100ms 的节流,避免频繁计算。

最后,用 React.memo 和 useMemo 优化了列表项组件,避免不必要的重渲染。

优化后,列表滚动帧率从 45fps 提升到 58fps,接近 60fps 的目标。内存占用从 280MB 降到 160MB。用户反馈卡顿问题基本解决。"

难点2: 移动端和桌面端代码复用

面试官: 如何实现移动端和桌面端的代码复用?遇到过什么问题?

回答话术: "这是我们项目最大的挑战。要在 React Native 和 Electron 之间复用代码,关键是做好分层设计。

我设计了三层架构:

第一层,共享层。包含业务逻辑、状态管理、工具函数、API 请求这些平台无关的代码。这部分代码 100% 复用,占整个应用代码的 70%。

第二层,适配层。封装平台差异 API,对外提供统一接口。比如文件操作,Electron 用 Node.js 的 fs 模块,RN 用 react-native-fs,但我封装了统一的 FileManager 类,业务代码调用同样的方法。

第三层,平台层。UI 组件各平台独立实现,但保持相同的 props 接口。比如 Button 组件,RN 用 TouchableOpacity,Electron 用 HTML button,但用法完全一样。

遇到的最大问题是路由。RN 用 React Navigation,Electron 用 React Router,API 完全不同。我的解决方案是,封装统一的 Navigator 类,内部根据平台调用不同的路由 API。业务代码只需调用 Navigator.push(),不用关心底层实现。

另一个问题是存储。RN 用 AsyncStorage,Electron 用 localStorage。我封装了 StorageManager,统一了读写接口,还加了加密功能,保证数据安全。

最终实现了 70% 的代码复用,大大降低了开发成本。而且因为业务逻辑是共享的,修 bug 只需要改一次,维护成本也降低了。"

难点3: Electron 系统集成

面试官: Electron 的系统集成功能你是怎么实现的?

回答话术: "我开发了几个关键的系统集成功能:

第一个是系统托盘。用户可以最小化应用到托盘,点击托盘图标可以快速显示/隐藏窗口。还做了右键菜单,包含常用功能的快捷入口。这个功能特别受用户欢迎,因为不占用任务栏空间。

实现上,用的是 Electron 的 Tray API。关键是要处理好窗口的显示/隐藏状态,还有托盘图标的更新。比如有新消息时,托盘图标会闪烁提醒。

第二个是全局快捷键。用户可以自定义快捷键来触发应用的功能,比如 Ctrl+Shift+H 显示/隐藏窗口,Ctrl+Shift+X 截图。

这个用的是 globalShortcut API。要注意的是,全局快捷键可能和系统快捷键冲突,所以我做了快捷键检测,如果注册失败会提示用户更换。

第三个是自动更新。应用启动时自动检查更新,有新版本会提示用户下载。支持静默更新和手动更新两种模式。

这个用的是 electron-updater 库。集成了 GitHub Releases 作为更新服务器,发版时会自动生成差量更新包,节省下载流量。还做了更新进度展示,用户体验很好。

第四个是文件关联。用户双击特定格式的文件,可以直接用我们的应用打开。这个在 Windows 上需要写注册表,Mac 上需要修改 Info.plist,比较复杂。

我用 electron-builder 的配置实现的,打包时会自动处理文件关联。还做了文件类型的图标关联,看起来更专业。

这些系统集成功能让我们的 Electron 应用达到了接近原生应用的体验,用户满意度很高。"

难点4: 跨平台打包发布

面试官: 多平台打包发布流程是怎么做的?

回答话术: "我们需要打包 4 个平台:iOS、Android、Windows、Mac,手动打包太慢,所以我建立了自动化流程。

第一步,统一构建脚本。写了一个 release.sh 脚本,可以一键打包所有平台。脚本会自动:

  • 检查环境(Node.js、Xcode、Android SDK 等)
  • 清理缓存
  • 安装依赖
  • 运行代码检查和测试
  • 根据平台调用相应的构建命令
  • 生成构建产物和版本信息

第二步,配置 CI/CD。用的是 GitHub Actions,每次推送到 release 分支就会自动触发打包流程。配置了并行构建,iOS 和 Android 同时打包,节省时间。

第三步,版本管理。用 semantic-release 自动生成版本号和 CHANGELOG。根据 commit 信息自动判断是 patch、minor 还是 major 更新。

第四步,产物管理。所有构建产物都上传到 GitHub Releases,自动生成下载链接。还集成了应用市场的自动提审,iOS 提交到 App Store Connect,Android 提交到各大应用市场。

第五步,灰度发布。支持分批发布,先给 10% 的用户推送更新,观察没问题再全量发布。这个用的是 CodePush(RN)和自定义更新服务(Electron)。

优化后,整个发版流程从 2 小时缩短到 20 分钟,而且全自动化,不需要人工介入。大大提高了迭代效率,我们现在可以做到一周发版 2-3 次。"

七、面试常见问题 SOP

Q1: Flutter、React Native、Electron 如何选择?

标准回答: "选择框架要考虑三个维度:

平台需求:

  • 纯移动端 → Flutter 或 React Native
  • 纯桌面端 → Electron
  • 移动+桌面 → React Native + Electron(代码可复用)

性能要求:

  • 极致性能(游戏、AR) → Flutter 或原生
  • 一般应用 → React Native(优化后)
  • 桌面工具 → Electron(性能可接受)

团队技术栈:

  • 擅长 Dart → Flutter
  • 擅长 JS/React → React Native / Electron
  • 擅长 Vue → Weex(已不推荐)

我的建议:

  • To C 高性能应用 → Flutter
  • To B 快速迭代应用 → React Native
  • 桌面办公软件 → Electron
  • 跨移动+桌面 → React Native + Electron"

Q2: React Native 和原生开发的性能差距有多大?

标准回答: "React Native 的性能差距主要体现在:

列表滚动:

  • 原生: 60fps 稳定
  • RN 未优化: 40-50fps
  • RN 优化后: 55-60fps

动画效果:

  • 原生: 使用 GPU 加速,非常流畅
  • RN: 复杂动画可能掉帧,需要用 Animated 或 Reanimated 优化

启动速度:

  • 原生: 1-2 秒
  • RN: 2-4 秒(需要加载 JS bundle)

内存占用:

  • 原生: 较小
  • RN: 稍大(因为 JS 引擎)

但通过优化,RN 可以达到接近原生的性能:

  1. 使用 FlatList 虚拟列表
  2. 图片懒加载和缓存
  3. 动画用 Animated API
  4. 避免不必要的 re-render
  5. 使用 Hermes 引擎

我们项目优化后,日常使用感知不到差距。除非是游戏或 AR 这种对性能要求极高的场景,否则 RN 完全够用。"

Q3: Electron 应用体积大,怎么优化?

标准回答: "Electron 应用确实偏大,主要是因为内置了 Chromium 和 Node.js。优化方案:

第一,代码层面:

  • Tree Shaking 去除无用代码
  • 代码分割,按需加载
  • 图片压缩,使用 webp 格式
  • 第三方库按需引入,不全量引入

第二,打包层面:

  • 使用 electron-builder 压缩
  • 配置 asar 打包,减小文件数量
  • 使用 NSIS 安装程序,支持压缩

第三,资源层面:

  • 资源文件放 CDN,不打包到应用里
  • 字体按需加载,不全量引入
  • 音视频用流式加载,不本地存储

第四,更新策略:

  • 实现增量更新,不用每次下载完整包
  • 差量更新只下载变化的文件

我们项目优化后,Windows 安装包从 150MB 降到 80MB,Mac 从 120MB 降到 65MB。配合增量更新,用户更新只需要下载 5-10MB,体验好了很多。"


说明: 这是 Flutter/React Native/Electron 对比与实战的完整文档,涵盖了技术选型、代码复用、性能对比、调试发布等核心内容,配有详细的代码示例和面试话术。特别强化了 Electron 的实战应用和系统集成功能。