我第一次接触 React 的时候,其实挺抗拒的。那时候 jQuery 写得正顺手,突然告诉我 HTML 可以写在 JS 里,第一反应是"这什么邪教"。

后来硬着头皮学了,大概过了两周,突然就通了。回过头看,React 的核心概念其实不多,难的是转变思维方式。

这篇文章就是把我当初踩过的坑和理解过程整理出来,希望能帮你少走点弯路。


先搞清楚一件事:React 到底在解决什么问题

前端开发最头疼的事情之一,就是数据变了,页面怎么跟着变

以前我们手动操作 DOM:数据更新了,找到对应的节点,改 innerHTML,可能还要处理一堆事件绑定的移除和重建。项目一小还行,一大就变成噩梦。

React 的核心思路说白了就一句话:

你只管告诉 React 当前的数据长什么样,它帮你算出页面该怎么变。

这个思路叫 声明式 UI。你不需要描述"怎么一步步把页面改成新的样子",你只需要描述"新的页面长什么样"。


环境搭建,跳过那些有的没的

网上很多教程会花大量篇幅讲 Webpack 配置、Babel 转译之类的。说实话,入门阶段不用管这些

直接用官方脚手架就行:

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

浏览器自动打开 localhost:3000,能看到页面,环境就算搭好了。

如果你用 Vite,也完全可以,体验甚至更好:

npm create vite@latest my-app -- --template react

更快,配置也更清爽。

别在环境搭建上纠结太久,先写代码,以后再慢慢理解构建工具。


JSX:看起来像 HTML,但它是 JavaScript

打开 src/App.js,你会看到这样的代码:

function App() {
  return (
    <div>
      <h1>Hello World</h1>
    </div>
  );
}

第一次看会觉得:这不就是 HTML 吗?

但它不是。JSX 最终会被编译成 React.createElement() 的调用。你可以理解为,JSX 是一种让你在 JavaScript 里更舒服地描述 UI 结构的语法糖

既然是 JavaScript,就有一些和 HTML 不一样的地方,这几点早期经常坑人:

1. class 要写成 className

// 错
<div class="container">...</div>

// 对
<div className="container">...</div>

因为 class 是 JavaScript 的保留字。

2. 行内样式要传对象

<div style={{ color: 'red', fontSize: '16px' }}>
  红色文字
</div>

注意是双大括号,外层是 JSX 的表达式语法,内层是对象。

3. 所有标签必须闭合

// 错
<img src="photo.jpg">

// 对
<img src="photo.jpg" />

就这些。其他的用着用着自然就熟了,不用提前背。


组件:React 的基本单元

React 应用是由一个个组件拼起来的。一个按钮是一个组件,一个导航栏是一个组件,整个页面也可以是一个组件。

组件本质上就是一个 返回 JSX 的函数

function Greeting() {
  return <p>你好,欢迎来到我的网站</p>;
}

然后你可以在别的组件里用它:

function App() {
  return (
    <div>
      <Greeting />
      <Greeting />
      <Greeting />
    </div>
  );
}

页面上会出现三行"你好,欢迎来到我的网站"。

组件可以复用,可以嵌套,这就是 React 搭建界面的基本方式。没什么神秘的。


Props:组件之间的传话筒

组件不是孤立的,它们经常需要接收外部数据。这个机制叫 props(properties 的缩写)。

function UserCard({ name, age }) {
  return (
    <div className="card">
      <h2>{name}</h2>
      <p>年龄:{age}</p>
    </div>
  );
}

使用的时候:

<UserCard name="张三" age={28} />
<UserCard name="李四" age={32} />

几个要点:

  • props 是只读的。组件内部不能修改传进来的 props,这是一个很重要的原则。

  • 传数字、对象、布尔值这类非字符串的值,要用 {} 包裹。

  • 解构赋值 { name, age } 写起来更简洁,推荐这么用。


State:让组件"活"起来

Props 是从外面传进来的数据,组件自己管不了。但很多时候,组件内部也需要有自己的数据——比如一个计数器的当前值,一个输入框里的文字。

这就是 state

在函数组件里,用 useState 这个 Hook 来管理状态:

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>你点了 {count} 次</p>
      <button onClick={() => setCount(count + 1)}>
        点我
      </button>
    </div>
  );
}

