像“包工头”一样思考:深入浅出 React 组件化与 JSX 本质

71 阅读7分钟

在现代前端开发的浪潮中,React 无疑是那颗最耀眼的星。对于习惯了 HTML、CSS 和 JavaScript “三驾马车”分离的传统开发者来说,初入 React 的世界可能会有一种“重塑三观”的感觉。

今天,我们将通过几个生动的代码示例(模拟一个掘金社区的页面结构),结合 React 的核心概念,探讨组件化思维JSX 的本质,看看 React 是如何让我们从“搬砖工人”摇身一变成为“软件架构师”的。

一、 从“砌砖”到“积木”:思维模式的转变

在传统的网页开发中,我们关注的是 HTML 标签的堆砌。而在 React 中,核心单位不再是标签,而是组件(Component)

“HTML 有标签,CSS 有规则,JS 有逻辑,这是建筑里的砖头和沙子…… React 一下让我们成为包工头,先分工,组件化,组件组合起来组成网页。”

1.1 一切皆组件

让我们看一眼 App2.jsx 中的代码结构。这是一个典型的 React 页面入口,它描述了一个类似于“掘金”社区的首页布局:

// App2.jsx
function App() {
  return (
    <div>
      {/* 头部组件 */}
      <JuejinHeader />
      <main>
        {/* 内容区组件 */}
        <Articles />
        <aside>
          <Checkin />
          <TopArticles />
        </aside>
      </main>
    </div>
  )
}

在这里,<JuejinHeader /><Articles /><Checkin /> 不再是原生的 HTML 元素,而是我们自定义的函数组件

  • Header 负责顶部的导航和 Logo;
  • Articles 负责展示文章列表;
  • CheckinTopArticles 负责侧边栏的功能。

这种写法的最大优势在于关注点分离(Separation of Concerns) 。在传统开发中,我们可能在一个巨大的 HTML 文件里写上千行代码;而在 React 中,我们将界面拆解为独立的、可复用的单元。

1.2 为什么组件是函数?

你可能会问,为什么定义一个组件只需要写一个函数?

function JuejinHeader() {
  return (
    <div>
      <header>
        <h1>JueJin首页</h1>
      </header>
    </div>
  )
}

“为什么组件是函数呢?”

本质上,UI 就是数据的映射。React 组件接收输入(Props),返回界面描述(JSX)。函数是完成这种“输入 -> 输出”转换最自然的数学模型。React 推崇函数式组件,正是利用了 JS 函数的轻量和灵活,让组件化开发变得像搭积木一样简单。

二、 JSX:披着 HTML 外衣的 JavaScript

很多初学者在看到 const element = <h1>Hello</h1>; 这种代码时会感到困惑:为什么 JS 里能写 HTML 标签?这不报错吗?

这就是 JSX (JavaScript XML)

2.1 JSX 的真面目

App.jsx 中,有这样一段极具启示性的代码:

// JSX 写法
const element = <h2>JSX 是 React 中用于描述用户界面的语法拓展</h2>;

// 编译后的本质(React.createElement)
const element2 = createElement(
    'h2', 
    null, 
    'JSX 是 React 中用于描述用户界面的语法拓展'
);

这段代码揭示了 JSX 的真相:JSX 只是 React.createElement 函数的语法糖

当我们在写 <div className="box">Content</div> 时,Babel 等编译器会将其转化为 React.createElement('div', {className: 'box'}, 'Content')

JSX 依旧是在 JS 中书写,这也是为什么在 React 中我们需要使用 className 代替 class(因为 class 是 JS 的保留关键字),使用 htmlFor 代替 for也是同理。因为你本质上是在写 JS 对象,而不是 HTML 字符串。

2.2 XML in JS 的威力

Vue 的模板语法(Template)是将 JS 逻辑嵌入到 HTML 结构中,而 React 的 JSX 则是将 HTML 结构完全融入 JS 逻辑中。这赋予了 React 极高的灵活性。

看看 App.jsx 是如何处理列表渲染的:

{
    todos.length > 0 ? (
        <ul>
            {
                // 利用原生 JS 的 map 方法
                todos.map((todo) => (
                    <li key={todo.id}>
                        {todo.title}
                    </li>
                ))
            }
        </ul>
    ) : (<div>暂无待办事项</div>)
}

这里没有 Vue 中的 v-ifv-for 指令。React 开发者不需要学习额外的模板语法,只需要掌握 JavaScript。mapfilter、三元运算符 (? :)、&& 逻辑短路,这些标准的 JS 语法就是 React 的“指令”。

这种 “All in JS” 的策略,让 React 拥有了图灵完备的表达能力。

三、 响应式与状态:组件的灵魂

静态的组件只是躯壳,数据(State) 才是组件的灵魂。

现代前端框架的特性:响应式(Reactive)数据绑定(Data Binding) 。在 React 中,我们通过 Hooks 来实现这一点。

3.1 useState:让组件“动”起来

App.jsx 中,我们看到了 useState 的身影:

import { useState } from 'react';

function App() {
    // 定义状态 name,初始值为 "Vue"
    const [name, setName] = useState("Vue");
    
    // 3秒后将名字修改为 "React"
    setTimeout(() => {
        setName("React");
    }, 3000)

    return (
        <h1>Hello <span className="title">{name}!</span></h1>
    )
}

这里体现了 React “UI = f(State)” 的核心公式。

  1. 我们声明了一个状态 name
  2. 我们在 JSX 中使用 {name} 进行绑定。
  3. 当我们调用 setName("React") 时,React 会感知到数据变化,自动重新执行 App 函数,计算出新的 DOM 结构,并更新到页面上。

