从零开始构建一个现代化前端项目:一个iOS老炮的“被迫转型”记

罗华
2025-12-19 10:09
阅读 1414

去年双11前夜,我正窝在成都太古里的咖啡馆里,一边啃着兔头一边给公司App修复一个诡异的内存泄漏——是的,我是个做了6年iOS开发的老兵,见证了Swift从2.0一路卷到5.x,也亲手把Objective-C项目一点点迁移到SwiftUI。本以为这辈子就跟UIKit和Combine锁死了,结果领导一句话:“小张啊,咱们要搞个Web端的区块链数据看板,你不是学得快嘛?下个月上线。”

我当时差点把豆花饭喷出来。区块链?React?我不是连npm install都只在帮同事看报错时才敲过吗?

但没办法,成都的生活节奏虽舒服,可跳槽市场不等人。翻了翻BOSS直聘,清一色写着“熟悉React/Vue,有区块链项目经验优先”。行吧,为了简历上多一行“全栈能力”,硬着头皮上。


需求来了:一个“看起来很酷”的区块链浏览器

产品需求很简单(他们总这么说):用户输入一个钱包地址,就能看到该地址在以太坊上的所有交易记录、NFT持仓、代币余额,还得支持实时更新。UI要“赛博朋克风”,加载不能卡,最好还能分享截图。

我内心OS:你当我是Tony Stark啊?

但吐槽归吐槽,活儿得干。我决定从零搭一个现代化前端项目,目标是:结构清晰、性能拉满、部署丝滑、代码能让人看懂——毕竟以后可能还要交接给真正的前端同事(别让他们骂我祖宗十八代)。


第一步:脚手架选型,别被生态吓死

React 生态简直比 iOS 的 Cocoapods + Swift Package Manager + Carthage 混合使用还乱。Vite?Create React App?Next.js?Remix?

我翻了 GitHub 上几个热门开源区块链项目(比如 Etherscan 的简化版),发现大多数新项目都用 Vite + TypeScript + Tailwind CSS 这套组合拳。理由很实在:

  • Vite 启动快得离谱,改一行代码秒级热更新,再也不用等 Webpack 像煮泡面;
  • TS 能避免我这种 JS 新手写出 undefined is not a function 的史诗级 Bug;
  • Tailwind 写 UI 快,而且“赛博朋克风”无非就是紫蓝渐变 + 发光字体,Tailwind 配个 text-transparent bg-clip-text bg-gradient-to-r from-purple-500 to-blue-500 就搞定。

于是初始化:

npm create vite@latest crypto-explorer -- --template react-ts
cd crypto-explorer
npm install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

然后按官方文档配好 tailwind.config.cjspostcss.config.cjs,把 main.tsx 里引入 Tailwind 的 CSS。搞定,骨架有了。


第二步:状态管理?先别急着上 Redux

很多新手(包括曾经的我)一上来就搞 Redux Toolkit,结果写了一堆 boilerplate,其实项目根本不需要全局状态。

我的策略是:能用 React Context + useReducer 解决的,绝不引入第三方库。这个项目的核心状态就两个:当前查询的钱包地址、以及对应的链上数据(transactions, tokens, nfts)。

// context/BlockchainContext.tsx
import { createContext, useContext, useReducer } from 'react';

type State = {
  address: string | null;
  loading: boolean;
  data: BlockchainData | null;
};

type Action =
  | { type: 'SET_ADDRESS'; payload: string }
  | { type: 'FETCH_START' }
  | { type: 'FETCH_SUCCESS'; payload: BlockchainData }
  | { type: 'FETCH_ERROR' };

const initialState: State = { address: null, loading: false, data: null };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'SET_ADDRESS': return { ...state, address: action.payload };
    case 'FETCH_START': return { ...state, loading: true };
    case 'FETCH_SUCCESS': return { ...state, loading: false, data: action.payload };
    case 'FETCH_ERROR': return { ...state, loading: false, data: null };
    default: throw new Error('Unknown action');
  }
}

const BlockchainContext = createContext<{ state: State; dispatch: React.Dispatch<Action> } | undefined>(undefined);

export function BlockchainProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <BlockchainContext.Provider value={{ state, dispatch }}>
      {children}
    </BlockchainContext.Provider>
  );
}

export function useBlockchain() {
  const context = useContext(BlockchainContext);
  if (!context) throw new Error('useBlockchain must be used within BlockchainProvider');
  return context;
}

简单、可控、没魔法。后续如果真需要复杂状态流(比如 WebSocket 实时推送),再考虑 Zustand 或 Jotai,它们比 Redux 轻量太多。


