关注「索引目录」公众号,获取更多干货。
我并非任何特定框架的拥趸。对我而言,框架只是工具。就像我宁愿选择一把结实耐用的好锤子,也不愿选择一把锈迹斑斑、漏洞百出的旧锤子一样……我也更喜欢那些能帮助我构建易于维护且使用起来更舒适的软件的工具。正因如此,我喜欢保持技术更新,了解底层原理,而不是仅仅“使用大家都在用的东西”。
我每天都使用 Angular——没错,Angular 会强制你采用某种架构(尽管相信我,你仍然可以用它构建出一团乱麻……问问我是怎么知道的😬)。
但我真的非常喜欢 React。每当我做业余项目时,我都会经常用到它。我热爱 JSX,也喜欢 React 与纯 JavaScript 的接近程度,尤其是与 Angular 相比。但是……你知道那句话怎么说:能力越大,责任越大😎 如果落入不合适的人手中……这种自由可能会毁掉一个项目。
我见过不少项目,它们不仅仅是乱糟糟的。
看起来就像有人用 jQuery 写完,删掉$(document).ready一些代码,再随便加点 JSX 代码上去……然后就真的想让它运行起来🤡
这是从旧代码库盲目迁移吗?
还是开发人员内心深处从未真正放弃过 jQuery?😉
很难说。
总之,以下是一些典型的迹象,表明你的 React 代码并非真正的 React……它只是披着 JSX 外衣的 jQuery。而且,这一点显而易见。
1️⃣ 一个“包揽一切”的巨型组件
React 文件。看起来像这样.tsx。从技术上讲,它有子组件。甚至可能还有一个用于 CSS 类名的实用程序。但从情感上来说呢?
感觉就像一个老式的 index.html 文件里住着一个巨大的 script 标签😂
一大堆数据。正在获取数据。正在更新用户界面。正在管理事件。正在管理布局。哦,可能还会有一个模态框、三个下拉菜单、一个表格和一个侧边栏,为什么不呢?
🧨 示例
function App() {
const [data, setData] = useState([]);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [filter, setFilter] = useState("");
useEffect(() => {
fetch("/api/items")
.then(res => res.json())
.then(setData);
document.getElementById("loader").style.display = "none";
document.getElementById("list").style.display = "block";
const el = document.getElementById("filter");
el.addEventListener("input", e => setFilter(e.target.value));
}, []);
return (
<>
<input id="filter" />
<div id="loader">Loading...</div>
<ul id="list">
{data.filter(d => d.includes(filter)).map(d => <li>{d}</li>)}
</ul>
<button onClick={() => setSidebarOpen(!sidebarOpen)}>Toggle</button>
<div className="sidebar">{sidebarOpen && "Hello"}</div>
</>
);
}
🤔 为什么会发生这种情况?
-
有人从 jQuery 迁移过来,然后把所有东西都塞进了一个文件里。 -
“它能用,别碰它。” -
早期不做任何架构方面的决定。 -
“我们稍后会进行重构”(我们都知道故事的结局)。
✅ 应有的样子
-
按职责拆分组件(单一职责原则) -
分离: -
数据/逻辑组件 -
演示组件
-
在适当的时候将逻辑移至自定义钩子中 -
避免直接操作 DOM——让 React 来管理 UI。 -
记住:许多小组件 > 一个核心组件
2️⃣ 一个useEffect无所不能的巨头……
useEffect(() => { /* 🤹♂️ everything happens here */ }, [])
获取数据、添加事件监听器、切换类、更新 DOM、与 7 个不同的服务通信、滚动、分析、弹出通知……所有这些都集成在一个精彩的效果中。
基本上:
取下 $(document).ready,替换成useEffect,然后发货到生产环境😎
🧨 示例
useEffect(() => {
fetch("/api/stats")
.then(res => res.json())
.then(data => {
setStats(data);
document.title = "Dashboard";
document.getElementById("counter").textContent = data.users;
});
const resize = () => document.body.classList.toggle("mobile", window.innerWidth < 700);
window.addEventListener("resize", resize);
window.scrollTo(0, 0);
return () => window.removeEventListener("resize", resize);
}, []);
🤔 为什么会发生这种情况?
-
缺乏思维模型:“效果=奇迹发生的地方”。 -
从旧代码中复制粘贴。 -
“嘿,它只运行一次,是处理一切的完美场所!” -
缺乏对效果应有针对性和范围的认识。
✅ 应有的样子
-
每一种效果都应该承担一个明确的责任。 -
将巨型效应分裂 useEffect成多个聚焦效应 -
如果逻辑可以放在渲染层,就不要把它放在特效层。 -
记住:
useEffect !== lifecycle method
-
避免将所有应用程序行为都塞进一个“只运行一次”的效果中。
3️⃣useEffect那些应该直接用 JSX 实现的东西
这有点伤人😅
在 effect 中进行 DOM 操作只是为了更新文本、类、可见性或 JSX 可以自然声明的内容。
而不是:
if condition → change DOM
React 需要:
if condition → render something else
🧨 示例
useEffect(() => {
const el = document.getElementById("message");
if (error) {
el.classList.add("visible");
el.textContent = error;
} else {
el.classList.remove("visible");
}
}, [error]);
同时 React:
{error && <p className="message">{error}</p>}
🤔 为什么会发生这种情况?
-
仍然抱持着命令式思维:“用户界面是我可以改变的东西,而不是我可以描述的东西。” -
旧模式:“所有东西都是动态的?➝ 必须放在 useEffect 中!” -
旧习惯难以改变。
✅ 应有的样子
-
UI 更改应该用 JSX 以声明式的方式表达。 -
使用条件渲染显示/隐藏元素 -
从状态派生类,不要手动切换它们 -
思考: -
“状态改变 → React 重新渲染” -
不是“状态改变→我修补DOM”
4️⃣ 一个巨大的 CSS 类“开关”,而不是真正的逻辑
UI 状态存储在……不在 state 中
……不在 props 中
……而是……🥁 存储在 CSS 类名中。
应用程序逻辑变为:
-
“如果它有这个课程,那就意味着它是开放的。” -
“如果不行,那就关门了。” -
“如果它具备这个和那个条件,那么它就处于某种神奇的状态,没有人能完全理解它了。”
恭喜,你构建了一个状态机……在你的样式表中🙃
🧨 示例
useEffect(() => {
const steps = document.querySelectorAll(".step");
steps.forEach(step => {
step.addEventListener("click", () => {
steps.forEach(s => s.classList.remove("active"));
step.classList.add("active");
});
});
}, []);
🤔 为什么会发生这种情况?
-
传统思维。 -
“这在 jQuery 中是可以实现的,为什么要改呢?” -
多年来,CSS 一直被用作状态持有者——这种习惯根深蒂固。
✅ 应有的样子
-
将 UI 状态保存在 React 状态或 store 中 -
让 UI 从状态派生而来,而不是将逻辑编码到 CSS 中。 -
对于复杂流程,请考虑: useReducer-
一个合适的状态机
-
经验法则: -
CSS = 样式 -
React 状态 = 逻辑
5️⃣ 动画效果可以简化为“只需在 JS 中切换类”
需要动画吗?
在JS中添加/删除类。搞定。React
?哦,是的,技术上还在👀 只是默默观察。
没有状态。没有声明式转换。没有结构。
只是:
点击 → 添加类 → 稍后移除类 → 希望不会出问题
🧨 示例
function Notification() {
useEffect(() => {
const btn = document.getElementById("show");
const box = document.getElementById("note");
btn.onclick = () => {
box.classList.add("visible");
setTimeout(() => box.classList.remove("visible"), 2000);
};
}, []);
return (
<>
<button id="show">Show</button>
<div id="note" className="notification">Hello!</div>
</>
);
}
🤔 为什么会发生这种情况?
-
熟悉的旧模式。 -
快速修复版本已上线。 -
前端架构设计没有分配任何时间。
✅ 应有的样子
-
基础动画由 React 状态触发,而非手动 DOM 操作。 -
最简单的选择: -
JSX 中的状态 → 类切换
-
更好的选择: -
由状态驱动的 CSS 过渡 -
React Transition Group -
Framer Motion
-
尽量避免强制使用动画,而应该声明动画。
6️⃣ 将 DOM 元素保留在状态中😱
罕见……但我见过。
一旦见过,就永远不会忘记——它会在你心中留下深刻的印象😅
useState<HTMLDivElement | null>(null)
然后是这样的逻辑:
-
“如果我们有了这个元素,就直接用它来做事情。” - 而不是将意图
存储在状态中。
那不是 React,那完全是原始的 DOM 操控。
🧨 示例
const [el, setEl] = useState(null);
useEffect(() => {
setEl(document.getElementById("target"));
}, []);
function highlight() {
el.style.background = "yellow";
}
🤔 为什么会发生这种情况?
-
混淆了 refs、state 和 DOM。 -
将 React 仅仅视为渲染标记的辅助工具。 -
完全不理解 React 的作用。
✅ 应有的样子
-
如果需要访问 DOM → 使用 ref - 意图
存储在状态中,而不是原始 DOM 节点中。 -
DOM 是实现细节 -
状态描述的是你希望发生什么,而不是如何改变元素。
💬 这为什么重要
我并不是说代码必须永远完美。
有时候,快速的权宜之计是可以接受的。有时候,截止日期才是最重要的。有时候,历史遗留问题确实存在。
但是当 React 像 jQuery 和 JSX 一样使用时……
-
维护工作变成了一场噩梦 -
团队入职变得痛苦 -
虫子藏在无人知晓的黑暗角落。 -
建筑因自身重量而坍塌
React 的强大之处不在于它能更好地操作 DOM,
而在于:
✨状态 → UI
, 而不是
🔧 UI → 补丁 → 补丁 → 补丁
🤍 一点反思
React 赋予我们自由。这种自由固然美好。
但自由需要责任。没有责任,React 就会变成:
一个品牌形象更好的 jQuery
这有点可惜,因为 React可以给我们带来结构、清晰度和可预测性——如果我们善加利用的话。
如果你的项目有上述种种问题……
这没什么可耻的。
这只是意味着它背后有历史、压力、背景以及人为决策。
关注「索引目录」公众号,获取更多干货。

