上午九点,链艺坊的开放式办公区已经坐满了人。
林晨盯着屏幕上打开的代码仓库,眉头不自觉地皱了起来。昨天下午项目会结束后,tech Lead 陈峰已经把项目权限和任务详情发给了他。这是一个 NFt 拍卖平台的前端模块,需要他负责用户资产展示、出价交互和交易状态实时更新的部分。
技术栈清清楚楚地写在项目文档里:React 18 + typeScript + Vite,与后端智能合约交互使用 ethers.js,部分复杂状态管理用 Zustand。每一个词他都认识,但组合在一起,尤其是要在三天内做出“可演示版本”,压力像潮水般涌来。
他上一次正经写前端代码,还是五年前用 jquery 和 bootstrap 做后台管理系统。React 他听说过,知道是 Facebook 搞的框架,但从来没碰过。typeScript 更陌生,只知道是 JavaScript 加了类型系统。至于 Vite、Zustand 这些工具,名字都新鲜。
“晨哥,环境配好了吗”?旁边工位探过来一张年轻的脸,是昨天一起吃午饭的前端同事阿杰,戴着黑框眼镜,头发染了一撮灰蓝色。
“差不多了,正在看项目结构”。林晨尽量让自己的声音听起来平静。
“那就好。这个项目其实不难,核心逻辑都在合约那边,我们前端主要是把数据展示好,交互做流畅”。阿杰说着,指了指自己屏幕,“我负责拍卖列表和搜索,咱们模块挨着,有问题随时问我”。
“谢谢”。林晨点点头,心里稍微松了点。至少同事看起来还算友善。
第一步:初识代码墙
他深吸一口气,点开项目 REAdmE,从头开始看。先按照文档把依赖装好,然后启动开发服务器。终端里滚过一行行安装日志,最后显示“Local:
Auction platform”的标题。
“第一步总算没卡住”。林晨心里默念,开始按照任务描述,找到对应的组件目录。第一个要改的文件是 src/ponents/Assetcard.tsx。点开,满眼都是陌生的语法。
林晨盯着代码看了足足十分钟。interface、React.Fc、useState、usecallback、usememo、bigNumber…… 这些概念像一堵墙横在面前。他能大概猜出一些——useState 应该是管理状态的,usecallback 和 usememo 跟性能优化有关,interface 定义类型。但具体怎么用,为什么要这么用,脑子里一片空白。
更麻烦的是,这代码和他习惯的后端 Java/python 写法完全不同。函数式组件、hooks、JSx 语法…… 每一样都需要重新理解。
“晨哥,卡住了”?阿杰又探过头来,看到林晨屏幕上的代码,“哦,Assetcard 啊,这个组件其实挺典型的。你要改的是出价按钮那块的样式,产品说现在的按钮不够醒目,要加个渐变背景和 hover 效果”。
林晨苦笑:“样式先放放,我连这组件怎么工作的都还没完全看懂”。
阿杰愣了一下,随即露出理解的表情:“哦对,你之前主要做后端是吧?React 这套确实需要适应一下。要不你先看看官方文档?React 和 typeScript 的官网教程都挺不错的”。
“嗯,正在看”。林晨已经打开了 React 官网,同时另一个标签页是 typeScript handbook。
调试:新手的挫败感
时间一分一秒过去。上午十点半,林晨勉强理解了函数式组件的基本概念,尝试着修改了一点样式代码。保存后,浏览器热更新,按钮确实变了颜色。但当他尝试添加一个简单的功能——在出价时显示加载状态——时,问题来了。
按照他的理解,应该在 handlebid 函数里设置某个状态,然后在按钮上根据这个状态显示“出价中……”。他模仿着现有代码,加了个 const [isSubmitting, setIsSubmitting] = useState(false),然后在按钮的 disabled 属性里用这个状态。
保存,编译报错:type boolean is not assignable to type string | undefined.
林晨盯着错误信息,不明所以。他检查代码,按钮的 disabled 属性确实是布尔值啊。
“阿杰,能帮我看一眼吗”?他不得不求助。
阿杰凑过来,扫了两眼:“哦,这里。你看按钮的 disabled 属性类型定义,可能是从某个 UI 库来的,它接受的是 string 或者 undefined,不是 boolean。你得改成 disabled={isSubmitting ? true : undefined}”。
林晨恍然,同时又感到一阵无力。这种细节,没有经验根本想不到。改好,编译通过。但点击按钮测试时,控制台又报错:Uncaught typeError: onbidclick is not a function。
“这又是为什么”?林晨感觉自己像个刚学编程的新手。
阿杰耐心解释:“onbidclick 是从父组件传下来的回调函数,但你在 Assetcard 里测试的时候,父组件没传这个 prop,所以是 undefined。你得在父组件里模拟一个函数传进来,或者先判断是否存在”。
林晨按照提示修改,终于让按钮能正常显示加载状态了。就这么一个简单的功能,花了他整整一个小时。
进度与规范的挑战
抬头看时间,已经十一点四十。办公区里开始有人起身去吃饭,讨论着中午吃什么。几个年轻同事在聊昨晚玩的游戏,术语飞溅——“符文”、“打野”、“gank”。林晨完全插不上话。他关掉文档页面,揉了揉发酸的眼睛。一个上午,只勉强弄懂了一个组件的一小部分,而周五就要交付可演示版本。
这种进度,别说三天,给他三周都不一定够。
午休时,他没什么胃口,在楼下便利店随便买了个三明治,回到工位边吃边继续看代码。阿杰和其他几个同事结伴出去吃了,办公区空了一半。林晨啃着干巴巴的三明治,眼睛没离开屏幕。他打开项目的其他组件文件,一个个看过去,试图理解整个前端架构。越看心越沉——项目用了不少他完全没接触过的库和模式:React Router 做路由、React query 管理服务端状态、tailwind cSS 写样式……
下午一点半,同事们陆续回来。阿杰看到林晨还在工位,有些惊讶:“晨哥,你没休息啊”?
“嗯,抓紧时间熟悉熟悉”。林晨说。
下午的工作更具体。陈峰在团队群里 @ 了他,让他下午四点前提交一个简单的 pR,把 Assetcard 组件的几个样式问题修复了。这是个小任务,算是让他熟悉 git 工作流。林晨不敢懈怠,按照公司文档的流程,从拉取最新代码、创建分支、修改、提交、推送到远程,一步步做。提交前他还特意检查了代码格式,确保符合项目的 ESLint 配置。
三点五十,pR 创建成功。他松了口气,以为至少这关过了。十分钟后,陈峰在 pR 下面评论了:
“@林晨 提交信息写得太简单了,‘fix styles’这种描述不够具体。请参考我们的提交规范:feat:、fix:、docs: 等前缀,后面跟模块名和具体改动。另外,你这个分支是从 main 直接拉的吗?应该从 develop 拉。重新弄一下”。
林晨看着评论,脸有些发烫。他确实没仔细看提交规范文档,分支也拉错了。
“好的,马上改”。他在下面回复。重新操作,又花了二十分钟。这次他仔细看了规范文档,提交信息写成:fix(asset-card): adjust bid button styles with gradient and loading state。
再次提交,陈峰很快点了通过。“好了,下次注意”。陈峰私聊发来一句。
“明白,谢谢陈导”。林晨回复,手心有点出汗。
夜战与思维的火花
这只是个最简单的代码提交流程,就出了岔子。真正的编码挑战还在后面。
下午剩下的时间,林晨强迫自己进入高效学习状态。他不再试图一次性理解整个项目,而是采用“问题驱动”的方式——遇到什么不懂的,立刻查文档、搜 Stack overflow、看项目里类似的代码是怎么写的。React 的 hooks 机制、typeScript 的类型推导、Vite 的热更新原理…… 一个个概念像拼图一样慢慢拼凑起来。他发现自己多年后端开发积累的编程思维还是有用的——组件化对应模块化、状态管理对应数据流、生命周期对应初始化销毁。只是具体实现方式不同。
到下午六点,正常下班时间。办公室里大部分人还没走,web3 公司似乎没有准点下班的习惯。阿杰和几个同事在讨论一个技术方案,声音时高时低。林晨看了看任务列表,今天计划要完成的“理解项目架构和核心组件”只完成了一半。他犹豫了一下,关掉了准备下班的念头。
房贷月供一万二,试用期薪资三万,存款三十万在缓慢消耗。这份工作不能丢,至少试用期必须过。他点开外卖软件,叫了份隆江猪脚饭,然后继续回到代码世界。
晚上七点,外卖到了。林晨在茶水间快速吃完,回来时看到陈峰也还在工位,正对着三个显示器调试智能合约代码。
“陈导,还不走”?林晨随口问了句。
“嗯,合约有个 gas 优化问题要解决”。陈峰头也没抬,“你呢?第一天就加班”?
“技术栈不太熟,想多花点时间补补”。
陈峰这才转过头,看了林晨一眼:“正常。web3 技术迭代快,前端生态半年一变。保持学习就行。有问题随时问,但尽量先自己查文档”。
“明白”。林晨点头。
回到工位,他泡了杯茶,重新打开电脑。夜晚的办公室安静了许多,只有键盘敲击声和偶尔的讨论。窗外是南山区科技园的夜景,高楼灯火通明。
林晨深吸一口气,点开了 Solidity 的官方文档。既然前端暂时啃不动,他想先看看智能合约那边是什么逻辑。毕竟拍卖的核心业务在链上,理解了合约,前端只是展示层。
Solidity 的语法让他稍微舒服了点——至少是类 c 的语言,有变量、函数、结构体、事件这些熟悉的概念。他找到项目用的拍卖合约代码,一行行读下去。这是一个简单的英式拍卖合约:卖家上架 NFt,设置起拍价和拍卖时长;买家出价,必须高于当前最高价;拍卖结束后,最高出价者获得 NFt,卖家收到款项。
逻辑清晰,但细节很多:出价时要锁定 Eth、拍卖结束后的资金结算、流拍处理、手续费计算…… 林晨看着看着,忽然心里一动。这拍卖逻辑,和他之前研究的量化交易策略里的“趋势跟踪”有点相似——价格在特定规则下形成趋势,直到时间截止形成最终成交。
如果把每个 NFt 看作一只股票,拍卖过程就是一段短期的价格发现过程。那么,能不能用类似的数学模型来描述?这个念头一闪而过,他自嘲地笑了笑。现在连前端代码都写不利索,还想这些。但那个想法像颗种子,悄悄埋在了心里。
归途与自省
晚上九点半,林晨终于把拍卖合约的核心逻辑理清楚了。他合上电脑,颈椎已经僵硬得发疼。
林晨收拾东西,轻声离开。电梯下行时,他看着镜面里自己疲惫的脸,三十五岁的眼角已经有了细纹。回到家快十点了。苏婉还没睡,在客厅备课。
“这么晚”?她抬头,眼里有担忧。
“嗯,技术栈不太熟,加了会儿班”。林晨放下背包,瘫在沙发上。
苏婉起身给他倒了杯水:“别太拼了,身体要紧”。
林晨接过水杯,温热的感觉从掌心传来。他忽然想起白天那些年轻同事讨论游戏的样子,想起自己面对陌生代码时的无力感,想起陈峰那句“保持学习就行”。
“婉婉”,他轻声说,“我觉得自己像个刚入行的新人”。
苏婉坐到他身边,握住他的手:“你本来就是新入行啊。从跨境电商到 web3,跨度这么大,给自己点时间”。
“可是时间不等人”。林晨看着天花板,“周五就要交东西,我现在连门都没入”。
“那也得一步一步来”。苏婉语气温柔,“你忘了当年刚学编程的时候了?也是从 ‘hello world’ 开始的。现在至少你已经有十年经验,学习能力比那时候强多了”。
林晨沉默了一会儿,点点头。是啊,十年前他刚毕业,连 Git 都不会用,现在不也成了资深工程师?技术栈再新,底层逻辑还是相通的。
晚上十一点,林晨躺在床上却睡不着。他干脆拿起手机,搜索“React 快速上手”、“typeScript 实战技巧”。他看到一篇讲 React hooks 最佳实践的文章,正好解答了他白天的疑惑。
凌晨十二点半,林晨闭上眼睛。明天,要从哪里突破呢?他想好了:先不管那些花哨的库和工具,集中精力把核心用法搞熟。
代码可以骗人,但逻辑不会。而逻辑,是他最擅长的东西。