AICon日程100%就绪,9折倒计时最后一周 了解详情
写点什么

Solid.js 就是我理想中的 React

  • 2022-04-12
  • 本文字数:3308 字

    阅读完需:约 11 分钟

Solid.js 就是我理想中的 React

我大约在三年前开始在工作中使用 React。巧合的是,当时正好是 React Hooks 出来的时候。我当时的项目代码库有很多类组件,总让我觉得很笨重。

 

我们来看看下面的例子:一个每秒递增一次的计数器。


class Counter extends React.Component {  constructor() {    super();    this.state = { count: 0 };    this.increment = this.increment.bind(this);  }  increment() {    this.setState({ count: this.state.count + 1 });  }  componentDidMount() {    setInterval(() => {      this.increment();    }, 1000);  }  render() {    return <div>The count is: {this.state.count}</div>;  }}
复制代码


对于一个自动递增的计数器来说要写这么多代码可不算少。更多的模板和仪式意味着出错的可能性更大,开发体验也更差。

Hooks 很漂亮,但是容易出错


当 hooks 出现的时候我非常兴奋。我的计数器可以简化为以下写法:


function Counter() {  const [count, setCount] = useState(0);  useEffect(() => {    setInterval(() => {      setCount(count + 1);    }, 1000);  }, []);  return <div>The count is: {count}</div>;}
复制代码


等等,这其实是不对的。我们的 useEffect hook 在 count 周围有一个陈旧闭包,因为我们没有把 count 包含在 useEffect 依赖数组中。从依赖数组中省略变量是 React hooks 的一个常见错误,如果你忘记了,有一些 linting 规则会警告你的。

 

我稍后会回到这个问题上。现在,我们把缺少的 count 变量添加到依赖数组中:


function Counter() {  const [count, setCount] = useState(0);  useEffect(() => {    setInterval(() => {      setCount(count + 1);    }, 1000);  }, [count]);  return <div>The count is: {count}</div>;}
复制代码


但现在我们遇到了另一个问题,看看应用程序的运行效果:



精通 React 的人们可能知道发生了什么事情,因为你每天都在与这种问题作斗争:我们创建了太多的间隔(每次重新运行效果时都会创建一个新间隔,也就是每次我们增加 count 时间隔都会增加)。可以通过几种方式来解决这个问题:

 

  • 从清除间隔的 useEffect hook 返回一个清理函数

  • 使用 setTimeout 代替 setInterval(还是要使用清理函数)

  • 使用 setCount 的函数形式来避免直接引用当前值

 

事实上哪种办法都行得通。我们在这里实现最后一个选项:


function Counter() {  const [count, setCount] = useState(0);  useEffect(() => {    setInterval(() => {      setCount((count) => count + 1);    }, 1000);  }, []);  return <div>The count is: {count}</div>;}
复制代码


我们的计数器修好了!由于依赖数组中没有任何内容,因此我们只创建了一个间隔。由于我们为计数设置器使用了回调函数,因此永远不会在 count 变量上有陈旧闭包。

 

这是一个人为做出来的例子,但除非你已经使用 React 一段时间,否则它仍然很令人困惑。我们中有许多人每天都会遇到更复杂的情况,即使是最有经验的 React 开发人员也会为之头痛不已。

假的响应性


我思考了很多关于 hooks 的事情,想知道为什么它们感觉不太对劲。结果我通过探索 Solid.js 找到了答案。

 

React hooks 的问题在于 React 并不是真正的响应式设计。如果 linter 知道一个效果(或回调或 memo)hook 何时缺少依赖项,那么为什么框架不能自动检测依赖项并对这些更改做出响应呢?

深入研究 Solid.js


关于 Solid,首先要注意的是它没有尝试重新发明轮子:它看起来很像 React,因为 React 有一些显眼的模式:单向、自上而下的状态;JSX;组件驱动的架构。

 

如果我们用 Solid 重写 Counter 组件,会这样开始:


function Counter() {  const [count, setCount] = createSignal(0);  return <div>The count is: {count()}</div>;}
复制代码


到目前为止我们看到了一个很大的不同点:count 是一个函数。这称为访问器(accessor),它是 Solid 工作机制的重要组成部分。当然,我们这里没有关于按间隔递增 count 的内容,所以下面把它添加进去:


function Counter() {  const [count, setCount] = createSignal(0);  setInterval(() => {    setCount(count() + 1);  }, 1000);  return <div>The count is: {count()}</div>;}
复制代码


