从STM32到Vue:一个硬件仔的前端转型血泪史

代码收藏夹
2025-12-22 18:05
阅读 236

去年双11前两周,我正蹲在示波器前调试一块LoRa模块,手机突然震动——组长发来钉钉:“小王,明天开始你跟前端组一起搞新项目,用Vue。”
我当时差点把探针插进自己眼睛里。

要知道,我上一次写HTML还是大四做课程设计,那时候<marquee>标签还没被现代浏览器彻底拉黑。五年嵌入式生涯,我的世界里只有寄存器、中断、DMA和永远跑不满的RAM。结果现在要我搞“响应式数据绑定”?我连JavaScript的闭包都还没搞明白!

但没办法,公司要搞一个区块链资产可视化平台,后端是我熟悉的Go(谢天谢地),前端却没人愿意接——产品经理要求“像MetaMask那样丝滑”,测试团队还在旁边阴阳怪气:“硬件出身的也能写前端?” 行吧,为了房租(上海内环一室户月租8500,懂的都懂),我咬牙上了。


为什么是Vue而不是React?

说实话,我最初想直接上React。毕竟社区大、生态全,而且我司后端用TypeScript,React+TS看起来很“工程化”。但当我打开Create React App生成的node_modules目录,看到那1200+个依赖包时,我这个习惯了裸机编程的人直接PTSD了——这比我们整个嵌入式固件的代码量还大!

反观Vue,官方脚手架create-vue生成的项目清爽得让我感动。核心理念也更符合我的硬件思维:

  • 渐进式框架:可以只用vue.runtime.esm-bundler.js(~30KB),不像React必须搭配ReactDOM、JSX编译器等全家桶
  • 模板语法直观v-if/v-for这种声明式写法,比我当年写FreeRTOS任务调度还清晰
  • 单文件组件(SFC).vue文件把template/script/style放一起,调试时不用在三个文件间跳转,对嵌入式老狗极其友好

当然,我也知道React有Hooks、并发渲染等高级特性。但对于一个要快速交付、且团队里只有我一个“伪前端”的项目来说,Vue的学习曲线明显更平缓。上周五晚上加班到凌晨两点,我甚至能靠ChatGPT三句话搞定一个动态表单组件,而隔壁用React的同事还在和useEffect的依赖数组搏斗……

真实场景吐槽:产品经理临时加需求说要支持“区块链交易实时追踪动画”,我用Vue的<transition>配合CSS keyframes半小时搞定;React组那边折腾了一天,最后用了framer-motion,结果bundle size暴涨40%。


Vue生态工具链实战对比

进入正题。我们的项目需要:

  1. 展示区块链钱包地址的NFT持仓
  2. 实时监听以太坊事件(通过Web3.js)
  3. 支持暗色/亮色主题切换
  4. 在低端安卓机上60fps流畅运行

针对这些需求,我对主流方案做了横向对比:

需求 Vue方案 React方案 嵌入式老狗点评
状态管理 Pinia (轻量) Redux Toolkit (重型) Pinia的store像全局变量,亲切!
UI组件库 Element Plus + 自定义主题 Ant Design Element的按需引入省了2MB JS
动画 Vue内置transition + GSAP Framer Motion GSAP控制帧率更精准
Web3集成 vue-web3封装 web3-react Vue插件直接this.$web3调用
性能监控 Vue DevTools + Lighthouse React DevTools + Web Vitals Vue DevTools的时间轴太香了

特别说下Pinia——这简直是为硬件人设计的状态管理!没有Redux那些繁琐的action/type/reducer,直接定义store函数:

// stores/wallet.ts
export const useWalletStore = defineStore('wallet', () => {
  const address = ref('')
  const balance = ref(0n) // 注意!BigInt用于处理wei单位
  
  const connect = async () => {
    // 调用Web3.js连接钱包
    const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' })
    address.value = accounts[0]
    updateBalance()
  }
  
  return { address, balance, connect }
})

在组件里直接const { address } = storeToRefs(useWalletStore()),数据自动同步。这不就是我在STM32里用extern volatile uint32_t sensor_data的感觉吗?(虽然原理完全不同,但心理上莫名安心)


区块链集成踩坑实录

说到区块链,前端最大的痛点其实是Web3.js的异步地狱。比如获取NFT元数据:

// 反面教材:回调嵌套地狱
web3.eth.getBalance(address, (err, bal) => {
  if (err) throw err
  nftContract.methods.tokenURI(tokenId).call((err, uri) => {
    if (err) throw err
    fetch(uri).then(res => res.json()).then(metadata => {
      // ...终于拿到数据
    })
  })
})

Vue3的async/await + Composition API救了我:

<script setup>
import { onMounted, ref } from 'vue'
import { useWalletStore } fsrom '@/stores/wallet'

