浅色圆锥曲线爱好者PostsNotesAbout

为什么我不看好现在的React

Disclaimer: 个人观点, 基于个人使用感想不一定反映实际情况。

我最初接触到vue是在 2017 年, 然后在2018年开始工作中开始同时使用 vuereact. 也算是经历了 Class Component -> hooks, vue2 -> vue3的变化。本文结合这些经历谈一谈为什么我现在觉得react hooks其实不是一个好API, reactvue的现状, 以及其他react ecosystem的问题

hooks

现在新写react项目的时候, hooks是唯一选择, 相比以往的Class Component时代有很大的改进, 然而在经过几年使用之后, 可以切身体会到hooks其实并不是那么便于使用, 很容易出现各种问题, 也相对verbose需要boilerplate.

我们仔细看看一个使用hooks例子.

tsx
import React, { useState, useEffect } from 'react';
function App() {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const res = await fetch('https://example.com');
const data = await res.json();
setData(data);
};
fetchData();
}, []);
return (
// ...
);
}

在这里看起来干净简单的Component, 其实在实际使用上存在有很多潜在的问题.

(在重重限制下)重新发明轮子

useState等的hooks, 需要和正常写代码很不一样的心智模型, 也有很多的限制 hooks-rules.

  • Only Call Hooks at the Top Level
    • Don’t call Hooks inside loops, conditions, or nested functions.
  • Only Call Hooks from React Functions

其中明确说明在循环, 条件和嵌套函数中不能使用hooks. 这些问题习惯之后相对不会遇到这些问题, 但是这些是由于hooks实现而非js自身的限制, 对于初学者并不友好. 而且这些条件是隐形的(并且是灾难性的!), 如果不配合使用eslint有时很难注意到.

长期使用hooks下来, 很多时候会感到 const [xxx, setXxx] = useXxx(init);其实很罗嗦. 因为本质上的行为和 let x = init; x = 1没有很大区别, 但是敲大量需要额外的代码. (我不觉得snippet是一个好的解决手段)

除此之外, setXxx会造成整个组件的再渲染, react官方的宣称除非有实际性能影响否则不会造成问题, 然而实际上出现性能问题时,

  1. 需要进行实际benchmark(并不是那么容易做)
  2. 需要做手动优化(useCallback, useMemo, ...)
  3. 手动优化可能造成state和UI的不一致
  4. 如果使用 useRef, 那么需要回到起点由编写者自己管理state变化, 和使用react的目的背道而驰.

在组件中使用react本身提供的hooks的时候, 大部分比不上mutation直接. 比如svelte, 或者使用 script setup RFC 的vue3, 需要考虑的东西相当少的多, 语法也接近js本身.(it just works™)

svelte

html
<script>
let count = 0;
</script>
<p>{count}</p>
<button type="number" on:click={() => count +=1}> + </button>
<button type="number" on:click={() => count -=1}> - </button>

vue

vue
<script lang="ts" setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<p>{{ count }}</p>
<button @click="count -= 1"> + </button>
<button @click="count += 1"> - </button>
</template>

useEffect类似手动的 watch(...), 但是有着更多使用上的痛点, 首当其冲就是异步处理, 需要多一层warp, (还不包括错误处理, 请求的状态管理(cancel)等). 其次需要手动写所有的dependents, (并不是reactive by themselves)

此外, hooks造成了运行环境的分割, hooks的逻辑无法在react外使用, 外部的第三方库也为了支持react也需要追加处理. 最简单的例子就是RxJS,

useGlobalState?

如果说了那么的多react本身的问题, 那么为什么不使用库避免呢? 然而选择react的库又是另一个问题, 最典型的例子是react至今仍然没有一个官方的全局状态管理方案. useContext 显然可以解决一部分问题, 然而并不能解决全部, 不然也不会有那么多不同状态管理库(redux, recoil, zustand, ...)和状态管理库的库(reselect, redux-toolkit, redux-thunk, ...)存在了.

