从零跑通第一个React应用:一个国企程序员的踩坑实录

技术达人Cloud
2026-04-03 06:37
阅读 2130

上周五下午四点半,我正悠闲地刷着知乎,准备迎接周末双休的美好时光——没错,咱在杭州一家“养老型”国企做前端,最大的福报就是不加班、双休稳如狗。突然,领导在钉钉群里@我:“小张啊,下个月有个内部管理系统的改造项目,打算用React重写,你先熟悉一下,下周三前跑个demo出来。”

我当时内心OS:React?不是说好继续用Vue2养老到退休吗?但转念一想,隔壁阿里网易都在卷React生态,连我们这种“慢节奏”单位也开始跟进了。再说了,万一哪天真想跳槽去大厂,没点React经验简历都过不了筛。

于是,我打开Claude Code(最近重度依赖它写样板代码),丢了个prompt:“帮我生成一个最简React入门教程,包含安装、组件、状态管理,适合刚接触React的开发者。” 它秒回了一堆内容,但说实话,太理想化了——现实哪有那么顺利?

今天这篇文章,就记录我从create-react-app开始,到成功跑起第一个能点能动的小应用全过程,中间踩的坑、掉的头发,全都给你摊开讲明白。


为什么是React?不是Vue?

先别急着喷我“技术选型盲目”。其实我们团队去年还在用jQuery+Bootstrap搭后台,直到某次线上事故——产品经理临时加了个“动态表单嵌套”需求,后端返回的JSON结构层级深得像俄罗斯套娃,前端硬生生用DOM操作拼了三天,最后上线还崩了两次。

运维老哥在复盘会上幽幽地说:“你们要是用个现代框架,这事儿半小时搞定。”

痛定思痛,团队决定拥抱现代化。Vue3虽然香,但考虑到未来可能对接公司中台系统(人家全是React写的),加上GitHub上React生态更庞大、Embedding场景(比如把第三方UI组件嵌入现有系统)支持更好,最终拍板:上React。


工具链选择:别被Webpack吓退

很多人卡在第一步:环境配置。Webpack?Babel?ESLint?听着就头大。但好消息是——你现在根本不用碰这些

我直接用了官方脚手架:

npx create-react-app my-first-react-app
cd my-first-react-app
npm start

浏览器自动打开 http://localhost:3000,经典的React旋转Logo出现。搞定!全程5分钟,连Node版本都没检查(反正我本地是v18.17.0,够新)。

📌 小贴士:如果你在国内,建议提前设置npm镜像源,不然create-react-app可能卡半天。我一般用:

npm config set registry https://registry.npmmirror.com

当然,也有同事推荐Vite。但对我们这种“求稳不求快”的国企项目来说,CRA(Create React App)的默认配置已经足够——而且它背后有Facebook背书,兼容性有保障,连IE11都能靠polyfill勉强撑住(虽然我们系统只支持Chrome最新版)。


第一个组件:别只会写Hello World

按照惯例,教程都会让你改App.js,输出“Hello, React!”。但我觉得这太无聊了,不如做个带交互的待办事项列表(Todo List),既能体现状态管理,又贴近实际业务。

我在Claude Code里输入:“用函数式组件写一个简单的Todo List,包含添加、删除、标记完成功能,样式简洁。”

它返回的代码基本可用,但有几个问题:

  1. 没有输入校验(空字符串也能提交)
  2. 状态更新直接用push,违反React不可变原则
  3. 没有key警告处理

于是我手动调整,最终代码如下:

// src/App.js
import React, { useState } from 'react';
import './App.css';