const { address } = storeToRefs(useWalletStore())
const nfts = ref([])

onMounted(async () => {
  try {
    const tokens = await fetchTokens(address.value) // 封装好的API
    const metadataPromises = tokens.map(token => 
      fetch(token.uri).then(r => r.json())
    )
    nfts.value = await Promise.all(metadataPromises)
  } catch (e) {
    ElMessage.error('区块链数据加载失败:' + e.message)
  }
})
</script>

关键优化点

  1. Promise.all并发请求,避免串行等待(区块链RPC节点延迟高)
  2. 对IPFS的HTTP网关做缓存(localStorage + TTL)
  3. 大数运算用ethers.BigNumber而非原生Number(防精度丢失)

最惊险的是上线前一天,发现MetaMask切换网络后组件没更新。查了三小时才发现:Vue不会监听window.ethereum对象的变化!最后用watchEffect手动触发:

watchEffect(() => {
  if (window.ethereum) {
    window.ethereum.on('chainChanged', () => location.reload())
  }
})

(别笑,这招在嵌入式里叫“看门狗复位”,糙但有效)


性能优化:从60fps到掉帧再到60fps

作为前嵌入式工程师,我对性能有种病态执着。项目初期在红米Note 8(骁龙439)上跑只有28fps,页面滚动像PPT。用Chrome DevTools分析发现:

  1. 过度渲染:每个NFT卡片都是独立组件,但props包含整个metadata对象
  2. 内存泄漏:Web3事件监听器没销毁
  3. 主线程阻塞:BigNumber计算没放Web Worker

优化手段

1. 组件精细化拆分

<!-- 优化前 -->
<NFTCard :metadata="fullMetadata" />

<!-- 优化后 -->
<NFTImage :uri="metadata.image" />
<NFTTitle :name="metadata.name" />

配合v-memo(Vue3.2+)跳过相同props的组件更新:

<div v-for="nft in nfts" :key="nft.id" v-memo="[nft.image]">
  <NFTImage :uri="nft.image" />
</div>

2. 事件监听器清理onUnmounted里移除Web3监听:

onUnmounted(() => {
  window.ethereum?.removeListener('accountsChanged', handleAccountsChange)
})

3. 计算密集型任务Offload 把ETH余额格式化(wei → ether)放到Web Worker:

// worker/format.js
self.onmessage = (e) => {
  const formatted = ethers.utils.formatEther(e.data)
  self.postMessage(formatted)
}

// 组件中
const worker = new Worker('/format.js')
worker.postMessage(balanceInWei)
worker.onmessage = (e) => {
  formattedBalance.value = e.data
}

最终效果(红米Note 8):

指标 优化前 优化后
FPS 28 58
首屏加载 3.2s 1.1s
内存占用 180MB 95MB

Javascript?不,是TypeScript!

虽然标题写了Javascript,但我必须强调:2024年了,新项目请直接上TypeScript!尤其当你像我一样经常手抖把==写成=的时候。

Vue3对TS的支持堪称完美:

  • defineProps<{ address: string }>() 类型自动推导
  • Pinia store的action返回值类型安全
  • Web3.js的ethers.js库自带完整TS声明

最爽的是VS Code的智能提示——当我输入ethers.utils.,立刻列出所有工具函数,再也不用翻文档查parseEther还是formatEther。这对从Keil MDK转来的我简直是降维打击(以前查STM32手册要翻PDF书签...)


结语:硬件人的前端生存指南

三个月过去,项目已稳定运行,甚至收到区块链团队的感谢邮件(说我们的前端比他们的DApp还流畅)。回看这段历程,有几点心得:

  1. 别被框架绑架:Vue/React只是工具,核心是解决问题。就像我以前在FreeRTOS和RT-Thread之间选型,适合场景最重要
  2. 善用AI但别依赖:ChatGPT帮我生成了80%的样板代码,但关键逻辑(如Web3事件处理)必须自己啃透
  3. 性能意识刻进DNA:前端不是“随便写写”,低端机用户占比超30%,每KB都要精打细算

最后说句掏心窝的:从寄存器操作到虚拟DOM,看似跨度极大,但底层思维一脉相承——都是在有限资源下,用最可靠的方式达成目标。下次再有人问“硬件转前端难吗?”,我会指着Element Plus的按需引入配置说:“看,这不就跟裁剪uC/OS-II一样嘛!”

(完)

附:我的开发环境速览

  • 编辑器:VS Code + Volar插件(别用Vetur了!)
  • 调试:Vue DevTools + MetaMask的Network日志
  • 构建:Vite 4.x(比Webpack快5倍,启动只要800ms)
  • 测试:Vitest(单元测试) + Cypress(E2E)
  • 部署:Vercel(免费额度够个人项目用)

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