最近带团队的时候发现一个现象:很多刚入行或者转岗的前端同学,一上来就抓着React的源码啃,或者在各种进阶教程里反复横跳,结果基础没打牢,碰到稍微复杂的界面就卡壳。这篇React初阶学习笔记,就是给这类同学准备的。我计划用几篇笔记的篇幅,把React从入门到进阶的学习路径、核心概念、实操细节和踩坑经历串起来。第一篇先解决“地基”问题:React到底是什么、环境怎么搭、JSX和组件的基础怎么打,以及初学阶段最常见的报错怎么排查。不管你是完全零基础的转行者,还是已经会写一点原生JavaScript想上手框架的人,这篇笔记都可以直接对照着学。
1. 先搞明白React是干嘛的,再决定要不要学它
1.1 React的定位:它解决的是“视图层”的问题
React本质上是一个用于构建用户界面的JavaScript库,注意是“库”而不是“框架”。这个词的差别很多人会忽略,但实际影响你后面的学习方式。库的意思是它只管视图这一层,至于数据怎么从后端取、路由怎么跳转、状态仓库怎么设计,React都不强制你用什么方案,而是把选择权留给你。这一点和完整框架的思路完全相反。初学阶段最忌讳的就是被“库”和“框架”的概念绕晕,其实你可以把它理解为一个专门负责“把数据变成页面”的转换器:数据一变,页面也跟着变,你不用手动去操作DOM,React会帮你算清楚该更新哪些地方。
很多人刚开始学React会被“虚拟DOM”这个词吓到。我的建议是,初阶阶段不用深挖虚拟DOM的实现细节,把它理解成一个“中间副本”就够了。当你改变数据时,React先在内存里用这个副本做比较,找出真正变化的节点,再一次性更新到真实页面上。这样做的好处是减少对页面DOM的频繁操作——浏览器里最耗性能的操作之一就是改DOM,频繁改会把页面拖慢。你只要知道,React帮你做了“数据驱动视图”这件事,初阶的核心就是搞清楚数据和视图之间那层对应关系。
1.2 初学阶段最应该掌握的学习顺序
我见过太多初学者踩同一个坑:刚开始就想把React全家桶(路由、状态管理、UI组件库)一次配齐。结果环境没跑通,光配置就搞了一天,最后连一个Hello World都没看到。如果你也在这个阶段,先把那些东西全部放一边。初学阶段最核心的学习范围是:JSX语法、函数组件、props、useState这个钩子、事件处理、条件渲染、列表渲染。就这七个东西,足够你写出七成以上的常规界面交互。等你把这七个点练熟了,再去看路由、状态管理、UI组件库,你会发现它们本质上都是这些基础能力的扩展,理解起来会轻松很多。
这个顺序是有讲究的。JSX是React的世界观,不懂它你就读不懂任何组件代码;组件是React的基本组成单元,所有页面都是组件的组合;props负责父传子,useState负责组件内部状态变化;条件渲染和列表渲染是实际做页面时天天要用的逻辑。所以我把它们排在第一篇笔记里,一篇讲透,比断断续续看几十个小时视频有效得多。下一篇笔记我预计会讲副作用、表单处理和组件间通信,那是在这几块基础之上的递进。
2. 搭建开发环境:从零跑通第一个React页面
2.1 版本与工具准备:用命令行完成环境配置
搭建React开发环境其实比想象中简单,不需要手动下载一堆文件。你需要的基础条件只有两个:一是装了Node环境的电脑,版本越新越好,建议用LTS版本(长期维护版),因为开发工具更新迭代快,版本太老会遇到很多兼容性报错,而且报错的提示内容往往很晦涩;二是一个趁手的代码编辑器,装好之后打开终端,确认命令行里能正常输出版本号,环境就算就绪了。
这里有个常被忽略的点:环境安装依赖包时经常遇到网速慢或者拉取失败的问题,原因是默认的下载源访问不稳定。解决办法很简单,把包管理器指向镜像源,或者在初始化命令时加上镜像参数。这样做之后,安装速度会从几分钟降到几十秒,新手在这里花的时间能省下一大半。不要小看这一步,很多人第一天学React不是被JSX难倒的,而是被安装依赖卡到崩溃。
2.2 创建项目与启动开发服务器:三步跑起来
确认环境没问题之后,创建一个新项目只需要一条命令:在终端里输入npm create,后面跟上你的项目名和官方推荐模板参数。这一步会在当前目录下生成一个工程文件夹,文件夹内部已经帮你配置好了转译、打包、自动刷新这些开发时需要的能力。你平时可能听人说过“配置地狱”,指的是早期需要手动改一堆配置文件才能跑起来,现在这些已经全部封装好了,你用脚手架创建的项目是开箱即用的。
npm create 项目名 模板参数 cd 项目名 npm start项目创建完成后,切进项目目录,运行npm start启动开发服务器。等终端打印出编译成功的信息,在浏览器里打开对应地址,你就能看到React默认生成的欢迎页面。这里要注意的是:命令行的启动信息里会明确标注出本地访问地址,如果浏览器无法打开,优先检查这个服务是否真的起来了,而不是急着改代码。我第一次跑通这个流程时其实犯过糊涂:终端报了一个端口被占用的警告,页面怎么刷都出不来,后来才发现是之前的服务没关掉。解决办法就是关闭占用端口的进程,或者按照提示直接换个端口访问,React的脚手架工具一般会自己处理这种冲突,给出一个备用端口,你照着提示走就行。
创建完成后,整个项目里你最需要关注的文件就一个:App.js。这个文件是页面入口组件的源代码,你可以在里面改一行文字,保存后浏览器会自动刷新并显示修改结果,这个机制叫热更新。初学阶段完全不需要去关心其他配置文件的内容,先把这一个文件玩明白,比什么都强。
3. 初阶核心语法拆解:JSX、组件、props与state
3.1 JSX:在JavaScript里写HTML,换汤不换药
JSX是React最直观、也最容易让新手产生“这不就是HTML吗”错觉的语法。表面上看确实和HTML差不多,但它真不是HTML,它是JavaScript的一种语法扩展,允许你在JS代码里直接写类似HTML的标签结构。比如你可以在组件函数里写return <div>你好</div>,这段代码会被预先转译成正常的JavaScript函数调用,最终动态生成页面元素。这种写法的好处是:组件的结构和逻辑放在同一个文件里,你不用像以前那样在HTML文件和JS文件之间来回切换看逻辑,肉眼扫一眼就知道这段代码渲染出来是什么样子。
function Welcome() { const name = "学习React的人"; return <div>你好,{name}</div>; }但JSX有些细微规则一定要记牢,否则报错会让你摸不着头脑。第一,JSX只能有一个根节点,你return两个兄弟标签会直接报错,解决办法是包一层容器标签,或者用更轻量的包裹符号,这种符号在渲染后的页面上不会留下多余节点。第二,想在JSX里写JavaScript表达式,必须用花括号包起来,比如显示一个变量的值,或者做一次三元判断。第三,JSX里的class要写成className,for要写成htmlFor,这是它跟HTML的一个显著差异,记不住也没关系,报错时多看提示,翻译过来都指向这些细节。第四,所有标签必须显式闭合,自闭合的标签要写完整。
3.2 函数组件与class组件的区别:初学先掌握哪一种
组件是React的基本单位,一个页面就是由若干组件拼接起来的。组件的名字首字母必须大写,这是React区分组件和普通标签的一个硬性约定。你写div、span这种小写标签是原生元素,但写一个首字母大写的自定义标签,React就会把它当作组件来解析。初学阶段最常见的报错之一就是把组件名写成小写,然后发现页面渲染出来一片空白,控制台却在疯狂提示组件不存在。
React历史上支持两种组件写法:class组件和函数组件。class组件是早期的主力,代码看起来像定义一个类,内部还有生命周期方法等概念,这套东西对新手来说理解成本偏高。函数组件的本质就是一个接收数据并返回JSX的函数,配合后面的钩子能力,它已经能覆盖绝大部分业务场景。所以我的明确建议是:初学阶段直接学函数组件,不要去碰class组件。理由很简单,现代代码库里90%以上的新代码都是函数组件,你要读的开源项目、要参考的案例代码,基本都是函数组件写法。把这一种写法吃透,你就能读懂大多数代码。等以后确实碰到老项目里的class组件,再去补那个概念,完全来得及。
3.3 props与state:数据从哪来、怎么变
props和state是React数据机制的两个核心角色。props是父组件传给子组件的数据,它是只读的,子组件不能去修改收到的props。举个例子,你写一个通用的卡片组件,图片地址和标题文字由父组件传进来,卡片组件内部只负责展示,不负责修改这些数据,这就是props的正确用法。强调props只读是有原因的:React推崇数据单向流动,父组件把数据传下去,子组件把变化汇报上来,整个数据流清晰可追踪,调试时不会出现“哪个组件改了数据”这种悬案。
const [count, setCount] = useState(0);state是组件内部的可变状态。在函数组件里,创建state的写法是用useState这个钩子。useState返回一个数组,第一个元素是当前值,第二个元素是更新这个值的函数。很多新手会困惑为什么要多一个更新函数,直接给变量赋值不行吗。原因在于,直接修改变量不会触发页面重新渲染,而React的一切更新都源于状态变化,只有通过更新函数去改变state,React才知道“这个组件的状态变了,需要重新执行组件函数,得到新的界面”。可以拿“视图刷新”当作一个信号:状态更新函数就是向React发送的提醒信号,没有这个信号,数据再变页面也不理你。
这里还想提醒一个新手几乎必犯的误区:state不能直接修改嵌套对象。比如数组的push方法会直接改原数组,React检测不到这个改变,页面就不会更新。正确的做法是返回一个新数组、新对象,让引用发生变化。这个规律我后面会在列表渲染部分再强调一次,因为它是很多“状态不刷新”问题的根源。
4. 初阶交互必会:事件处理、条件渲染与列表渲染
4.1 事件处理:函数式更新与this陷阱
React里的事件处理其实比原生JavaScript要顺手,你不用再监听一个个具体的DOM事件,直接在JSX标签上绑定事件即可。常见的事件类型包括点击、输入、提交、鼠标移动等,绑定格式都是在事件命名前加上on前缀,后面跟一个处理函数。这里要注意的是,绑定的必须是函数本身,而不是函数的调用结果。很多新手会写onClick={handleClick()},结果页面加载完函数立刻执行,点击反而没反应。正确写法是onClick={handleClick},只传函数名,不执行。
function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> 点击了 {count} 次 </button> ); }初学class组件时,事件处理还会遇到this指向不明确的坑,解决方案是绑定时用箭头函数。不过既然我建议直接学函数组件,这个问题就基本不存在了。函数组件里的事件处理函数本身就是普通函数,配合闭包能力,访问状态值完全没问题。状态更新的细节值得多说一句:如果在同一个事件处理里多次调用状态更新函数,React会将这些更新合并处理,最终只触发一次渲染。而且要特别记住,状态更新是异步的,更新函数执行之后,立刻读同一个state变量,读到的仍然是旧值。正确做法是把更新函数写成接收新值作为参数的写法,或者用函数式更新,让更新逻辑基于最新状态计算,这是初学阶段很容易踩到的异步陷阱。
4.2 条件渲染与列表渲染:key值里面藏着大坑
条件渲染就是“满足某条件就渲染A,否则渲染B”,最常见的是三元运算和与运算的写法。比如大量渲染与运算的写法在条件不满足时会渲染false,而React不会渲染布尔值,页面就什么都不显示。但有个特殊情况:0会被渲染出来。如果你写的是“列表长度不为0时显示列表”,新手很容易写成数组长度 && 列表,当长度为0时,页面上会直接显示一个数字0,而不是什么都不显示。解决办法是把长度判断改成数组长度 > 0 && 列表,让表达式结果严格返回布尔值而不是数字0。这类小细节就是一个典型的“页面表现诡异、控制台却毫无报错”的问题。
function TodoList({ todos }) { return ( <ul> {todos.map((todo) => ( <li key={todo.id}>{todo.title}</li> ))} </ul> ); }列表渲染的固定写法是把数组映射成JSX元素数组,直接把新数组放到需要渲染的位置。这里的关键是给每个渲染项设置key,key的作用是帮助React在列表增删、排序时,精准识别哪些元素被改动过,从而复用已有节点,而不是全部重新渲染。key必须设置在循环的直接元素上,而且必须选择数据里唯一且稳定的标识——比如业务数据的编号字段。实际开发中经常看到有人把数组下标当key,这样做在简单列表里看不出问题,可一旦你允许用户增删或排序,就会出现选中状态错乱、输入框内容对不上行的诡异现象。因为下标会跟着位置变化,React拿对不上号的标识去匹配,自然会出现张冠李戴。
顺手再说一个性能相关的细节:条件判断尽量写在渲染之前,而不是在JSX里套很多层嵌套三元。一来代码可读性强,二来避免不必要的函数调用。等到项目复杂了,你会发现这种小习惯直接决定代码能不能维护。
5. 初学阶段最容易踩的坑与排查经验
5.1 常见报错与解决方案速查表
把初学阶段最长碰到的报错集中整理了一下,几乎每个都有人问过。
| 报错现象 | 常见原因 | 处理办法 |
|---|---|---|
| 页面空白且控制台提示组件名称未定义 | 组件名首字母小写,或根本没有导入该组件 | 把组件名改为大写开头,检查导入路径是否正确 |
| JSX解析报错:标签未正确闭合 | 自闭合标签少写了斜杠,或标签配对错误 | 按报错提示的行号逐行检查标签结构 |
| 更新state后页面不刷新 | 直接用push、splice等方法改数组或改对象属性 | 改用展开运算创建新数组或新对象再更新 |
| 循环生成重复标签但未设置key | 列表项缺失key属性 | 给最外层循环元素添加唯一稳定的key |
| 页面无法加载,端口被占用 | 开发服务器进程未关闭 | 关闭旧进程或按提示切换新端口访问 |
这张表里的最后一行,我实际遇到过的频率比想象中高。原因不是技术难,而是很多编辑器里的终端面板开关按钮不够显眼,旧服务一直悄悄跑着。排查经验是:开发服务器起不来时,第一步永远先看终端最后两行的输出信息,报错内容里通常已经写明了解法,不要一上来就怀疑代码逻辑。
5.2 调试思路:先看状态,再看渲染
初学阶段调试时要记住一个基本思路:先确认状态对不对,再确认渲染对不对。如果组件里显示的内容和预期不符,先用console.log把参与计算的关键变量在渲染函数里打出来,看它是不是你期望的值。变量没问题,再排查JSX写法是否漏了花括号、是否拼错了变量名。这个顺序能帮你快速定位问题,而不是在JSX里盲改。
一个很实用的方式是在组件顶部打印state对象,每次组件重新渲染时都会输出一份当前状态。你就能直观看到:状态什么时候变了、变了以后组件函数是不是重新执行了。如果状态变了但渲染函数没重新执行,问题大概率在更新函数没有生效,回到上面说的state直接修改陷阱。如果状态没变但页面变了,那就要往父组件方向排查,大概率是有别的地方在控制数据源。
调试工具方面,除了浏览器的开发者工具之外,建议安装React开发者工具扩展,它能以树形结构展示组件层级,逐个查看每个组件的props和state。这个工具对查层级复杂的组件问题帮助极大,你可以顺着组件树自上而下检查数据在每一层的传递情况,瞬间暴露“某个组件压根没收到props”这类问题。
5.3 给初学者的几条实操建议
坦白说,我带过不少人学React,也远程帮朋友排查过不少报错,大家的问题高度重合:不是智商问题,也不是基础太差,而是学习方法出了问题。不少人是看视频觉得全会,一动手就废。为什么会这样?因为React的语法密度比原生JavaScript高,光是花括号、圆括号、箭头函数三种写法组合在一起就需要肌肉记忆,看得再多不动手,等于白看。
第一条建议,每天动手写代码的时间至少要比看教程的时间多一倍。React是一个实践性极其强的技能,看视频和文档只能让你“看懂”,动手写才会让你真正“会写”。你可以把教程里的例子手打一遍,然后改动几个变量、换几种写法,观察页面的变化。第二条,遇到报错先读英文提示,不要急着翻译成中文,报错信息里的组件名、行号、变量名都是定位线索,翻译反而可能丢失信息。第三条,写代码时保持小步验证的习惯:改一小段逻辑就运行一次,确认没问题再进行下一步,这能避免你一次性堆积大量问题却不知道从哪开始排查。第四条,把常用报错形成为自己的速查笔记,这次遇到的坑记录下来,下次遇到同类问题直接照方抓药,学习效率会明显提升。
我自己的体会是,学习React第一周最容易放弃的阶段就是环境搭好之前。等你在浏览器里真正看到自己写的那行字渲染出来,后面的一切都会顺很多。先把这篇里的基础过一遍,有问题随时回来翻,效果比硬着头皮追下一篇要好得多。下一篇笔记我预计会接着讲副作用处理、表单受控组件以及父子组件通信,到时候见。