我在字节搬砖两年后,终于鼓起勇气回头学 React

杰出艺术家
2025-12-23 17:02
阅读 1929

上周五晚上九点半,我盯着屏幕上 Go 服务返回的 JSON 数据发呆。我们组刚把一个内部配置中心重构完,后端用 Go 写得飞快,但前端还是老掉牙的 jQuery + Bootstrap 组合。产品经理小王又在群里艾特我:“后端接口都 Ready 了,前端啥时候能上线?双 11 前必须搞定啊!”

我叹了口气——作为一个在字节基础架构组搬了快两年砖的后端工程师,整天和 etcd、Kubernetes、Go 协程打交道,但面对前端这块“神秘领域”,说实话一直有点发怵。直到上个月团建喝多了,隔壁前端组的老李拍着我肩膀说:“兄弟,现在不懂点 React,连看日志界面都显得你 out 了。”

行吧,被逼上梁山。趁着周末两天没值班,我决定从零开始搞个 React 项目,目标很朴素:把我们那个丑到爆的内部工具页面重写一下。没想到这一写,居然真跑起来了!今天就把我踩过的坑、装过的 VSCode 插件、以及怎么把 Go 后端和 React 前端串起来的经验,全盘托出。


为什么是 React?而不是 Vue 或 Svelte?

其实我一开始想试试 Vue3,毕竟听说上手快。但打开 GitHub 一搜,公司内部 80% 的新项目(包括抖音中台某些模块)都用 React,连我们组最近孵化的 DevOps 平台也选了它。再加上 TypeScript + React 的组合在字节几乎成了标配,面试官张哥上次还吐槽:“现在招人,不会 React hooks 直接 pass。”

所以,与其对抗趋势,不如拥抱它。而且我发现 React 的组件化思想,和我们写 Go 微服务的“单一职责”原则莫名契合——每个组件只干一件事,props 就像 API 入参,state 就像服务内部状态。这种思维迁移让我这个后端仔舒服了不少。


环境搭建:别被 node_modules 劝退

第一步永远是最劝退的。我在家里的 MacBook 上折腾,先确保 Node.js 装了(建议用 nvm 管理版本),然后:

npx create-react-app config-center-ui --template typescript

小贴士:一定要加 --template typescript!别信什么“先学 JS 再转 TS”的鬼话,在字节,没有类型检查的代码根本过不了 CI。

等了五分钟(网速慢+依赖多),项目骨架搭好了。这时候我的 VSCode 自动弹出提示:“检测到 React 项目,是否安装推荐扩展?”——赶紧点“是”。我常用的插件包括:

  • ESLint:配合 Prettier,自动格式化,拯救我的缩进强迫症
  • Auto Rename Tag:改 HTML 标签再也不怕漏关
  • Thunder Client:轻量级 API 测试,比 Postman 快
  • Import Cost:实时显示 import 包的大小,防 bundle 膨胀

进入项目目录,npm start,localhost:3000 弹出熟悉的欢迎页。那一刻,我仿佛看到了双 11 上线的曙光(虽然可能只是幻觉)。


第一个组件:从展示静态数据开始

我们的内部工具很简单:拉取一个配置列表,展示 key-value 对。先不管后端,我 mock 一段数据:

// src/components/ConfigList.tsx
import React from 'react';

interface ConfigItem {
  key: string;
  value: string;
}

const mockConfigs: ConfigItem[] = [
  { key: "feature_flag_new_ui", value: "true" },
  { key: "timeout_ms", value: "5000" },
];

export const ConfigList: React.FC = () => {
  return (
    <div className="config-list">
      <h2>Configuration Center</h2>
      <ul>
        {mockConfigs.map((item, index) => (
          <li key={index}>
            <strong>{item.key}</strong>: {item.value}
          </li>
        ))}
      </ul>
    </div>
  );
};

注意几个细节:

  • 用了 TypeScript 定义 ConfigItem 接口,避免后期字段乱改导致运行时崩
  • key={index} 虽然不完美,但对静态 mock 数据够用(真实场景应该用唯一 ID)
  • 没有 CSS-in-JS,直接用原生 className,因为后续要接入公司统一 Design Token

App.tsx 里引入:

import { ConfigList } from './components/ConfigList';

function App() {
  return (
    <div className="App">
      <ConfigList />
    </div>
  );
}

刷新页面,列表出来了!虽然丑,但至少不是白屏。那一刻我差点截图发朋友圈:“React 初体验成功!”(后来忍住了,怕被前端同事笑)