这肯定行不通,对吧?每次组件渲染时不会设置新的间隔吗?

 

没有。它就这么正常运行了。

 

但为什么会这样?好吧,事实证明 Solid 不需要重新运行 Counter 函数来重渲染新的计数。事实上,它根本不需要重新运行 Counter 函数。如果我们在 Counter 函数中添加一个 console.log 语句,就会看到它只运行一次。


function Counter() {  const [count, setCount] = createSignal(0);  setInterval(() => {    setCount(count() + 1);  }, 1000);  console.log('The Counter function was called!');  return <div>The count is: {count()}</div>;}
复制代码


在我们的控制台中,只有一个孤独的日志语句:


"The Counter function was called!"
复制代码


在 Solid 中,除非我们明确要求,否则代码不会多次运行。

但是 hooks 呢?


于是我在 Solid 中解决了 React useEffect hook 的问题,而无需编写看起来像 hooks 的东西。我们可以扩展我们的计数器例子来探索 Solid 效果。

 

如果我们想在每次计数增加时 console.log count 怎么办?你的第一反应可能是在我们的函数中使用 console.log:


function Counter() {  const [count, setCount] = createSignal(0);  setInterval(() => {    setCount(count() + 1);  }, 1000);  console.log(`The count is ${count()}`);  return <div>The count is: {count()}</div>;}
复制代码


但这不起作用。请记住,Counter 函数只运行一次!但我们可以使用 Solid 的 createEffect 函数来获得想要的效果:


function Counter() {  const [count, setCount] = createSignal(0);  setInterval(() => {    setCount(count() + 1);  }, 1000);  createEffect(() => {    console.log(`The count is ${count()}`);  });  return <div>The count is: {count()}</div>;}
复制代码


这行得通!而且我们甚至不必告诉 Solid,说这个效果取决于 count 变量。这才是真正的响应式设计。如果在 createEffect 函数内部调用了第二个访问器,它也会让效果运行起来。

一些更有趣的 Solid 概念

响应性,而不是生命周期 hooks


如果你已经在 React 领域有一段时间的经验了,那么下面的代码更改可能真的会让你大跌眼镜:

const [count, setCount] = createSignal(0);setInterval(() => {  setCount(count() + 1);}, 1000);createEffect(() => {  console.log(`The count is ${count()}`);});function Counter() {  return <div>The count is: {count()}</div>;}
复制代码


并且代码仍然是有效的。我们的 count 信号不需要存在于一个组件函数中,依赖它的效果也不需要。一切都只是响应式系统的一部分,“生命周期 hooks”实际上并没有起到太大的作用。

细粒度的 DOM 更新


前面我主要关注的是 Solid 的开发体验(例如更容易编写没有错误的代码),但 Solid 的性能表现也得到了很多赞誉。其强大性能的一个关键来源是它直接与 DOM 交互(无虚拟 DOM)并执行“细粒度”的 DOM 更新。

 

考虑对我们的计数器进行以下调整:


function Counter() {  const [count, setCount] = createSignal(0);  setInterval(() => {    setCount(count() + 1);  }, 1000);  return (    <div>      The {(console.log('DOM update A'), false)} count is:{' '}      {(console.log('DOM update B'), count())}    </div>  );}
复制代码


运行它会在控制台中获得以下日志:


DOM update ADOM update BDOM update BDOM update BDOM update BDOM update BDOM update B
复制代码


换句话说,每秒更新的唯一内容是包含 count 的一小部分 DOM。Solid 甚至没有重新运行同一 div 中较早的 console.log。

小结


在过去的几年里我很喜欢使用 React;在处理实际的 DOM 时,我总感觉它有着正确的抽象级别。话虽如此,我也开始注意到 React hooks 代码经常变得容易出错。我感觉 Solid.js 使用了 React 的许多符合人体工程学的部分,同时最大程度减少了混乱和错误。本文向你展示的是 Solid 的一些让我惊叹的部分,感兴趣的话我建议你查看https://www.solidjs.com并自己探索这个框架。

 

原文链接:https://typeofnan.dev/solid-js-feels-like-what-i-always-wanted-react-to-be/

2022-04-12 15:3311613

评论 5 条评论

