从STM32到Vue:一个硬件仔的前端转型血泪史
去年双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生态工具链实战对比
进入正题。我们的项目需要:
- 展示区块链钱包地址的NFT持仓
- 实时监听以太坊事件(通过Web3.js)
- 支持暗色/亮色主题切换
- 在低端安卓机上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>
关键优化点:
- 用
Promise.all并发请求,避免串行等待(区块链RPC节点延迟高) - 对IPFS的HTTP网关做缓存(localStorage + TTL)
- 大数运算用
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分析发现:
- 过度渲染:每个NFT卡片都是独立组件,但props包含整个metadata对象
- 内存泄漏:Web3事件监听器没销毁
- 主线程阻塞: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还流畅)。回看这段历程,有几点心得:
- 别被框架绑架:Vue/React只是工具,核心是解决问题。就像我以前在FreeRTOS和RT-Thread之间选型,适合场景最重要
- 善用AI但别依赖:ChatGPT帮我生成了80%的样板代码,但关键逻辑(如Web3事件处理)必须自己啃透
- 性能意识刻进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