R

React 正在自我毁灭

2025-06-04

React 正在自我毁灭

几篇帖子之前,一位深思熟虑的评论者说,他们想了解“为什么 React 对你如此有吸引力”。我试图在那篇文章中概述其中的一些原因(因为 Redux 与我认为 React 的很多优点相悖)。但我并没有真正解释 React 的核心是如何如此优雅的。我也没有恰当地强调当今的许多实践是如何慢慢侵蚀这种优雅的。

(之前的帖子标题为《Redux 的分裂效应》,可以在这里找到:https://dev.to/bytebodger/the-splintering-effects-of-redux-3b4j

“控制点”与“关注点分离”

当我们几乎所有的企业应用程序都通过服务器端处理交付时,MVC 模式占据了主导地位。MVC 模式非常有用,因为它让我们避免盲目地将所有东西塞进一个类/页面/模块/函数中。它让我们更加谨慎地将数据(模型)、显示(视图)和逻辑(控制器)分离。

如果说这种模式有什么“问题”的话,那就是随着我们的应用大部分甚至全部被推入 UI 层,它开始变得……“模糊”。仍然有一些开发者试图坚持这样的理念:所有数据调用都应该与所有显示分离,而显示又应该与所有逻辑分离。但这种模式在单页应用中并没有提供太多价值。

当前一代“富”互联网应用让这些区分变得颇具挑战性(即便不是完全错误)。这听起来像是异端邪说吗?如果是这样,那么请思考一下:浏览器内置的实时处理能力越多,浏览器就越能有效地成为真正的控制台

您是否曾经构建过真正的控制台应用程序?(如果没有也没关系。但是如果您有的话,对这个主题很有用。)虽然今天看起来可能有些过时,但是如果您曾经构建过一个小型的 Visual Basic 应用程序,该应用程序旨在直接在操作系统中运行,您可能会开始感受到我的意思。

在控制台应用中,你通常会有各种可放置在屏幕上的组件。大多数组件都具有一组共同的功能:

  1. 控制组件的属性。通常,这些属性定义组件的初始外观/行为。

  2. 保存组件持续信息的内部存储器。这些信息可能包括:组件的位置、当前显示特性、相关组件的信息等。

  3. 预先存在或程序员定义的操作。这些事件通常由用户与该组件的交互触发。

  4. 该组件与其他组件“对话”或与其他数据存储交互的接口。

  5. 有些组件是独立的,但很多都是容器组件,可以容纳一个或多个子组件。

请注意,此组件模型中没有任何内容试图满足 MVC 模式。在严格的 MVC 方法下,组件自身的内部内存将在其他位置处理——在模型中。任何通过其操作触发的逻辑都将在其他位置处理——在控制器中。甚至对组件显示功能的任何调整也将在其他位置处理——在视图中。

那么,控制台应用程序组件在某种程度上算不算“糟糕”的编程?毕竟,我们这里只有一个“东西”——一个组件——它将逻辑、数据和显示都打包在一个包里。所以这肯定有问题……对吧

嗯...不。

你看,我们在这里讨论的控制台组件可以合理地处理逻辑数据和显示,所有这些都被包装到同一个“东西”中,因为我们只赋予该组件对那些应该自然地在其控制范围内的东西的控制权

换句话说,控制台组件可以(并且应该)处理属于该组件的数据(模型)。它可以(并且应该)处理该组件显示(视图)。它可以(并且应该)处理逻辑(控制器)以处理从该组件触发的操作

不同类型的控制台

随着每次浏览器更新,它们越来越接近真正的控制台。如果你是一名 React 开发者,这些说法可能听起来很熟悉。

React 包含组件。这些组件(可以)拥有自己的内部状态。每个组件都有一个render()函数来处理其自身的显示(null如果没有要渲染的显示,该函数可以返回)。并且它们可以拥有任意数量的关联函数,用于处理与其自身操作相关的逻辑。

这一切都可以通过最基本的例子来证明:

import React from 'react';

export default class Counter extends React.Component {
   state = {counter:0};

   decrement = () => {
      this.saveCounter(this.state.counter - 1);
      this.setState(prevState => {counter:prevState.counter - 1});
   };

   increment = () => {
      this.saveCounter(this.state.counter + 1);
      this.setState(prevState => {counter:prevState.counter + 1});
   };

   render = () => {
      return (
         <>
            <div>Counter = {this.state.counter}</div>
            <button onClick={this.increment}>Increment</button><br/>
            <button onClick={this.decrement}>Decrement</button><br/>
            <button onClick={this.reset}>Reset</button><br/>
         </>
      );
   };

   reset = () => {
      this.saveCounter(0);
      this.setState({counter:0});
   );

   saveCounter = (counter = 0) => {
      fetch(`https://127.0.0.1/saveCounter?counter=${counter}`);
   };
}
Enter fullscreen mode Exit fullscreen mode

在这个场景中,我把整个<Counter>组件本质上看作一个“东西”。如果你愿意,可以称之为一个“逻辑单元”。所以,尽管这个小例子包含很多内容,但它们都属于同一个逻辑单元

组件<Counter>有自己的内存(状态)。但这实际上是合理的,因为它负责的唯一内存与这个逻辑单元直接相关。

它有自己的布局(渲染)。但这完全合理,因为它只渲染与自身直接相关的项目

它有动作,以及处理这些动作所需的逻辑。但这完全说得通,因为这些动作都与自身直接相关。

最后,我们甚至有了数据层的初始阶段,如内部所示fetch()saveCounter()但这在这里很有意义,因为它保存的数据与自身特定相关。

换句话说,即使这个组件负责渲染、处理内部数据、外部数据以及与操作相关的逻辑,这些都合情合理因为所有这些都属于这个组件的控制范围

说实话,我的确看到了其中的妙处。如果我想知道某个组件到底在干什么,我会直接查看组件的代码。我知道……这概念有点儿激进,对吧?而且这也不是我自己瞎编的。翻遍 React 的核心文档,你会发现有很多类似的示例。

但像这样的代码在“野外”越来越少见了。这种模式的美妙之处正在瓦解——因为 React 正在自我毁灭。

这就是为什么我们不能拥有美好的事物

除了博客和教程网站之外,你很少在“真实”应用中看到类似上述代码。我的意思并非仅仅因为上面的例子很小/简单。而是因为 React 开发者一直在妖魔化这个例子中展示的许多简单概念。他们不断地在这个基础框架上挑刺,直到结果变得面目全非。

关注点分离

MVC 或许已经不是什么新鲜事了,但它仍然在许多人心中挥之不去。我收到过其他专业 React 开发者的反馈,他们说像上面这样的示例违反了关注点分离原则。当然,出于我上面列出的所有原因,我认为这简直荒谬至极。但尽管如此,许多 React 开发者似乎仍然对在任何组件中添加过多的“逻辑”心存疑虑。

我之前工作的地方,他们实际上为每个组件都创建了两个组件。第一个组件包含…… 。第二个组件包含该组件中使用的所有函数。他们将这个同级组件称为调度器。然后,他们将调度器中的所有函数绑定到第一个组件。他们竟然认为这是促进关注点分离的绝妙方法。我当时觉得这简直是愚蠢至极。render()

你越是把这些功能放到遥远的文件/目录中,你的应用就越难用,故障排除也就越困难。

我们如今构建应用程序的方式就像造一辆汽车,决定发动机应该在芝加哥,车轮和传动轴应该在亚特兰大,油箱应该在西雅图,而驾驶室应该在达拉斯。然后我们庆幸自己实现了关注点分离

问题之所以出现,是因为我们所有人都对过去不得不维护的应用程序有过噩梦。那些可怕的“车辆”里,包含发动机、燃煤发电厂、留声机、烤箱和三台坏掉的模拟电视机——所有这些都挤在一个文件/类/函数/组件里。这种经历给我们带来了巨大的心理创伤,以至于现在我们试图制造新的汽车,把所有不同的部件都扔到很远的地方。但我们很少停下来思考:“等一下,哪些部件仍然应该放在一起,放在一个地方,彼此靠近?”

对“纯洁”的痴迷

如今的 React/JavaScript 开发者们痴迷于纯粹的概念。纯粹的组件。纯粹的函数。纯粹的教条。这些开发者会很乐意喝下一品脱漂白剂——只要你向他们保证那是绝对纯净的漂白剂。

听着,我明白了。尽可能地将你的应用分解成尽可能多的“纯粹”组件/函数是有用的。这种纯粹性会让测试更容易,错误也更少。而上面的例子绝对不是“纯粹”的。

但是,你无法构建比博客演示更大的东西,最终必然会创建一些“不纯”的组件/函数。你的应用需要某种状态、外部内存和副作用。它需要与某种数据存储进行交互。而做这些事,必然会违背“纯洁圣经”的原则。

状态管理噩梦

开发者追求更“纯粹”的一种方式是,将一些庞大、笨重的状态管理机制塞进他们的应用中,然后让它处理所有那些令人讨厌、肮脏、不纯粹的状态/数据管理工作。所以他们会采用像上面这样的组件,当他们完成它之后,它基本上只剩下函数了render()。然后他们会费尽心思地自我吹嘘,因为重构后的组件如此“纯粹”。但这并不是纯粹,而是模糊

当然,我们可以在ReducerAction订阅器以及各种其他状态管理结构中处理这些极其邪恶的逻辑。这样,当我们打开这个组件的代码文件时,就会对它的“纯粹性”感到自满。但是……这个组件就没有任何意义了。

加上状态管理,你打开这个文件后会很难弄清楚计数器是如何设置的,或者它在哪里设置的。你不得不通过根本不在该文件附近的目录/文件来追踪这个逻辑。不知何故,React 开发者竟然认为这……是一件好事???

Klass R Stoopid

如今,太多 React 开发者每天早上醒来,都会把一头肥牛犊和他们的第一个孩子献祭在函数的祭坛上。他们被 React 光明会洗脑,认为任何包含关键字的代码都是邪恶和愚蠢的。而任何class包含函数的代码都是神圣和正义的。

他们很少能用任何经验主义的理由来解释为什么这些恶魔般的课程如此“糟糕”。他们只是皱着眉头,挖着鼻子,嘟囔着什么“课程真糟糕。你真是个笨蛋。”

我不是不理解那些class讨厌它的人。这个词太复杂了。除了最顶尖的程序员,其他人都很难理解。它到处都是“面向对象编程”(OOP-shtank)的影子。你根本无法忍受代码class里居然有关键字!这太不公平了!!你完全有权利,只要看到那个吓人、讨厌、可怕的class关键字,就蜷缩成胎儿的姿势。

这并不是函数的谩骂。函数很美,函数很棒。但在上面的例子中,所有显示的内容都属于一个逻辑单元。我们可以创建一个单独的counter.js文件,包含本页定义的所有函数,但不要放在类之外,但这只会混淆这个组件的初衷

很多讨厌类、崇尚函数的人似乎忽略了一点:在这种情况下,所有应该绑定到组件的数据/显示/逻辑都应该class有一个逻辑命名空间。没错……你可以把它分解成一系列松散连接的函数——但这除了安抚函数之神之外,没有任何逻辑意义。<Counter>

(如果你想了解我对你的仇恨的极度愚蠢的全面分析class,请查看这篇文章:https://dev.to/bytebodger/the-class-boogeyman-in-javascript-2949

钩子之外的任何东西都是愚蠢的

我不会太详细地讨论这一点,因为它有点像之前关于类与函数之争的延伸。但如今,即使你热爱函数,即使你公开谴责类,那……对精英们来说也不够好。如果你没有花费你的夜晚/周末/假期去弄清楚如何将每一段代码重构为Hook,那么你就只是一个假装“真正”程序员的脚本小子。

在我看来,Hooks 的拥趸简直就是邪教。我已经见过太多这样的例子了——无论是在互联网上,还是在现实生活中——有人拿来一个基于类的组件,本来它被认为是不好/错误的/邪恶的,然后他们把它重构成一个 Hook,它的代码行数和 Hook 一样多,甚至更多,然后他们就沾沾自喜,就好像他们做了一件特别的事情,应该得到一块饼干、一个笑脸、一碗冰淇淋,上面还撒了额外的糖屑。

失去焦点

在“默认”的 React 框架中, 确实很美setState()setState()仅设计用于调用它的组件。换句话说,setState()被明确限制在该组件的控制点。当然,你可以将状态变量传递给后代。你甚至可以传递一个函数,允许后代调用该状态变量的更改。但更新该状态变量的实际工作只能在其所在的组件内部完成。

这很关键,因为状态管理工具把这个概念抛到了九霄云外。一旦你抛弃了这个概念,你就会开始实现一大堆笨重的结构(比如reduceractions),试图把这个妖怪重新塞回瓶子里。

但是,如果你将状态保存在它“应该”的位置——也就是应该控制它的组件内部,那么你就无需费尽周折。这样,你就可以把所有状态变量的更新都保存在一个逻辑上合适的位置。

Redux 等工具的所有混淆开销都是因为它们试图重新夺回React 默认模型中已经存在的状态更新控制权。

结论

不管这听起来可能像什么,但事实上,我并不太在意你在项目中是否使用 Redux(或其他状态管理工具)。我不在乎你是否想把所有这些功能拆分到各自分散的目录中。我不在乎你是否因为我(继续)犯下使用evil class关键字的罪过而认为我是个白痴。

但是,席卷 React 社区的诸多潮流(它们确实潮流)确实对这个原本非常漂亮的框架造成了切实的损害。迟早会有人推出 Hooks 的替代品,到时候他们会说你使用这些过时的框架简直是白痴。(尽管他们无法提供任何实证证据来支持他们的观点。)

React最初令人惊叹的特性,如今在“真正的” React 应用中已变得稀缺。React Illuminati 花费了大量时间试图修复/替换 React 的原始功能(这些功能从一开始就没有出现问题),以至于现在我们的 React 应用/组件比那些意大利面条化的 jQuery 应用更难排查问题。

你很少能直接打开组件的代码,看看它在做什么。精英们把所有逻辑都扔进了应用程序的黑暗角落。

我并不是说每个 React 组件都必须/应该看起来像上面那个。但是,我们越偏离这个模型,就越会削弱 React 最初优秀的许多特质。

文章来源:https://dev.to/bytebodger/react-is-eating-itself-fga
PREV
拉取请求的争议艺术
NEXT
使用 Object.freeze() 的 JavaScript 常量