我们不需要像 jQuery 时代那样手动去 document.querySelector 查找节点然后修改 innerHTML。我们只需要修改数据,界面就会自动响应。

3.2 交互与逻辑闭环

组件不仅展示数据,还要处理交互。

const [isLoggedIn, setIsLoggedIn] = useState(false);
const toggleLogin = () => {
    setIsLoggedIn(!isLoggedIn);
}

<button onClick={toggleLogin}>
    {isLoggedIn ? "退出登录" : "登录"}
</button>

这段代码展示了 React 处理用户输入的简洁性。我们将 JS 函数 toggleLogin 直接传递给 onClick 属性。点击按钮 -> 触发函数 -> 更新状态 (setIsLoggedIn) -> 触发重渲染 -> 按钮文字改变。

这是一个完美的逻辑闭环,数据流向清晰可控。

四、 总结:架构师的视角

回顾 App2.jsxApp.jsx,我们不难发现 React 的设计哲学:

  1. 组件化:将复杂的页面拆解为 HeaderArticles 等独立单元,像包工头分发任务一样管理 UI。
  2. JSX:利用 JS 的强大能力描述 UI,而不是被 HTML 模板限制。
  3. 单向数据流:通过 State 驱动视图更新,保证了逻辑的可预测性。

React 的学习门槛或许比直接写 HTML 高一点,因为它要求开发者具备更扎实的 JavaScript 功底。但一旦跨过这个门槛,你会发现自己不再只是一个写页面的“切图仔”,而是一个构建用户界面的“架构师”。

你现在是否对“包工头”这个比喻有了更深的理解?在 React 的世界里,你就是那个指挥千军万马(组件)构建宏伟建筑(应用)的总工程师。

五、附录:App.jsx 和 App2.jsx 源码

1、App.jsx

import { useState, createElement } from 'react';
import './App.css';

function App() {
    // 状态变量 name,初始值为 "vue"
    // setName 是更新 name 状态的函数
    const [name, setName] = useState("Vue");
    const [todos, setTodos] = useState([
        { id: 1, title: "学习 Vue", done: false },
        { id: 2, title: "学习 React", done: true },
        { id: 3, title: "学习 JSX", done: false },
    ]);
    const [isLoggedIn, setIsLoggedIn] = useState(false);
    const toggleLogin = () => {
        setIsLoggedIn(!isLoggedIn);
    }

    const element = <h2>JSX 是 React 中用于描述用户界面的语法拓展</h2>;
    const element2 = createElement('h2', null, '它看起来很像 HTML,但实际上是 JavaScript 的一种扩展语法');

    setTimeout(() => {
        setName("React");
    }, 3000)
    return (
        // 根元素只能有一个, 所以这里用了 Fragment 来包裹, 文档碎片节点
        <>
            {element}
            {element2}
            <h1>Hello <span className="title">{name}!</span></h1>
            {
                todos.length > 0 ? (
                    <ul>
                        { // 原生 JS, React 能不用新语法就不用
                            // xml in js
                            todos.map((todo) => (
                                <li key={todo.id}>
                                    {todo.title}
                                </li>
                            ))
                        }
                    </ul>
                ) : (<div>暂无待办事项</div>)
            }
            {isLoggedIn ? <div>已登录</div> : <div>未登录</div>}
            <button onClick={toggleLogin}>
                {isLoggedIn ? "退出登录" : "登录"}
            </button>
        </>
    )
}

export default App

关于 App.jsx代码中第26行和第48行出现的<></>

什么是 Fragment?

Fragment 是 React 提供的一个特殊组件,它允许您将多个子元素分组,而无需向 DOM 添加额外的节点。简单来说,Fragment 就是一个"文档碎片",它可以包装多个元素但不会在最终渲染的 HTML 中留下任何痕迹。

为什么需要 Fragment?

在 React 中,每个组件必须返回一个单一的根元素。这意味着如果您想返回相邻的多个元素,您必须将它们包装在一个共同的父元素中。

Fragment 的使用方式

  1. 显示写法
import { Fragment } from 'react';

function App() {
  return (
    <Fragment>
      <h1>Hello</h1>
      <p>World</p>
    </Fragment>
  );
}
  1. 简写形式
function App() {
  return (
    <>
      <h1>Hello</h1>
      <p>World</p>
    </>
  );
}

Fragment 的优势

  1. 减少不必要的 DOM 层级 :避免为了满足 React 的单根元素要求而添加无意义的包装元素,如使用div标签包裹
  2. 保持语义化 :不会破坏 HTML 的语义结构
  3. 性能优化 :减少 DOM 节点数量,提高渲染性能
  4. 样式友好 :避免因额外包装元素影响 CSS 选择器和布局

2、App2.jsx

组件函数可以用函数式写法也可以用函数表达式的写法,因此下面的组件代码出现了两种写法

function JuejinHeader() {
  return (
    <div>
      <header>
        <h1>JueJin首页</h1>
      </header>
    </div>
  )
}

const Articles = () => {
  return (
    <div>
      Articles
    </div>
  )
}

const Checkin = () => {
  return (
    <div>
      Checkin
    </div>
  )
}

const TopArticles = () => {
  return (
    <div>
      TopArticles
    </div>
  )
}

function App() {
  return (
    <div>
      <JuejinHeader />
      <main>
        <Articles />
        <aside>
          <Checkin />
          <TopArticles />
        </aside>
      </main>
    </div>
  )
}

export default App