在 JSX 里搭积木:初探 React 的组件化哲学
现代前端开发早已告别了“写页面”的时代,转而进入“构建界面系统”的新纪元。React 正是这场范式迁移中的关键推手——它不提供模板语言,也不依赖指令系统,而是将 UI 视为 JavaScript 的自然延伸。通过两个简洁的示例文件,我们可以清晰地看到 React 如何以函数为砖、以状态为胶,搭建起一个声明式、可组合的用户界面世界。
JSX:不是模板,而是 UI 的声明式语法
乍看之下,JSX 极易被误认为是“在 JavaScript 里嵌套 HTML”。但实质上,它是一种用于描述 UI 结构的声明式语法扩展,其本质是 React.createElement() 的语法糖。
const element = <h2>JSX 是 React 中用于描述用户界面的语法扩展</h2>;
const equivalent = createElement('h2', null, 'JSX 是 React 中用于描述用户界面的语法扩展');
二者在运行时完全等价。JSX 的存在,并非为了炫技,而是为了让开发者能以接近 HTML 的直观方式表达组件结构,同时保留在 JavaScript 环境中进行逻辑控制的能力。
组件即函数:封装与复用的基本单元
React 将组件定义为返回 JSX 的纯函数。这种设计看似简单,却蕴含深意:
function Header() {
return <header><h1>掘金首页</h1></header>;
}
const Sidebar = () => <aside>侧边栏内容</aside>;
每个组件都是一个独立的功能单元,内聚了结构、样式(通常通过 CSS 模块引入)和交互逻辑。更重要的是,它们可以像乐高积木一样自由组合(简单模拟一下掘金的首页布局):
//根组件
function JuejinHeader(){
return(
<div>
<header>
<h1>掘金首页</h1>
</header>
</div>
)
}
const Ariticles = () => {
return(
<div>
Articles
</div>
)
}
const Checkin = () => {
return(
<div>
Checkin
</div>
)
}
const TopArticles = () => {
return(
<div>
TopArticles
</div>
)
}
function App(){
return(
<div>
{/* <h1>hello <b>React!</b></h1> */}
{/* 头部组件 */}
<JuejinHeader/>
<main>
<Ariticles/>
<aside>
<Checkin/>
<TopArticles/>
</aside>
</main>
</div>
)
}
export default App
此时,整个应用不再是扁平的 DOM 树,而是一棵语义清晰的组件树。开发者不再操作 <div> 和 <span>,而是操作 <Header />、<Sidebar /> 这样的抽象单元——这正是现代 UI 工程的核心思想。
函数为何成为组件的最佳载体?
将组件实现为函数,绝非偶然。函数天然具备:
- 输入输出机制(通过 props 接收数据,返回 JSX)
- 无副作用的纯度(理想情况下,相同输入总产生相同输出)
- 可组合性与可测试性
配合 Hooks(如 useState),函数组件还能拥有状态和生命周期能力,彻底摆脱了类组件的繁复。例如:
const [name, setName] = useState("vue");
setTimeout(() => setName("react"), 3000);
状态变更后,组件函数自动重新执行,生成新的 UI 快照。这种“状态驱动视图”的模式,让数据流变得可预测、可追踪。
React 的“规矩”:约束带来秩序
React 对组件编写设定了若干硬性规则,初看像是限制,实则是工程化的基石。
单一根节点(Single Root)
每个组件必须返回恰好一个根元素。若需包裹多个兄弟节点,可使用 Fragment(简写为 <>...</>):
return (
<>
<h1>标题</h1>
<p>内容</p>
</>
);
此举避免了无意义的 DOM 嵌套,同时强制组件保持单一职责。
组件命名首字母大写
React 通过首字母大小写区分原生标签与自定义组件:
<div>→ 原生 HTML 元素<ArticleList />→ 自定义组件
若误写为 <articleList />,React 会将其视为未知 HTML 标签,导致渲染失败。这一约定虽小,却是组件系统语义清晰的关键保障。
React 的核心语法习惯:用 JavaScript 思考 UI
React 的设计哲学可概括为:不发明新语法,而是最大化利用 JavaScript 本身的能力。以下几点尤为典型:
从一段代码看react的几个基本语法
import {useState,createElement} from 'react'
import './App.css'
function App(){
const [name,setName] = useState("vue")
const [todos,setTodos] =useState([
{id:1,title:"学习react",done:false},
{id:2,title:"学习node",done:true},
{id:3,title:"学习angular",done:false},
])
const [isLoggedIn,setIsLoggedIn] = useState(false)
const toggleLogin = () => {
setIsLoggedIn(!isLoggedIn);
};
//语法糖,主要是简化模板开发,提升代码的可读性
const elemnet =<h2>JSX 是 React 中用于描述用户界面的语法扩展</h2>
const element2 = createElement('h2',null,'JSX 是 React 中用于描述用户界面的语法扩展')
setTimeout(()=>{
setName("react")
},3000)
return(
//文档碎片节点
<>
<h1>Hello <span className="title">{name}!</span></h1>
{elemnet}
{element2}
{
todos.length>0 ?(
<ul>
{
//xml in js
todos.map((todo)=>{
return(
<li key={todo.id}>{todo.title}</li>
)
})
}
</ul>
):
(
<div>暂无待办事项</div>
)
}
{
isLoggedIn ? <div>已登录</div> : <div>未登录</div>
}
<button onClick={toggleLogin}>
{isLoggedIn ? "退出登录" : "登录"}
</button>
</>
)
}
export default App
值得注意的是,由于 JSX 写在 JavaScript 上下文中,某些关键字需做转义:
class→className
for→htmlFor
这并非设计缺陷,而是一种有意为之的约束——UI 不再是外部模板,而是程序逻辑的一部分。
效果图:
1. 条件渲染:用三元运算符或逻辑与
{isLoggedIn ? <Dashboard /> : <LoginPrompt />}
{user && <UserProfile user={user} />}
没有 v-if,没有 *ngIf,只有原生 JavaScript 表达式。逻辑越复杂,越应提前计算变量,保持 JSX 简洁。
2. 列表渲染:拥抱 .map()
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
每个列表项必须携带唯一 key,帮助 React 高效比对虚拟 DOM。使用数据 ID 而非数组索引作为 key,是避免渲染错乱的最佳实践。
3. 事件处理:传递函数引用
<button onClick={handleClick}>点击</button>
注意:此处传入的是 handleClick 函数本身,而非调用结果 handleClick()。后者会导致函数在每次渲染时立即执行,引发无限循环。
4. 注释语法特殊
JSX 中的注释需包裹在 {/* ... */} 中:
{/* 这是合法的 JSX 注释 */}
<div>Hello</div>
普通 // 注释在 JSX 块内无效,这是语法解析器的硬性要求。
5. 花括号 {}:JSX 中的“魔法门”
在 JSX 中,花括号 {} 是通往 JavaScript 世界的唯一入口。它允许你在声明式模板中嵌入任意合法的 JavaScript 表达式——变量、函数调用、对象属性、甚至三元运算,皆可通行。
<h1>Hello, {user.name}!</h1>
<p>当前共有 {items.length} 项任务</p>
<div>{formatDate(new Date())}</div>
需要注意的是:
- 只能放表达式,不能放语句 。例如
if、for、var等语句不能直接写在{}中; - 若需复杂逻辑,应提前在组件函数体中计算好,再将结果传入 JSX;
- 表达式求值结果若为
null、undefined或false,React 会自动忽略,不渲染任何内容(这常用于条件显示)。
花括号就像 JSX 的“呼吸孔”——让静态结构得以与动态逻辑自由交换气息,却又不至于破坏其声明式的整洁骨架。
6. 样式引入:从全局污染到模块化隔离
React 并未内置样式方案,而是将选择权交还给开发者。常见的 CSS 引入方式有以下几种:
(1)传统 CSS 文件导入
import './App.css';
这是最直接的方式,适用于快速原型或小型项目。但需注意:CSS 仍是全局作用域,类名冲突风险依然存在。
(2)CSS Modules(推荐用于中大型项目)
import styles from './App.module.css';
// 使用时通过对象访问
<div className={styles.container}>内容</div>
构建工具(如 Webpack)会自动为类名添加哈希后缀(如 container_3xK9s),实现样式局部作用域,彻底避免命名污染。
(3)内联样式(Inline Styles)
const headerStyle = {
color: 'teal',
fontSize: '24px',
margin: '1rem 0'
};
<h1 style={headerStyle}>标题</h1>
内联样式以 JavaScript 对象形式书写,属性名采用驼峰命名(如 fontSize 而非 font-size)。虽然灵活,但难以复用,且不支持伪类、媒体查询等高级特性,通常仅用于动态样式场景。
(4)CSS-in-JS(如 styled-components、Emotion)
这类方案将样式与组件深度绑定,实现真正的“组件即单元”。虽不在基础语法范畴,但体现了 React 社区对“关注点聚合”的极致追求。
无论采用哪种方式,React 的核心理念始终如一:样式是组件的一部分,而非外部附属品。正如结构用 JSX 描述、逻辑用函数封装,样式也应被纳入组件的自治边界之内。
与 Vue 的路径分野:两种响应式,两种世界观
Vue 与 React 都推崇组件化与响应式,但实现路径迥异:
- Vue 采用“增强 HTML”策略,通过指令(如
v-if、v-for)扩展模板能力,降低学习曲线; - React 则坚持“UI as Code”,将一切逻辑交还给 JavaScript,追求更强的表达力与一致性。
前者像一位体贴的向导,后者则更像一位严格的教练——它不替你做决定,但赋予你完整的控制权。
从 DOM 操作到组件组合:开发范式的跃迁
过去,前端开发者如同泥瓦匠,逐块砌起 <div> 与 <span>;如今,React 让我们化身建筑设计师,先规划模块(组件),再组装系统(组件树)。审查元素时,看到的不再是杂乱的标签,而是 <App><Header><Navigation>... 这样的语义结构。
这种转变,不仅是工具的升级,更是思维的进化:UI 不再是静态文档,而是一个由状态驱动、由组件构成的动态系统。
React 或许门槛略高,但它所倡导的函数式思维、声明式编程与组件化架构,正是构建复杂前端应用的坚实地基。正如一句业内调侃所言:
“在 React 里,你不是在写网页,而是在用 JavaScript 编排一场 UI 的交响乐。”