前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Solid.js 就是我理想中的 React

Solid.js 就是我理想中的 React

作者头像
深度学习与Python
发布于 2022-04-19 02:05:47
发布于 2022-04-19 02:05:47
2K00
代码可运行
举报
运行总次数:0
代码可运行

作者 | Nick Scialli

译者 | 王强

策划 | 闫园园

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 出现的时候我非常兴奋。我的计数器可以简化为以下写法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 变量添加到依赖数组中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 的函数形式来避免直接引用当前值

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 组件,会这样开始:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function Counter() {
  const [count, setCount] = createSignal(0);
  return <div>The count is: {count()}</div>;
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 语句,就会看到它只运行一次。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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>;
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"The Counter function was called!"

"The Counter function was called!"在 Solid 中,除非我们明确要求,否则代码不会多次运行。

但是 hooks 呢?

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 函数来获得想要的效果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 领域有一段时间的经验了,那么下面的代码更改可能真的会让你大跌眼镜:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 更新。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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>
  );
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
DOM update A
DOM update B
DOM update B
DOM update B
DOM update B
DOM update B
DOM 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-03-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 InfoQ 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
ggplot2优雅绘制蛋白结构域与基因结构图
❝小编很久之前写过一系列基因家族数据可视化的文档,最近对基因家族数据可视化又有了新的认识下面来绘制这一类文章里面的一张常用图,下面来看具体操作 ❞ 绘制进化树 tree <- read.newick("tree.nwk",node.label = "support") %>% ggtree(branch.length = "none")+ theme_void()+ theme(legend.title=element_blank(), legend.position =
R语言数据分析指南
2022/09/21
1.9K0
ggplot2优雅绘制蛋白结构域与基因结构图
漂亮的单细胞多组火山图
《Single-cell transcriptome analysis reveals the association between histone lactylation and cisplatin resistance in bladder cancer 》
用户11414625
2024/12/20
1070
漂亮的单细胞多组火山图
生物信息数据分析教程视频——13-3种R包(DESeq2、edgeR和limma)进行RNAseq的差异表达分析与比较
视频地址:http://mpvideo.qpic.cn/0bc3zeaakaaalqalwhjtmzrvbsodaxeqabia.f10002.mp4? 参考文章: 超详细的DESeq2和edg
DoubleHelix
2022/12/15
1.4K0
生物信息数据分析教程视频——13-3种R包(DESeq2、edgeR和limma)进行RNAseq的差异表达分析与比较
多分组差异分析结果的两种展示形式
最近分析了一批RNA-seq的测序数据,发现DEseq2分析后有多了比较组。之前我们会绘制多个火山图或Upset图去呈现结果。但是,由于这两种方式被大家用太多了,所以我们想换几种另外的展示方式。我们在网上差了很多资料,其中有两个图个人感觉很不错,于是,就有了这一期的文案。下面我们直接进入今天的主题分享:
生信菜鸟团
2024/05/20
5220
多分组差异分析结果的两种展示形式
ggplot2绘制突变全景图
附件下载地址:https://ehoonline.biomedcentral.com/articles/10.1186/s40164-021-00200-x
医学和生信笔记
2022/11/15
1.2K0
ggplot2绘制突变全景图
ggplot2优雅绘制多组旭日图
有需要学习数据可视化的朋友,欢迎到小编的「淘宝店铺」 「R语言数据分析指南」下单购买,内容主要包括各种「高分论文的图表分析复现以及一些个性化图表的绘制」均包含数据+代码。购买会员文档后微信发小编订单号即邀请进新的会员交流群。
R语言数据分析指南
2024/03/20
5210
ggplot2优雅绘制多组旭日图
一网打尽转录组差异分析!!!
差异分析在转录组数据分析中占据着举足轻重的地位,是揭示基因表达变化的关键步骤。然而,面对众多如DESeq2、limma和edgeR等转录组分析R包,分析人员常常面临选择困境。本文旨在深入探讨这些常用差异分析R包的特点、优劣,以及它们与t检验/Wilcox秩和检验(Wilcox-rank-sum test)在差异分析结果上的异同点。
生信学习者
2024/06/11
4580
一网打尽转录组差异分析!!!
没想到修个火山图这么麻烦
MAplot转录组差异基因表达展示_maplot r语言_TS的美梦的博客-CSDN博客自己也顺着这线索另外找了教程
生信菜鸟团
2023/09/09
7050
没想到修个火山图这么麻烦
ggplot2优雅的绘制森林图
❝本节来介绍如何使用ggplot2来绘制森林图,下面通过一个小例子来进行展示 ❞ 加载R包 library(tidyverse) 导入数据 unicox <- read_csv("AKT3_mRNA_OS_pancan_unicox.csv") 绘制森林图 p1 <- ggplot(unicox,aes(HR_log, cancer, col=Type))+ geom_point(aes(size=-log10(p.value)))+ geom_errorbarh(aes(xmax =u
R语言数据分析指南
2022/09/21
1.5K0
ggplot2优雅的绘制森林图
NC图表绘制-ggplot2绘制多组填充热图
R语言数据分析指南
2024/04/02
1600
NC图表绘制-ggplot2绘制多组填充热图
R自定义构建函数绘制相关性条形图
构建好绘图函数后让我们来进行可视化的操作,由于原始数据较多在此我们筛选一部分数据进行可视化操作
R语言数据分析指南
2022/09/21
4500
R自定义构建函数绘制相关性条形图
TCGA数据挖掘(四):表达差异分析(4)
在之前我们的文章:TCGA数据挖掘(三):表达差异分析中,我们利用的是TCGAbiolinks包中的TCGAanalyze_DEA函数进行差异表达分析,我们也提到可以选择基于limma或edgeR包进行分析,TCGA数据挖掘(三):表达差异分析这一讲中我们利用的是edgeR包,之后我们在文章:TCGA数据挖掘(四):表达差异分析(2)和TCGA数据挖掘(四):表达差异分析(3)中分别也介绍了其他方法的差异分析,包括edgeR和DESeq包,今天这一讲,我们就利用TCGAbiolinks包中的TCGAanalyze_DEA函数基于limma包进行差异分析。
DoubleHelix
2019/09/18
4.6K0
TCGA数据挖掘(四):表达差异分析(4)
ggplot2优雅的绘制配对气泡图
Step1. R包和数据加载、主题设置 测试数据在: 链接:https://pan.baidu.com/s/1MuMgMZZCcdO-IGS7_ysfkQ?pwd=1234 提取码:1234 libr
生信菜鸟团
2023/08/23
4990
ggplot2优雅的绘制配对气泡图
ggplot2轻松绘制误差线点图与箱线图
R语言数据分析指南
2023/08/18
5330
ggplot2轻松绘制误差线点图与箱线图
三种转录组差异分析方法及区别你会了吗?
在做项目时,曾有小伙伴对我用edgeR进行差异分析筛选出的具体显著差异基因表示质疑,因为发表的文章清楚的说明某个基因是差异基因,但是我edgeR的分析结果并没有表明。在小伙伴的质疑下,我认真看了下文章,发现文章用的是DEseq2进行差异分析。值得注意的是该小伙伴关注的差异基因是一个离散比较大的基因,此处的离散较大可以理解为假定对照组为5,6,7;实验组则为14,13,3的情况。那为什么这个基因在edgeR分析下不是显著差异基因,然而在DEseq2的分析下是差异基因呢?这应该很大程度源于算法判定显著差异基因的区别。接着,我看了关于DEseq2与edgeR区别的描述,发现「edgeR与Deseq2都是基于负二项分布模型做的,两者处理同一组数据时,相同阈值处理大部分基因是一样的,但是也会有一部分基因会因为离散度不同导致差异不同」,如刚刚示例的基因离散度被DEseq2识别为差异,但是不被edgeR识别,所以两种算法获取的差异基因与数目是存在细微区别的。
生信菜鸟团
2022/10/31
5.9K1
三种转录组差异分析方法及区别你会了吗?
文献组图
追风少年i
2025/01/07
640
文献组图
scRNA分析| Seurat堆叠小提琴图不满足? 那就ggplot2 堆叠 各种元素
单细胞常见的可视化方式有DimPlot,FeaturePlot ,DotPlot ,VlnPlot 和 DoHeatmap几种 ,Seurat均可以实现,但文献中的图大多会精美很多。比如
生信补给站
2023/08/25
4.6K0
scRNA分析| Seurat堆叠小提琴图不满足?  那就ggplot2 堆叠 各种元素
带有疾病进展的多分组差异结果如何展示?
此次给绘制的图来自文献《Triple DMARD treatment in early rheumatoid arthritis modulates synovial T cell activation and plasmablast/plasma cell differentiation pathways》,是2017发表的,使用了他们团队自己2016发表的转录组测序数据,所以其实是有两个转录组测序数据集。
生信技能树
2025/01/02
1430
带有疾病进展的多分组差异结果如何展示?
跟着Nature Communications学作图:R语言ggplot2堆积柱形图组合饼状图
https://www.nature.com/articles/s41467-024-45739-5
用户7010445
2024/03/22
3360
跟着Nature Communications学作图:R语言ggplot2堆积柱形图组合饼状图
生物信息数据分析教程视频——07-TCGA数据库:基因的表达探索
视频地址:http://mpvideo.qpic.cn/0b2ewiaakaaahmalygztmbrvbmwdawzaabia.f10002.mp4? 参考文章: 【0代码】单基因泛癌分析教程 视频
DoubleHelix
2022/12/15
7150
推荐阅读
相关推荐
ggplot2优雅绘制蛋白结构域与基因结构图
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档