反应与信号:10年后
#javascript #网络开发人员 #react #solidjs

旧的温斯顿丘吉尔(Churchill)如何去?

那些未能从历史上学习的人注定要重复它

尽管可能会增加一个更具讽刺意味的附录。

那些研究历史的人注定要站在同时,而其他所有人都重复。

在过去的几周中,我们已经看到了围绕着高元素反应性复兴的兴奋的高潮,被称为信号,被称为信号。

有关JavaScript中信号的历史记录,请查看我的文章:

事实是信号永远不会消失。他们像第三方图书馆一样默默无闻,或者隐藏了框架的普通对象API。即使反应和虚拟DOM带来的常见言论将模式谴责为不可预测和危险。他们没有错。

,但它不仅仅是10年的辩论。因此,我想谈论这些年来情况如何变化并提供SolidJS作为箔纸。


“修理”前端

这次对话的核心是理解什么是反应。 React不是其虚拟DOM。反应不是JSX。到目前为止

组成,单向数据流,从DSL自由,显式突变和静态心理模型。

React具有一组非常强大的原理,可以指导它比任何实施细节更重要。即使几年后,周围的思想领袖也有这种想法,他们固定了前端。

但假设他们没有,该怎么办?如果还有其他方法可以解决当天的问题,该怎么办?


一个可靠的替代方案

固体背后的概念同样简单。它甚至分享了构图,单向数据流和明确的突变等想法,使反应感觉像是对UI开发的简单答案。它在反应性世界之外的地方有所不同,一切都是一种效果。几乎是反应的对立面,将您所做的一切都视为纯净(如没有副作用)。

当我想创建带有信号的计数器按钮时,我会这样做:

function MyCounter() {
  const [count, setCount] = createSignal();

  const myButton = document.createElement("button");
  myButton.onclick = () => setCount(count() + 1);

  // update the text initially and whenever count changes
  createEffect(() => {
    myButton.textContent = count();
  });

  return myButton;
}

我会打电话给我的功能,然后取回按钮。如果我需要另一个按钮,我会再做一次。这是非常设定的,忘记了。我创建了一个DOM元素,并设置了一些事件侦听器。像DOM本身一样,我不需要打电话给我的按钮进行更新。它是独立的。如果我想要一种更符合人体工程学的写作方式,我会使用JSX。

function MyCounter() {
  const [count, setCount] = createSignal();

  return <button onClick={() => setCount(count() + 1)}>
    {count()}
  </button>
}

信号与过去的信号不同。他们的处决是无故障的。它们是推/拉混合动力车,可以建模计划的工作流程(例如悬念或并发渲染)。并通过自动处置来减轻泄漏的观察者模式。他们曾经是leading benchmarks for several years,不仅用于更新,而且是创建的。


不变性

这么好吗?好吧,也许不是:

显然,这是反应解决的问题。他们到底解决了什么?

将Dan的具体问题暂时放在一边,这取决于不变性。但不是最直接的。信号本身是不变的。您无法修改他们的内容并期望他们做出反应。

const [list, setList] = createSignal([]);

createEffect(() => console.log(JSON.stringify(list())));

list().push("Doesn't trigger");

setList(() => [...list(), "Does trigger"]);

即使使用使用.value的Vue,preact或Qwik的变体,您也正在替换不通过分配来突变的值。那么信号是“可变状态”的意思是什么?

拥有颗粒事件驱动的架构的好处是进行隔离的更新。换句话说,突变。相比之下,React的纯渲染模型抽象了基本的可变世界,每次运行都重新创建其虚拟表示。

查看两个声明的库时,这种区别有多重要,该库会在数据接口是明确的,副作用管理的以及执行良好的情况下驱动状态的。


单向流

我不是2向绑定的粉丝。单向流是一件非常好的事情。我经历了这些推文中引用的相同内容。您可能已经注意到Solid在其原始词中采用了读/写隔离。它的嵌套反应性代理甚至是如此。

如果创建一个反应性原始性,则会获得仅读取接口和写入接口。关于这一点的看法已经根深蒂固,以至于社区的成员喜欢拖拉我,滥用了Getters和Setters伪造Mutability。

我想对Solid的设计要做的重要事情之一就是保持思维的当地。固体中的所有工作都是在效果的地方完成的,这是我们插入DOM的地方。父母是否使用信号都没关系。您的作者需要您的需要。如果需要的话,将每个prop视为反应性,并在需要的地方访问它。没有必要的全球思维。不担心可重构性。

