从零搭一个现代化前端项目,顺便聊聊区块链和React的奇妙组合

罗勇△
2026-01-05 16:18
阅读 244

上个月在腾讯光子工作室实习的时候,带我的mentor丢给我一个需求:“搞个能展示NFT资产的小型DApp前端,两周上线”。我当时人就麻了——我一个主攻后端性能优化的Vim党,连MetaMask都没装过,现在要写区块链相关的前端?而且还是用React?

但没办法,秋招季快到了,简历上总得有点像样的项目。深圳这边腾讯系公司对“全栈能力”越来越看重,光会调perfeBPF可不够看。于是,我硬着头皮,从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)上滑动列表时掉帧严重。产品经理路过看了一眼,说:“这体验不行啊,用户还以为我们链挂了。”

于是赶紧优化。思路有三:

  1. 懒加载(Lazy Load):用react-intersection-observer,只加载视口内的图片
  2. 占位图:先显示模糊缩略图或骨架屏
  3. 图片压缩代理:通过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.iocloudflare-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

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