在其中取舍并不是一件简单的事情. 在其中最有名, 生态最完善的redux在去年的 state-of-js调查中呈现了明显的下降趋势.

state of redux 2020

同样, 尚未正式发布的Concurrent Mode也(可能)引起许多问题. useMutableSource的rfc解决了一部分breakage, 但是实际上使对应的情况并没得到Concurrent Mode的恩惠.

JSX与AOT优化

vue3/vite发布之前,选择react的很大一部分原因是由于优秀的typescript支持和贴近js的语法. 然而由于react和jsx的限制, 运行时比起vue, svelte等拥有自己AOT(ahead of time)编译优化能力的框架会多出很多runtime计算(svelte甚至不需要VDOM). 其次由于react的渲染时会进行props的full diff, 同样会造成速度的下降, 出现在vue, svelte中不存在的性能问题.

这在某种程度上可以被server components所解决, 但是server-components目前看来会需要特定的服务器(BFF / middleware), 也就是nodejs限定, 这或许造成另外的瓶颈.

上一篇文章中也有提到在使用Volar(johnsoncodehk/volar)之后, 实际的typescript支持并不比react有任何差距.

现在react的主流环境还是以webpack为主, 相比esbuild基础的vite编译速度和开发体验不是一个数量级别. 最近个人的开发的项目也几乎只使用了vite+vue3. 当然vite也支持react, 虽然并不能看到react社区有明显的动作.

说到优秀的typescript支持, react自身还有一个巨大的问题 -> written in flow, react自身也有遇到flow起因的问题. 显然flow相对typescript并不是那么成功, 那么react会使用flow到什么时候呢...?

flow fix me

Framework as a business

最后有关react的问题是两大框架next.jsgatsby的商业化以及对开源的态度转变.

最先显现出这一点的是gatsbyIncremental Builds, 这一功能最初在2020年4月的官方blog作为Gatsby Cloud的专享功能被介绍. 长期以来gatsby的编译速度一直是很大问题, 一些相对大型网站甚至需要小时单位的编译时间. 然而部分解决这个问题的Incremental Builds, 直到一年后的2021年4月才被发布.

同样next.js对自定义webpack设定的艰难程度非常有名. 许多功能 serverless functionsAutomatic Static Optimization等不被Custom Server所支持, 在vercel之外想要自己部署SSG也并不是那么简单.

在各方面可以感受到next.jsgatsby的核心团队的开发方向并不是那么纯粹(甚至可能比不上facebook的react), 顾及自己的公司利益也是无可厚非. 但是作为一般开发者真的有向一家公司无偿提交issue/debug/发pr的愿望吗? 然而相对vue等OSS来说, 有着更好的资金来源是无法否定的优势条件.

其他

react优势

react由于react reconciler的存在和相对纯粹的组件模型, 渲染对象不单纯是DOM, 可以是任意的对象, 这也早就就基于JSX的意想不到的功能实现. 其中包括

要想和这些库达到同一水平不是很简单.

Remix

Remix是另一个口碑很好?的react框架, 许多用过多人宣称解决了现在react的很多问题, 然而最低需要$250/yr的付费许可, 因此本文不与讨论.

Preact

我其实一直对preact非常的看好, 可惜因为这个那个没有长期使用的经验. 然而react以及next.js的作者对preact持有不同的意见.有关的议论可见下面的推和原来的thread.

Wrap it up

现在react的问题来源非常的复杂. Hooks/JSX等的设计问题, 既有的负面财产(flow/webpack), 开发体制(OSS or business)等等. 相比之下我认为现状vuesvelte处于优势(svelte kit基于vite!), 作为前端开发着也相当写起来fun&productive.

React的这些问题在我看来在短期内难以被得到解决, 可能将来一段时间vite+vue3仍然会是我的前端go-to framework.

© 2023