从零搭一个现代化前端项目,顺便聊聊区块链和React的奇妙组合
上个月在腾讯光子工作室实习的时候,带我的mentor丢给我一个需求:“搞个能展示NFT资产的小型DApp前端,两周上线”。我当时人就麻了——我一个主攻后端性能优化的Vim党,连MetaMask都没装过,现在要写区块链相关的前端?而且还是用React?
但没办法,秋招季快到了,简历上总得有点像样的项目。深圳这边腾讯系公司对“全栈能力”越来越看重,光会调perf和eBPF可不够看。于是,我硬着头皮,从create-react-app开始,一步步搭出了这个项目。今天就来复盘一下整个过程,顺便给和我一样刚接触现代化前端开发的同学避避坑。
起手式:别再用create-react-app了(真的)
我知道很多教程一上来就说“用CRA快速启动”,但2024年了,这玩意儿真的有点老。打包慢、配置黑盒、HMR(热更新)有时候卡到你想砸键盘。我在本地yarn create react-app my-dapp之后,光等依赖安装就花了8分钟——我家1000M宽带啊!
后来一怒之下换成了 Vite。是的,就是那个尤雨溪力推的构建工具。启动速度直接起飞:
npm create vite@latest my-dapp -- --template react-ts
cd my-dapp
npm install
npm run dev
三秒内本地服务器跑起来,改一行代码,浏览器瞬间刷新。那一刻我悟了:前端工程化的体验,真的能影响程序员的心情。上周五晚上加班改UI,要是还用CRA,我估计早就去天台吹风了。
区块链怎么接?别慌,有现成轮子
说到区块链,很多人第一反应是“智能合约”、“Solidity”、“Gas费”。但作为前端,我们其实只需要关心一件事:怎么和链上数据交互。
我这个项目的需求很简单:用户连接钱包后,显示他持有的NFT列表。不需要写合约,只需要读数据。
于是,我用了两个关键库:
ethers.js:比Web3.js更轻量、API更清晰wagmi:基于React Hooks的以太坊交互库,自带MetaMask、WalletConnect支持
安装命令如下:
npm install ethers wagmi viem @tanstack/react-query
注:
viem是wagmi底层用的新一代以太坊客户端库,性能比ethers还好,但API类似,学习成本低。
接下来,在main.tsx里初始化wagmi:
// main.tsx
import { createConfig, http } from 'wagmi'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const config = createConfig({
chains: [], // 后面可以加支持的链,比如Sepolia
transports: {
// 默认用公共RPC,生产环境建议用自己的节点
11155111: http('https://sepolia.infura.io/v3/YOUR_KEY'),
},
})
const queryClient = new QueryClient()
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</WagmiProvider>
</React.StrictMode>,
)
然后在组件里就能直接用Hooks了:
// WalletConnectButton.tsx
import { useAccount, useConnect, useDisconnect } from 'wagmi'
import { injected } from 'wagmi/connectors'
export default function WalletButton() {
const { address } = useAccount()
const { connect } = useConnect({ connector: injected() })
const { disconnect } = useDisconnect()
if (address) {
return (
<button onClick={() => disconnect()}>
Disconnect ({address.slice(0, 6)}...{address.slice(-4)})
</button>
)
}
return <button onClick={() => connect()}>Connect Wallet</button>
}
是不是比想象中简单?区块链前端的核心,其实是状态管理 + 异步数据流。而React + Hooks + Query的组合,天然适合这种场景。
性能优化:别让NFT图片拖垮你的页面
拿到NFT数据后,问题来了:每个NFT都有image字段,指向IPFS或Arweave上的图片URL。如果一次性加载几十张高清图,页面直接卡死。
我一开始偷懒,直接<img src={nft.image} />,结果在测试机(红米Note 10)上滑动列表时掉帧严重。产品经理路过看了一眼,说:“这体验不行啊,用户还以为我们链挂了。”
于是赶紧优化。思路有三:
- 懒加载(Lazy Load):用
react-intersection-observer,只加载视口内的图片 - 占位图:先显示模糊缩略图或骨架屏
- 图片压缩代理:通过CDN自动转WebP并压缩
关键代码如下:
import { useInView } from 'react-intersection-observer'
function NFTCard({ nft }: { nft: NFT }) {
const [ref, inView] = useInView()
const [imageLoaded, setImageLoaded] = useState(false)
// 使用Cloudflare Images做自动优化
const optimizedImageUrl = `https://imagedelivery.net/xxx/${nft.imageId}/public?width=300&format=webp`
return (
<div ref={ref} className="nft-card">
{!imageLoaded && <div className="skeleton" />}
{inView && (
<img
src={optimizedImageUrl}
onLoad={() => setImageLoaded(true)}
style={{ display: imageLoaded ? 'block' : 'none' }}
alt={nft.name}
/>
)}
</div>
)
}
优化前后对比(实测于Chrome DevTools Lighthouse):
| 指标 | 优化前 | 优化后 |
|---|---|---|
| LCP(最大内容绘制) | 4.2s | 1.1s |
| TTI(可交互时间) | 3.8s | 0.9s |
| 内存占用 | 180MB | 95MB |
终于能在低端机上丝滑滚动了!运维小哥看到监控曲线平稳,还请我喝了杯喜茶(深圳打工人の浪漫)。
工程化细节:Lint、格式、CI一个都不能少
作为一个被Linux内核社区PR流程毒打过的Vim党,我对代码规范有执念。所以项目一建好,我就把工程化脚手架搭全了:
- ESLint + Prettier:自动格式化,拒绝“你tab我空格”的圣战
- Husky + lint-staged:提交前自动检查,避免污染主干
- GitHub Actions:push后自动跑测试 + 构建预览
.eslintrc.cjs 配置片段:
module.exports = {
extends: [
'react-app',
'react-app/jest',
'plugin:jsx-a11y/recommended', // 可访问性检查
],
rules: {
'react-hooks/exhaustive-deps': 'error', // 这个救了我无数次
}
}
.prettierrc 则简单粗暴:
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}
至于为什么不用IDE?Emacs用户别骂了……我是真觉得Vim + coc.nvim + eslint_d 的组合够用。虽然调试React组件树还是得开DevTools,但日常编码效率不输VSCode。当然,前提是你愿意花三天配插件(手动狗头)。
踩坑记录:那些让我凌晨三点还在查文档的瞬间
坑1:MetaMask注入的window.ethereum不是所有浏览器都有
测试时发现Safari完全无法连接钱包。排查半天才意识到:MetaMask只在Chrome/Firefox插件环境中注入ethereum对象。解决方案是在wagmi配置里显式指定injected()连接器,并做降级提示:
if (typeof window.ethereum === 'undefined') {
alert('请安装 MetaMask 插件')
}
坑2:IPFS图片在微信内置浏览器打不开
因为微信屏蔽了非白名单域名的HTTP请求,而很多NFT的图片托管在ipfs.io或cloudflare-ipfs.com,直接403。临时方案是用代理服务中转,长期得自建网关。
坑3:React Strict Mode导致useEffect执行两次
开发环境启用了Strict Mode后,useEffect会故意执行两次,用来暴露副作用问题。但我有个监听钱包切换的逻辑,触发了两次请求,差点把Infura的免费额度打爆。最后用useRef做了防重:
const hasMounted = useRef(false)
useEffect(() => {
if (!hasMounted.current) {
fetchData()
hasMounted.current = true
}
}, [])
写在最后:前端没那么可怕
说实话,两周前的我绝对想不到自己能独立搞出一个区块链DApp前端。过程中踩了很多坑,但也学到了很多:从Vite的底层原理,到React Query的状态管理哲学,再到如何在资源受限设备上优化体验。
在深圳这座“卷都”,技术迭代快得吓人。但只要肯动手,从最小可行项目开始,现代化前端开发并没有想象中那么高不可攀。React生态虽然庞大,但核心思想很朴素:声明式UI + 状态驱动 + 工程化保障。
至于区块链?它只是另一种数据源而已。前端该关注的,永远是如何让用户“感觉快、用着爽”。
秋招加油吧,各位!我现在每天刷LeetCode的同时,也在往GitHub疯狂推代码——毕竟,简历上写“参与过Web3项目”比“熟悉HTML/CSS”听起来酷多了(虽然本质上都是搬砖)。
附:项目已开源在GitHub(私信我拿链接),欢迎Star & 提Issue。如果你也用Vim写React,咱们可以组个“复古前端联盟” 😎

评论 0