连接 Go 后端:跨域、fetch 和错误处理

现在要把 mock 数据换成真实接口。我们的 Go 服务跑在 http://localhost:8080/api/configs,返回标准 JSON。

但在 React 里直接 fetch 会遇到 CORS 问题。本地开发时,最简单的解法是在 package.json 里加代理:

{
  "name": "config-center-ui",
  "proxy": "http://localhost:8080"
}

这样所有 /api 开头的请求都会被转发到 Go 服务。重启 dev server,然后改组件:

import React, { useState, useEffect } from 'react';

interface ConfigItem {
  key: string;
  value: string;
}

export const ConfigList: React.FC = () => {
  const [configs, setConfigs] = useState<ConfigItem[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    fetch('/api/configs')
      .then(res => {
        if (!res.ok) throw new Error('Network response was not ok');
        return res.json();
      })
      .then(data => {
        setConfigs(data);
        setLoading(false);
      })
      .catch(err => {
        console.error('Failed to fetch configs:', err);
        setError(err.message);
        setLoading(false);
      });
  }, []);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <div className="config-list">
      <h2>Configuration Center</h2>
      <ul>
        {configs.map((item, index) => (
          <li key={item.key}>
            <strong>{item.key}</strong>: {item.value}
          </li>
        ))}
      </ul>
    </div>
  );
};

这里用了 useEffect 做副作用(相当于 componentDidMount),配合 useState 管理加载状态。关键点

  • 一定要处理 error 和 loading 状态,否则用户看到白屏会以为系统挂了
  • Go 后端记得设置 CORS 头(生产环境用 Nginx 配),否则线上还是会跨域失败

调试时,我一度卡在 404——原来 Go 路由写的是 /configs,但我前端写了 /api/configs。查了半天才发现代理只转发路径,不改路由。改完 Go handler 后,数据终于出来了!


性能优化:别让 render 成为瓶颈

作为性能敏感型后端,我本能地打开了 Chrome DevTools 的 Performance 面板。发现每次 state 更新,整个列表都在 re-render。虽然现在只有两条数据,但以后上千条呢?

于是加上 React.memo 防止子组件不必要更新:

const ConfigItemRow: React.FC<{ item: ConfigItem }> = React.memo(({ item }) => {
  return (
    <li>
      <strong>{item.key}</strong>: {item.value}
    </li>
  );
});

// 在 ConfigList 里用 ConfigItemRow 替代 inline li

另外,fetch 放在 useEffect 里没问题,但如果未来要支持搜索或分页,就得考虑用 useCallback 包裹回调函数,避免闭包陷阱。这些细节,在字节的 Code Review 里都是必查项。


部署上线:GitHub Pages + CI/CD

本地跑通只是开始。为了演示给小王看,我决定部署到 GitHub Pages。

先安装 gh-pages:

npm install --save-dev gh-pages

package.json 加脚本:

{
  "scripts": {
    "predeploy": "npm run build",
    "deploy": "gh-pages -d build"
  },
  "homepage": "https://yourname.github.io/config-center-ui"
}

然后 npm run deploy,几分钟后,页面就在 https://yourname.github.io/config-center-ui 可访问了!

当然,公司内部肯定不用 GitHub Pages,而是走自研的 CDN + 容器平台。但原理一样:build 出静态文件,扔到对象存储,配个域名。值得一提的是,我们组最近用 Go 写了个 CLI 工具,一键打包前端并推送到内部 Registry,比手动操作快十倍。


总结:后端学前端,值不值?

折腾完这个小项目,我最大的感受是:React 没想象中那么可怕。它的核心思想很清晰——UI = f(state),剩下的都是工程细节。

更重要的是,当我能独立完成一个前后端闭环,和前端同事沟通时底气足多了。上周站会上,我甚至能指出他们某个组件的状态管理可以优化,老李惊讶地说:“你什么时候偷偷卷 React 了?”

如果你也是后端,别被“前端水深”吓住。从一个小项目开始,用你熟悉的工程思维去拆解,很快就能上手。毕竟,在字节这样的公司,全栈能力越来越吃香——就算不做 fullstack,懂一点前端,debug 时也能少求人一次。

对了,这个项目的代码我放 GitHub 上了:github.com/yourname/config-center-ui(名字已脱敏)。欢迎 star,更欢迎提 PR 教我怎么写得更好。

最后,祝各位搬砖人少加班,多上线。双 11,我们稳了!

评论 0

最热最新
暂无评论
杰出艺术家Lv.1
0
影响力
0
文章
0
粉丝