第三步:资源加载与性能优化——别让用户等成化石

区块链数据接口通常又慢又大。一次请求可能返回几百条交易记录,每条还带时间戳、gas fee、token symbol……直接渲染?页面直接卡死。

我用了三个招:

  1. 虚拟滚动(Virtualized List):用 react-window 只渲染可视区域内的交易项。
  2. 懒加载 + 骨架屏:数据没回来前显示炫酷的骨架动画(Tailwind 配合 animate-pulse 一行搞定)。
  3. 缓存机制:用 localStorage 缓存最近查过的地址数据,加个 TTL(比如 5 分钟),避免重复请求。
// components/TransactionList.tsx
import { FixedSizeList as List } from 'react-window';

const TransactionRow = ({ index, style, data }: { index: number; style: React.CSSProperties; data: Transaction[] }) => (
  <div style={style} className="border-b p-4 hover:bg-gray-50">
    <div className="font-mono text-sm">{data[index].hash}</div>
    <div className="text-green-500">+{data[index].value} ETH</div>
  </div>
);

export const TransactionList = ({ transactions }: { transactions: Transaction[] }) => (
  <List
    height={600}
    itemCount={transactions.length}
    itemSize={80}
    itemData={transactions}
  >
    {TransactionRow}
  </List>
);

测试时,Chrome DevTools 的 Performance 面板跑出来 FPS 稳稳 60,产品经理看了直呼“这流畅度可以发朋友圈”。


第四步:区块链集成——别被 Web3.js 吓退

真正让我头疼的是连接区块链。早期项目常用 Web3.js,但现在社区更推荐 ethers.js —— API 更简洁,TypeScript 支持更好,bundle size 也小。

// utils/blockchain.ts
import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR_KEY');

export async function fetchTokenBalance(address: string, tokenAddress: string) {
  const contract = new ethers.Contract(
    tokenAddress,
    ['function balanceOf(address) view returns (uint256)'],
    provider
  );
  const balance = await contract.balanceOf(address);
  return ethers.formatUnits(balance, 18); // 自动处理精度
}

Infura 免费额度够用,但如果用户量大了就得考虑 Alchemy 或自建节点。不过对我们这个 MVP 来说,够了。


第五步:部署上线——让运维大哥少骂两句

本地跑得欢,上线就翻车?我可吃过亏。这次学乖了:

  • Vercel 一键部署,免费、快、还带 Preview URL(PR 自动部署预览),产品经理再也不用催“你本地好了算什么,线上呢?”
  • 所有 API Key 通过 Vercel 的环境变量注入,绝不提交到 Git
  • 开启 Brotli 压缩 + CDN,首屏加载压到 1.2s 以内
// vercel.json
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=300" }
      ]
    }
  ]
}

上周五晚上 11 点,终于点下 Deploy。刷新页面,紫蓝色界面流畅加载出我的钱包交易记录——那一刻,感觉比当年第一次用 Swift 写出 UITableView 还爽。


总结:iOS 老兵的前端初体验

这次“被迫转型”让我意识到:现代前端工程化程度其实和 iOS 不相上下。Vite ≈ Xcode 编译速度,TypeScript ≈ Swift 类型安全,Tailwind ≈ SwiftUI 声明式 UI。底层逻辑相通,只是语法糖不同。

如果你也在求职或想拓展技术栈,别怕从零开始。资源(教程、开源项目、Stack Overflow)多到爆炸,关键是要动手做个项目,哪怕是为了应付面试。

最后附上我的技术选型清单,供参考:

类别 选择 理由
构建工具 Vite 快,配置简单,HMR 秒级响应
状态管理 React Context + useReducer 轻量,够用,避免过度设计
UI 框架 Tailwind CSS 快速实现设计稿,主题定制灵活
区块链 SDK ethers.js 简洁、TS 友好、社区活跃
部署平台 Vercel 免费、自动 HTTPS、Preview URL 神器
性能优化 react-window + localStorage 缓存 避免卡顿,提升用户体验

现在,我的简历上多了“React 项目实战经验”,BOSS 直聘消息多了好几条。虽然可能还是回 iOS 岗,但至少下次团建,前端同事不会再笑我连 useState 都写错 hooks 顺序了。

对了,项目源码已开源(私信我拿链接),欢迎 Star —— 毕竟,谁不想在成都的慢生活里,悄悄卷赢别人呢?

评论 0

最热最新
暂无评论
罗华Lv.1
0
影响力
0
文章
0
粉丝