我们通过建议在编写道具时建议您访问信号的值而不是传递信号。让您的组件期望值而不是信号。如果可以反应性,则可以通过将它们包裹在Getter中来保存反应性。

<Greeting name={name()} />

// becomes
Greeting({ get name() {  return name() })

<Greeting name={"John"} />

// becomes
Greeting({ name: "John" })

它怎么知道的?一个简单的启发式。如果表达式包含函数调用或属性访问,则将其包装。 JavaScript中的反应性值必须为函数调用,以便我们可以跟踪读取。因此,任何可能是Getter或代理的功能调用或属性访问都可能具有反应性,因此我们将其包装。

积极的是,对于Greeting而言,无论您如何消费,您都以相同的方式访问该物业:props.name。没有isSignal检查或不必要地覆盖以使事物变成信号。 props.name始终是string。作为一个价值,没有对突变的期望。道具是只读的,数据流以一种方式流动。


选择与退出

这可能是讨论的关键。有很多方法可以解决此问题。大多数图书馆出于开发人员的经验原因选择了反应性,因为自动依赖性跟踪意味着不必担心丢失更新。

对于React开发人员来说,不难想象。图片挂钩没有依赖性阵列。钩子中的依赖性阵列的存在表明React可能会错过更新。同样,使用React Server组件时,您选择进入客户端组件(use client)。多年来,还有其他解决方案一直通过汇编自动化这一点,但有时有一些明确的话要说。

这通常不是一个单一的决定。您选择了您选择的东西,并且在任何框架中都选择了东西。实际上,所有框架可能更像是这样:

框架理想可以是无可非议的,但现实并不是那么清晰。

这使我进入了这个示例:

从实体的角度来看,这是2个截然不同的函数,因为实心的JSX处理以及它们仅运行一次。一旦您意识到,这并不是模棱两可的,并且很容易避免。甚至还有一个棉绒规则。

这就像期望它们相同:

const value = Date.now();
function getTime1() {
  return value;
}

function getTime2() {
  return Date.now();
}

移动表达式不会改变Date.now()的作用,而是吊起它会改变函数的行为。

也许它不是理想的,但是这种心理模型并非没有自己的好处:


这可以为真实而“修复”吗?

这是逻辑后续行动。这是一个语言问题。固定的外观如何?编译器面临的挑战在于,很难说明边缘案例并了解出现问题时会发生什么。这在很大程度上是历史上有反应或坚定谨慎界限的原因。

自第一次介绍固体以来,我们已经让人们exploring different compilation,因为作为原始人的信号非常适应性且性能。

在2021年,我对此进行了刺伤。

React团队还宣布他们也在研究。

两个系统都施加了规则。 React要您记住不要在功能体中做任何不纯洁的事情。那是因为如果您这样做,您可以观察到抽象泄漏,如果它们是为了优化引擎盖下的东西。包括可能不重新运行组件的部分。

固体已经进行了优化,而无需编译器或像React.memouseCallbackuseRef这样的额外包装器,但是像React一样,可以从一些更简化的人体工程学中受益,例如不必担心读取信号的何处。

最终结果几乎相同。


最后的想法

Image description

所有这一切的最奇怪的部分是,在看反应性时,反应团队并不感觉自己在照镜子。通过添加钩子,他们为接近信号的模型牺牲了部分重新渲染的纯度。通过添加编译器来删除有关记忆的钩子,他们完成了这个故事。

现在应该理解,这些是developed independently。或者至少没有得到确认,这并不奇怪,鉴于旧警卫的观点:

幸运的是,今天的反应也不是10年前的反应。

反应通过向我们传授应指导如何构建UI的重要原则来改变前端世界。他们这样做是通过信念的力量来做到的,这是混乱之海中有理由的独特声音。由于他们的出色工作和无数的教训,我们就是今天的所在。

次发生了变化。 “固定”的范式将重新浮出水面几乎是合适的。它关闭了循环。完成故事。当所有的噪音和部落主义消失时,剩下的就是一个健康竞争的故事,使网络前进。


可以说,通过将叙述一起缝制在上下文中来构建叙事是永远不会有礼貌的。时间继续前进,观点也会改变。但这是React思想领导的漫长而强烈的情绪。他们从一开始就一直在发声。从最近几天开始,我就可以在对话中抓住所有这些报价。

有关React的早期历史观看的更多信息: