在现代前端开发的浪潮中,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 负责展示文章列表;
- Checkin 和 TopArticles 负责侧边栏的功能。
这种写法的最大优势在于关注点分离(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-if 或 v-for 指令。React 开发者不需要学习额外的模板语法,只需要掌握 JavaScript。map、filter、三元运算符 (? :)、&& 逻辑短路,这些标准的 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)” 的核心公式。
- 我们声明了一个状态
name。 - 我们在 JSX 中使用
{name}进行绑定。 - 当我们调用
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.jsx 和 App.jsx,我们不难发现 React 的设计哲学:
- 组件化:将复杂的页面拆解为
Header、Articles等独立单元,像包工头分发任务一样管理 UI。 - JSX:利用 JS 的强大能力描述 UI,而不是被 HTML 模板限制。
- 单向数据流:通过 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 的使用方式
- 显示写法
import { Fragment } from 'react';
function App() {
return (
<Fragment>
<h1>Hello</h1>
<p>World</p>
</Fragment>
);
}
- 简写形式
function App() {
return (
<>
<h1>Hello</h1>
<p>World</p>
</>
);
}
Fragment 的优势
- 减少不必要的 DOM 层级 :避免为了满足 React 的单根元素要求而添加无意义的包装元素,如使用
div标签包裹 - 保持语义化 :不会破坏 HTML 的语义结构
- 性能优化 :减少 DOM 节点数量,提高渲染性能
- 样式友好 :避免因额外包装元素影响 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