跳至正文
来两杯美式
返回

React 核心(1):React 组件与函数的本质区别

By 来两杯美式
发布于

React 组件与函数的本质区别

核心心智模型:万物皆函数。组件本质上就是一个 JavaScript 函数,但签了一份特殊的”契约”。


一、两种函数的对比:普通函数 vs 组件

从四个维度彻底区分:命名、输入、输出、调用方式

1. 命名规范

这是 React 识别身份的唯一标识。

类型命名规则示例含义
普通函数小驼峰formatDate, handleClick工具逻辑、事件处理
组件函数大驼峰UserCard, AppUI 界面构建

教练提示:React 看到大写字母开头的标签(如 <UserCard />),会当作自定义组件处理;看到小写字母(如 <div />),会当作原生 HTML 标签。

2. 输入契约

// ❌ 错误想象:组件直接接收多个参数
function UserCard(name, age) { ... }

// ✅ 正确契约:组件接收一个 props 对象
function UserCard(props) {
  return <div>{props.name}</div>
}
// 调用时:<UserCard name="小白" />
// React 帮你把参数打包成对象 { name: "小白" }

3. 输出契约

JSX 是什么?一种写在 JavaScript 里的”类 HTML 语法”,让你用写 HTML 的方式描述界面。它不是 React 发明的——JSX 是独立的语法规范,Vue、SolidJS 等框架也用 JSX。只是 React 最早采用、推广得最广。

// JSX 长这样:看起来是 HTML,其实是 JavaScript
<div className="card">
  <h1>{title}</h1>
  <p>{content}</p>
</div>

核心规则就两条:

  1. {} 插入 JS 表达式{name}{1 + 1}{items.map(...)}
  2. 编译后是函数调用:浏览器不认识 JSX,构建工具(Vite 底层的 SWC/Babel)会把它编译成 React.createElement('div', ...) ——你写 JSX,构建工具帮你翻译

文件扩展名:含 JSX 语法的文件必须用 .tsx(TypeScript)或 .jsx(JavaScript),x 就代表含 JSX。TypeScript 项目中这是强制的——tsc 不会对 .ts 文件做 JSX 解析,遇到 < 直接报错。

文件内容扩展名
TypeScript + JSX.tsx
TypeScript,无 JSX.ts
JavaScript + JSX.jsx
JavaScript,无 JSX.js

4. 调用方式

正因为是 React 调用,所以不能随便写 iffor 破坏 React 的调度机制。

组件函数在哪执行? 取决于渲染方式:

场景执行位置
客户端渲染(Vite + React)浏览器
服务端渲染(Next.js 等 SSR)服务器 + 浏览器都执行

SSR 时,组件函数先在服务器跑一遍生成 HTML 发给浏览器,浏览器拿到后再跑一遍接管交互(hydration)。所以组件函数不等于”浏览器端代码”——它是 React 调度的函数,运行环境由渲染方式决定

这也是为什么 useEffect 只在浏览器端执行——副作用(操作 DOM、发请求)在服务器上没意义,React 会跳过。


二、实战代码对比

// ==========================================
// 场景 A:普通函数
// 用途:处理逻辑、计算数据
// ==========================================
function formatPrice(price: number) {
  // 1. 小写开头
  return `¥${price.toFixed(2)}`; // 2. 返回字符串
}

// 调用方式:
const priceDisplay = formatPrice(99.9); // 3. 你手动调用
console.log(priceDisplay); // 输出: ¥99.90

// ==========================================
// 场景 B:组件函数
// 用途:渲染 UI
// ==========================================
function ProductCard({ title, price }: { title: string; price: number }) {
  // 1. 大写开头
  // 2. 接收 props 对象

  const displayPrice = formatPrice(price); // 在组件里调用普通函数

  return (
    // 3. 返回 JSX
    <div className="card">
      <h2>{title}</h2>
      <p>价格:{displayPrice}</p>
    </div>
  );
}

// 调用方式:
// 4. 像 HTML 标签一样使用,React 框架负责调用
<ProductCard title="T3 Stack 指南" price={199} />;

三、传递数据的语法:引号 vs 大括号

语法用途示例
""传递字符串<UserCard name="小白" />
{}传递非字符串(数字、变量、函数、对象)<UserCard age={20} />

四、组件的”特权”:Hooks

Hooks(如 useStateuseEffect)是 React 框架提供的 API,不是浏览器原生的。叫 “Hooks” 是因为它们”钩入”了 React 的渲染生命周期。