function App() {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');

  const addTodo = () => {
    if (inputValue.trim() === '') return; // 防止空输入
    setTodos([
      ...todos,
      {
        id: Date.now(), // 简易ID生成,生产环境请用uuid
        text: inputValue.trim(),
        completed: false
      }
    ]);
    setInputValue(''); // 清空输入框
  };

  const toggleTodo = (id) => {
    setTodos(
      todos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <div className="app">
      <h1>我的待办事项</h1>
      <div className="input-section">
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="输入任务内容..."
          onKeyPress={(e) => e.key === 'Enter' && addTodo()}
        />
        <button onClick={addTodo}>添加</button>
      </div>
      <ul className="todo-list">
        {todos.map(todo => (
          <li key={todo.id} className={todo.completed ? 'completed' : ''}>
            <span onClick={() => toggleTodo(todo.id)}>
              {todo.text}
            </span>
            <button onClick={() => deleteTodo(todo.id)}>×</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

配套的CSS也简单美化了一下:

/* src/App.css */
.app {
  max-width: 600px;
  margin: 40px auto;
  padding: 20px;
  font-family: 'Segoe UI', sans-serif;
}

.input-section {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.input-section input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.input-section button {
  padding: 8px 16px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.todo-list li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.todo-list li.completed span {
  text-decoration: line-through;
  color: #888;
}

.todo-list li button {
  background: none;
  border: none;
  font-size: 20px;
  color: #ff4d4d;
  cursor: pointer;
}

运行效果:输入任务,回车或点“添加”,列表实时更新;点击文字可标记完成;点×删除。麻雀虽小,五脏俱全。


踩坑实录:那些文档不会告诉你的事

坑1:状态更新异步?不,其实是批量更新

刚开始写的时候,我尝试在addTodo里连续调用两次setTodos,结果发现只有最后一次生效。查了资料才知道,React在事件处理器中会批量更新状态,而不是真正的“异步”。

解决办法:要么合并操作,要么用函数式更新(即传入一个函数给setState)。

坑2:key警告不是小事

第一次没给<li>key,控制台飙红:

Warning: Each child in a list should have a unique "key" prop.

我随手改成index作为key,结果删除中间项时,后面所有项的状态都错乱了。原因?React靠key识别元素身份,用index会导致“身份漂移”。

正确做法:用稳定且唯一的ID,比如数据库主键,或者像我这样用Date.now()(仅限Demo)。

坑3:Embedding第三方组件时的样式冲突

后来我想嵌入一个GitHub开源的日期选择器(比如react-datepicker),结果它的CSS把我的按钮样式全覆盖了。

解决方案:

  • 使用CSS Modules(CRA默认支持,文件名.module.css
  • 或者用CSS-in-JS库如styled-components
  • 或者要求第三方组件提供无样式的headless版本

我们最终选了第一种,既保持原生CSS的熟悉感,又避免全局污染。


开发效率提升:我的工具链组合拳

作为懒人程序员,能用工具绝不手敲。我的日常组合:

工具 用途 备注
VS Code + Prettier 自动格式化代码 团队统一风格,PR不再吵缩进
React Developer Tools 浏览器调试组件树 必装插件,看props/state一目了然
Claude Code 生成样板代码/解释错误 比ChatGPT更懂代码上下文
GitHub Copilot 智能补全 写useEffect时自动提醒依赖数组

特别说下Claude Code——它不仅能生成代码,还能根据我的项目结构推理。比如我问:“如何在当前Todo List里加个‘清空已完成’按钮?” 它直接返回了完整的函数和JSX,连CSS类名都匹配上了。


部署上线:从localhost到内网访问

本地跑通只是第一步。我们系统要部署到内网服务器,供全公司使用。

CRA构建很简单:

npm run build

生成build目录,里面是静态文件。交给运维,他们用Nginx托管:

server {
    listen 80;
    server_name internal-todo.yourcompany.local;

    location / {
        root /var/www/my-first-react-app/build;
        try_files $uri $uri/ /index.html;
    }
}

注意那个try_files——这是为了支持React Router的客户端路由(虽然我们还没用到,但提前配好省事)。

上线当天,测试妹子提了个bug:“刷新页面404!” 我一看,果然是没配fallback。赶紧让运维加上,问题解决。血泪教训:SPA必须配history fallback!


总结:React真的难吗?

写完这个小应用,我的感受是:React的核心概念其实很朴素——组件化、单向数据流、状态驱动视图。难点不在语法,而在思维方式的转变。

以前写jQuery,是“我要操作哪个DOM”;现在写React,是“我的状态是什么,UI应该长什么样”。

另外,得益于丰富的生态和工具链,入门门槛比想象中低很多。只要你别一上来就啃Redux源码,别纠结Fiber架构原理,先做出能跑的东西,信心就来了。

至于我?下周三的demo顺利通过,领导还夸我“学得快”。其实我心里清楚:一半功劳归Claude Code,一半归GitHub上无数开源项目的示例代码。

最后送大家一句我在阿里朋友常说的黑话:“不要重复造轮子,除非你想造个更好的轮子。” React生态里,轮子多到能拼出一辆特斯拉——你要做的,只是学会怎么装上去。

周末愉快,打工人!

评论 0

最热最新
暂无评论
技术达人CloudLv.1
0
影响力
0
文章
0
粉丝