从零跑通第一个React应用:一个国企程序员的踩坑实录
上周五下午四点半,我正悠闲地刷着知乎,准备迎接周末双休的美好时光——没错,咱在杭州一家“养老型”国企做前端,最大的福报就是不加班、双休稳如狗。突然,领导在钉钉群里@我:“小张啊,下个月有个内部管理系统的改造项目,打算用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,包含添加、删除、标记完成功能,样式简洁。”
它返回的代码基本可用,但有几个问题:
- 没有输入校验(空字符串也能提交)
- 状态更新直接用
push,违反React不可变原则 - 没有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