只有组件函数才能使用 Hooks——因为 React 内部靠调用顺序来匹配每个 Hook 对应的 state。每渲染一次,组件里的 Hook 必须按相同顺序调用相同次数。普通函数不是 React 调度的,React 没机会追踪它的 Hook 调用顺序,所以会出错。

// ❌ 错误:普通函数里使用 Hook
function myUtil() {
  const [count, setCount] = useState(0);
}

// ✅ 正确:组件里使用 Hook
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

常用 Hooks:


五、组件函数 vs 组件实例

你写了一个组件函数,它只是一个蓝图。你在 JSX 里用一次,React 就造出一个实例

function Timer() { ... }  // 蓝图,不是实例

// 用了两次,造出两个实例
<Timer />   // 实例 A,有自己的 state、ref、props、effect、context、memo、callback……
<Timer />   // 实例 B,有自己的 state、ref、props、effect、context、memo、callback……

就像类和对象的关系——写 class Person 是蓝图,new Person() 一次就是一个实例,每个实例有自己的属性,互不影响。函数组件没有 new,但 React 每次遇到 <Timer />,内部会创建一个 Fiber 节点来”记住”这个实例。

Fiber 节点是 React 内部用来记住每个组件实例的数据结构,大概长这样:

Fiber {
  type: Timer           // 哪个组件函数
  memoizedState: ...    // 这个实例的 Hooks 数据(state、ref 等,链表存储)
  return: 父Fiber       // 树形结构:指向上级
  child: 子Fiber        // 指向下级
  sibling: 兄弟Fiber    // 指向同级下一个
}

你不用记这些字段,只需要知道:Fiber 就是 React 给每个组件实例建的档案——组件实例是逻辑概念,Fiber 是这个概念在 React 源码里的具体实现。所有 Fiber 节点组成一棵树,跟组件嵌套关系一一对应,React 靠这棵树做 diff 和调度。

所以两个 <Timer /> 各跑各的定时器、各记各的秒数,就是因为它们是两个独立的实例,各有各的存储空间。不仅是 state、ref、props、effect——所有 Hooks 的数据(context、memo、callback 等)都是挂在实例上的。函数蓝图只是一段代码,实例才是真正带着数据跑的东西。

关键认知:函数是 JS 的,“组件”和”实例”是 React 的概念。function Timer() {} 本身就是一个普通 JS 函数,没有任何特殊之处。是 React 的使用方式——<Timer /> 编译成 React.createElement(Timer),React 拿到这个函数、按自己的规矩调它、给它挂 state 和 ref——让它变成了”组件”。函数还是那个函数,“组件”这个身份是 React 赋予的。


六、T3 Stack 进阶视角:类型安全

用 TypeScript 强化”契约”:

type UserCardProps = {
  name: string;
  age: number;
  isAdmin?: boolean; // 可选属性
};

const UserCard = ({ name, age }: UserCardProps) => {
  // ...
};

// TypeScript 会自动检查:
// <UserCard name="小白" age={20} /> ✅ 通过
// <UserCard name="小白" age="20" /> ❌ 报错:age 必须是数字
// <UserCard /> ❌ 报错:缺少 name 和 age

七、总结心法

原则规则
本质组件是函数,函数是组件
区分看首字母(大写是组件,小写是普通函数)
调用普通函数你调,组件 React 调
实例组件函数是蓝图,<Component /> 是实例,每个实例各有各的 state/ref/props
数据引号传字符串,大括号传万物


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
所属专题
React
第 1 / 17 篇
查看系列全部文章
  1. 01.React 核心(1):React 组件与函数的本质区别
  2. 02.React 核心(2):声明式与 React 的实现机制
  3. 03.React 核心(3):Props 完全指南
  4. 04.React 核心(4):useState 完全指南
  5. 05.React 核心(5):useEffect 完全指南
  6. 06.React 核心(6):useRef 完全指南
  7. 07.React 核心(7):Context API 与状态共享
  8. 08.React 核心(8):useCallback 与 useMemo
  9. 09.React 核心(9):Hooks 规则与闭包陷阱
  10. 10.React 核心(10):自定义 Hook 设计
  11. 11.React 核心(11):Key 与列表渲染
  12. 12.React 核心(12):表单与受控/非受控组件
  13. 13.React 核心(13):React 渲染机制深入——Fiber 与调度
  14. 14.React 核心(14):React 18 并发特性
  15. 15.React 核心(15):Suspense 机制详解
  16. 16.React 核心(16):TanStack Query 入门指南
  17. 17.React 核心(17):TanStack Query 缓存机制详解

上一篇
React 核心(2):声明式与 React 的实现机制
下一篇
突破 JVM 物理边界:高性能网络 I/O 与底层零拷贝机制