useState(0) 做了两件事:

  1. 1.定义了一个初始值为 0 的状态变量 count

  2. 2.给了你一个更新函数 setCount

关键的来了:当你调用 setCount 的时候,React 会重新执行这个组件函数,用新的 count 值生成新的 JSX,然后和旧的 DOM 做对比,只更新变化的部分。

这就是 React 最核心的工作机制。整个流程大概是:

数据变化 → 重新执行组件函数 → 生成新的虚拟 DOM → 对比差异 → 更新真实 DOM

理解了这个循环,React 的很多事情都好理解了。


事件处理:和用户互动

React 里的事件处理用的是驼峰命名,而且传的是函数引用:

function Button() {
  function handleClick() {
    alert('被点了');
  }

  return <button onClick={handleClick}>点我</button>;
}

这里有个初学者经常犯的错误:

// 错!这样组件一渲染就会立刻执行 handleClick
<button onClick={handleClick()}>点我</button>

onClick={handleClick()} 里,handleClick() 会被立刻调用,返回值传给 onClick。你需要传的是函数本身,不是函数的调用结果。

如果需要传参数,用箭头函数包一层:

<button onClick={() => handleClick(item.id)}>删除</button>

列表渲染:用 map,别用 for 循环

在 JSX 里没法直接写 for 循环,因为 JSX 本质上是表达式。渲染列表用 Array.map()

function TodoList() {
  const todos = ['学 React', '写项目', '找工作'];

  return (
    <ul>
      {todos.map((todo, index) => (
        <li key={index}>{todo}</li>
      ))}
    </ul>
  );
}

key 是干嘛的?React 用它来识别列表中的每一项,以便在数据变化时高效地更新 DOM。尽量用数据自带的唯一标识(比如 id),不要用 index。用 index 当 key 在数据会增删排序的场景下会出问题。


条件渲染

React 里没有 v-if 这样的指令,条件渲染直接用 JavaScript 的写法:

function Greeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>欢迎回来</h1>;
  }
  return <h1>请先登录</h1>;
}

或者用三元表达式,适合简短的条件:

<div>
  {isLoggedIn ? <UserPanel /> : <LoginForm />}
</div>

还有个常见场景:如果某个值为空就不渲染:

<div>
  {messages.length > 0 && <Notification count={messages.length} />}
</div>

就这些,没什么特殊语法。


useEffect:处理副作用

到目前为止我们讨论的都是"数据 → UI"的映射。但真实应用里还有很多副作用:请求接口数据、操作浏览器的 title、设置定时器……

useEffect 就是干这个的:

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]);

  if (!user) return <p>加载中...</p>;

  return <h1>{user.name}</h1>;
}

第二个参数 [userId]依赖数组。它的意思是:只有 userId 变化的时候,才重新执行这个 effect。如果传空数组 [],就只在组件第一次渲染时执行一次。

这是 useEffect 最容易出错的地方。依赖数组写错了,要么请求发多了,要么数据该更新的时候没更新。写的时候要想清楚:这个 effect 依赖了哪些外部变量?


关于学习路径的几句真心话

学 React 不需要把所有概念一次性搞懂。我的建议是按这个顺序来:

  1. 1.先会用 JSX 和组件,能写静态页面

  2. 2.理解 props,让组件之间能传数据

  3. 3.学会 useState,让页面能响应交互

  4. 4.理解事件处理和条件/列表渲染

  5. 5.学会 useEffect,能请求接口数据

到这一步,你已经能写不少东西了。

至于 React Router、状态管理(Redux/Zustand)、TypeScript 集成这些,等你真的需要的时候再去学。提前学一堆用不上的东西,只会增加焦虑。


最后

React 的 API 设计其实挺克制的。它给你一套描述 UI 的方式和状态管理的基本工具,剩下的都是 JavaScript。

初学阶段最大的障碍不是 API 本身,而是思维转换——从"手动操作 DOM"转向"声明数据和 UI 的关系"。这个转变一旦发生,后面的学习会顺畅很多。

最好的学习方式永远不是看教程,而是动手做一个自己想做的东西。哪怕是做一个 Todo List,也比光看文档强十倍。遇到了不会的,查文档、搜报错信息、问社区,这个过程本身就是编程能力增长的过程。