React就是加了包装的document.createElement(),我来给你证明这一点
经过15年的 web 开发,我有了一个令人震惊的觉悟:现代 Web 开发界的宠儿 React,只不过是被精明的市场包装和日益复杂的抽象概念所包裹起来的 document.createElement()
。我将向大家证明这一点。
我们先来看一个简单的例子。这里演示如何用纯JavaScript创建一个标题(heading):
const heading = document.createElement('h1')
heading.textContent = '大家好'
heading.className = '问候语'
document.body.appendChild(heading)
这才是React的现代做法:
const Heading = ({ 文本, className }) => {
return <h1 className={className}>{文本}</h1>
}
// 示例:
<Heading text="Hello World" className="greeting" />
但是等一下!你可能会说:“React 提供了可复用的组件!”没错,这是它的原生版本:
function createHeading(text, className) {
const heading = document.createElement('h1')
heading.textContent = text
heading.className = className
return heading
}
// 用法示例:
document.body.appendChild(createHeading('Hello World', 'greeting'))
抽象的兔子坑
但是情况变得更好了。我们来看一个最近在一个“现代”的代码库中遇到的真实世界的React 组件:
<TypographyProvider theme={myTheme}>
<TextContainer variant="primary">
<Typography
component="h1"
variant="heading"
size="lg"
color="primary"
fontWeight="bold"
className={styles.customHeading}
>
你好,世界
</Typography>
</TextContainer>
</TypographyProvider>
这一切……只是为了渲染一个 <h1>
标签。这根本不是组件复用;纯粹为了抽象。我们从简单的 DOM 操作变成了套娃式的组件结构,每一个组件都增加了自己的复杂性层次、打包大小以及潜在的故障点或问题。
啊,没错,就是React的瑰宝——虚拟DOM。它被宣传为一种革命性的性能改进,但让我们实话实说:这个问题是我们自己制造出来的,因为我们使组件模型过于复杂了。我们实际上是说:“有了我们自己解决方案导致的问题的解决办法,而这些问题大多数都是我们自己想出来的。”
TypeScript '负担'为了更清楚地说明这一点,让我们来看看当我们引入TypeScript时会发生什么的情况。
interface TypographyProps extends HTMLAttributes<HTMLElement> {
component?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span';
variant?: 'heading' | 'body' | 'caption';
size?: 'sm' | 'md' | 'lg' | 'xl';
color?: 'primary' | 'secondary' | 'error';
fontWeight?: 'normal' | 'bold' | 'lighter';
className?: string;
children: ReactNode;
}
const Typography: FC<TypographyProps> = ({
component = 'p',
variant = 'body',
size = 'md',
color = 'primary',
fontWeight = 'normal',
className,
children,
...rest
}) => {
const Component = component;
return (
<Component
className={clsx(
styles[variant],
styles[size],
styles[color],
styles[fontWeight],
className
)}
{...rest}
>
{children}
</Component>
);
};
这都是为了展示一个带有文本的HTML元素。
不顺风税趁着我们现在在这里,我们来谈谈 Tailwind — 因为写 CSS 类好像太难了,所以现在我们写内联样式,假装它们就是 CSS 类。
<div className="flex flex-col items-center justify-between p-4 m-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded transition-colors duration-200">
你好,世界
</div>
还记得我们曾经批评过内联样式吗?现在我们只是把它们变长,并称其为样式框架,而不是我们没有改进它们。
.button {
/* 显示为弹性盒子 */
display: flex;
/* 设置弹性盒子的方向为竖直排列 */
flex-direction: column;
/* 使项目在主轴方向上居中对齐 */
align-items: center;
/* 在交叉轴方向上将项目间隔均匀分布 */
justify-content: space-between;
/* 设置填充区域 */
padding: 1rem;
/* 设置外边距 */
margin: 0.5rem;
/* 设置背景颜色为蓝色 */
background: blue;
/* 设置字体颜色为白色 */
color: white;
/* 设置字体加粗 */
font-weight: bold;
/* 设置圆角半径 */
border-radius: 0.25rem;
/* 设置背景颜色过渡时间为0.2秒 */
transition: background-color 0.2s;
}
/* 在鼠标悬停时 */
.button:hover {
/* 设置背景颜色为深蓝色 */
background: darkblue;
}
我们遇到一个单行的 CSS 错误,这个问题让文本超出自1995年以来制造的每一台显示器的右边界。但至少我们不用再写 CSS 了!我们只是在写……更长的内联 CSS……打更多的字。
真正的成本这种复杂性带来了实际的成本:
- 更复杂的构建流程,
- 更高的理解成本,
- 新开发人员上手更难,
- 层层嵌套的依赖关系,
- 更大的包体积,
- 更多的故障点。
真正的问题是:为什么我们要接受这一点?答案在于以下几种因素的完美风暴:
- framework marketing
- 简历驱动开发
- 编筐文化编程
- 错误地认为更多的抽象总是意味着更好的代码
这里是我大胆的建议:也许,仅仅是也许,我们不需要用三层组件包裹每一个HTML元素。也许document.createElement()
并不是我们应避开的东西。也许我们不需要假装DOM不存在。
我已经能想象到一些回应了:
- “但是状态管理呢?”
- “组件生命周期呢?”
- “我该怎么处理复杂的UI更新?”
对此,我的回应是:我们是怎么在没有 React 的情况下,几十年来搭建出这么多复杂的 web 应用程序的?难道这些应用都是我们凭空想象出来的吗?
内容生产工厂依旧忙碌谈到用抽象概念掩盖现实,我们来聊聊你大概在哪里看到这篇文章。Medium 已经变成了一片荒漠,充斥着在不同标题下重新发布的 AI 生成的文章,每一则都承诺揭示“React 性能的 10 个 SECRETS”或“每个高级开发人员必须知道的 5 种 PATTERNS”。
这是一样的浅薄内容,通过不同的标题进行重复利用,每个标题都为了吸引更多的点击而优化,但却没有什么新内容。就像我们的排版组件通过层层抽象包裹基本的HTML一样,这些文章也将同样的陈旧的想法包裹在层层点击诱饵的标题中。
用AI来写这篇批评真讽刺,不过至少我说出了实话,你们的保费可是支付了这笔费用的。
最后,我们来总结一下React 并不是那么糟糕。它解决了真实的问题,并引入了一些真正有用的模式。但在某个阶段,我们偏离了正轨。我们将简单的 DOM 操作当作是极为不好的事情,导致了越来越复杂和繁冗的抽象,却解决了一些想象中的问题,反而带来了新的实际问题。
下次当你准备使用一个组件抽象的时候,问自己:我真的在让这变得更好,还是我只是在给 document.createElement()
打了个漂亮的蝴蝶结呢?
[作者注:我已经因为愤怒的回复而焦头烂额了,但我的保险代理人说我还需要更多的证据来证明这是有预谋的。请尽量广泛地分享这篇文章。]
— -
我是一位高级资深架构师,擅长以各种方式在屏幕上呈现DOM元素,已有15年的经验。
共同学习,写下你的评论
评论加载中...
作者其他优质文章