发布
用户头像
关于取消virtual DOM, 直接操作 DOM,这样不会带来性能开销吗?我的理解是虚拟DOM是轻量级的纯内存操作,性能好,所以才可以放心做高频次的刷新,只有发现了真正有改动了才会同步去DOM。取消虚拟DOM是省事了,可是高刷新场景下的性能是不是被牺牲了?
2022-04-17 14:00
回复
众所周知,性能最快的是原生
2022-04-18 10:34
回复
用户头像
生态怎么样,有组件库吗
2022-04-13 17:32
回复
用户头像
闭包问题 可以用ref解决 countRef.current = count
2022-04-12 16:29
回复
但是这个真的很赞
2022-04-12 16:31
回复
没有更多了
发现更多内容

Linux虚拟网络设备:底层原理与性能优化深度解析

GousterCloud

Linux Kenel 设备驱动

虚拟网络设备性能优化

GousterCloud

Linux Kenel 虚拟网卡

心态崩了,约了半个月,就只有3个面试!

王中阳Go

微服务 并发编程 面试题 面经 大厂面经

✅基于TTL 解决线程池中 ThreadLocal 线程无法共享的问题

派大星

ThreadLocal Java 面试题 互联网大厂面试

一次对Redis内存调整的过程

麦兜

redis

Linux虚拟网络设备深度解析:使用场景、分类与开发者指南

GousterCloud

Linux Kenel 虚拟网卡

Ascend C 自定义算子 Kernel Launch调用入门

华为云开发者联盟

人工智能 华为云 华为云开发者联盟 企业号2024年4月PK榜 自定义算子开发

透明LED屏幕如何设计效果更好?

Dylan

技术 设计 系统 LED LED显示屏

深入了解商品口碑:淘宝评论API助力消费者明智选择

技术冰糖葫芦

API Explorer API 文档 pinduoduo API

沙丘世界,如何抵抗勒索病毒的入侵?

白洞计划

网络安全

虚拟网络设备与网络安全:深入分析与实践应用

GousterCloud

Linux Kenel 虚拟网卡

随易周刊第002期 - 四象

前端之虎陈随易

程序员 前端 Web 周刊 技术周刊

阿里云佘俊泉:边缘云场景的探索与机遇

MasterInTech

异构计算 边缘云 云网融合

2024上海国际半导体产业展览会

AIOTE智博会

半导体展 半导体展会 半导体展览会 半导体博览会

嵌入式会越来越卷吗?

梦笔生花

嵌入式 嵌入式软件 嵌入式应用

虚拟网络设备的真正使命:实现有控制的通信

GousterCloud

Linux Kenel

NineData创始人&CEO叶正盛受邀参加『数据技术嘉年华』的技术大会

NineData

数据库 数据复制 实践 NineData 技术原理

华为云GeminiDB,广告RTA的“登云梯”

华为云开发者联盟

数据库 华为云 华为云开发者联盟 华为云GeminiDB 企业号2024年4月PK榜

掌握Linux虚拟网络设备:从基础到应用的全面指南

GousterCloud

Linux Kenel 网络设备

5.7打补丁—编译和官方一致的Linux_Generic包

GreatSQL

打造银行核心全栈国产化新模式,第三届数字金融技术创新与应用研讨会成功举办

先锋IT

LangChain Agent:赋予 LLM 行动力的神秘力量

蛋先生DX

#人工智能 LLM #LangChain AI Agent 企业号2024年4月PK榜

文心一言 VS 讯飞星火 VS chatgpt (232)-- 算法导论17.1 3题

福大大架构师每日一题

福大大架构师每日一题

虚拟网络设备的网络隔离机制:原理、意义与应用场景深度分析

GousterCloud

Linux Kenel 网卡

一键生成视频!用 PAI-EAS 部署 AI 视频生成模型 SVD 工作流

阿里云大数据AI技术

人工智能 阿里云 AI视频

XML文档节点导航与选择指南

小万哥

xml 程序人生 编程语言 软件工程 前端开发

Linux虚拟网络设备全景解析:定义、工作模式与实践应用

GousterCloud

Linux Kenel 设备

虚拟网络设备与Linux网络协议栈

GousterCloud

Linux Kenel

探索未来产业:新技术、新商业、新趋势

天津汇柏科技有限公司

未来产业

第46期 | GPTSecurity周报

云起无垠

Databend 开源周报第 139 期

Databend

Solid.js 就是我理想中的 React_大前端_Nick Scialli_InfoQ精选文章