您当前的位置:首页 > 电脑百科 > 程序开发 > 前端

Signals大火!为何众多前端主流框架都要实现它?

时间:2023-06-06 14:56:21  来源:今日头条  作者:高级前端进阶

话不多说,直接开始!

1.什么是反应式编程

反应式编程是一种编程思想与方式,是为了简化并发编程而出现的。与传统的处理方式相比,反应式编程能够基于数据流中的事件进行反应处理

例如:在 a+b=c 的场景,在传统编程方式下如果 a、b 发生变化,那么需要重新计算 a+b 来得到 c 值。而反应式编程中,不需要重新计算。a、b 的变化事件会触发 c 的值自动更新。这种方式类似于在消息中间件中常见的发布/订阅模式。由流发布事件,而代码逻辑作为订阅方基于事件进行处理,并且是异步执行的。

 

反应式编程中,最基本的处理单元是事件流(事件流是不可变的,对流进行操作只会返回新的流)中的事件。核心是基于事件流、无阻塞、异步的,使用反应式编程不需要编写底层的并发、并行代码。并且由于其声明式编写代码的方式,使得异步代码易读且易维护。

常用的反应式编程类库包括:Reactor、RxJAVA 2、Vert.x 以及 Ratpack 等等。

2.可以自动化执行的逻辑都将自动执行

当 React 被引入时,比其他任何库和框架都更加吸引开发者,它引入了一个非常有趣的概念称为单向数据绑定,或者更简单地说,作为虚拟 DOM 的一部分引入的单向数据流。

它提供了一种全新的体验,即当数据状态发生变化时,开发人员不必考虑更新如何在 UI 中流动。然而,随着越来越多的 hooks 被引入,有一些语法规则可以确保它们以最佳方式执行。 从本质上讲,与 React 的原始目的有偏差,即单向流或显式突变(explicit mutations)。比如:

  • 自动正确填写依赖数组
  • 自动记住正确的值或回调以进行渲染优化
  • 有意识地避免 prop drilling

然而,如果无法有效、正确地避免以上情况,可能导致一些严重的性能问题,即一股脑的重新渲染。 这与仅编写组件来构建 UI 的初衷略有不同。

props drilling 是数据以 props 的形式从 React 组件树中的一部分传递到另一部分, 只是传递的组件层级过深、而中间层组件并不需要这些 props,只是做一个向下转发, 这种情况就叫做 props drilling。

信号(Signals)采用反应式编程原语来帮助消除复杂性,并通过将注意力转移到正确的事情上来帮助改善开发人员体验,而不必明确遵循一组语法规则来获得性能提升。

3.什么是信号

信号是反应式编程的关键原语(Primitive)之一。从语法上讲,它们与 React 中的状态管理非常相似。然而,信号的反应式能力赋予了它的诸多优势。比如下面的例子:

const [state, setState] = useState(0);
// state -> value
// setState -> setter
const [signal, setSignal] = createSignal(0);
// signal -> getter
// setSignal -> setter

看起来几乎相同,除了 useState 返回一个值而 useSignal 返回一个 getter 函数。

信号在其概念中相当于一个值的框,当一个框中的值发生变化时,所有相关框中的值都会自动更新。 Signal 会重复该过程,直到更新所有框。

乍一看,这非常类似于 useState 和 useEffect 的组合,但请注意信号是全局定义的。 这允许,当框中的值更改时,仅刷新 VirtualDOM 中依赖于它的那些组件。

import { signal, computed } from "@preact/signals";
const count = signal(0);
// computed
const double = computed(() => count.value * 2);
effect(() => console.log(double.value)));
function Counter() {
  return (
    <button onClick={() => count.value++}>
      {count} x 2 = {double}
    </button>
  );
}

当然,这并不是 Signals 优势的全部。 在应用程序的整个生命周期中,信号引用不会改变,因此开发者不必担心多余的渲染。 computed 和 effect 函数都不需要依赖项列表而是会自动检测。 此外,如果依赖关系发生变化,但最终值保持不变,也不会刷新依赖关系。

本质上,Signals 是一个用纯 JavaScript 编写的库, 如果开发者在 JSX 中使用 signal,而不是 signal.value,它将被视为一个单独的组件。 这意味着如果它的值发生变化,不会渲染整个父组件,只会刷新一小段文本。

// 在此示例中,整个 Counter 组件将在计数更改时重新渲染
const count = signal(0);
function Counter() {
  return (
    <>
      <SomeOtherComponent />
      Value: {count.value}
    </>
  );
}
// 在此示例中,只有计数值会在计数更改时重新渲染
const count = signal(0);
function Counter() {
  return (
    <>
      <SomeOtherComponent />
      Value: {count}
    </>
  );
}

文章前面部分讲过,Signals 实际上是观察者模式的简单实现,当检索一个值时同时订阅了它。 当设置一个值时,实际上是发出一个事件。 真正的魔力始于 Signlas 和框架之间的接口。 为了方便开发人员,创建者应用了一些有争议的技巧,例如:重写 React.createElement 函数。

4.信号比状态好在哪里

一旦 useState 返回一个值,库通常不再关心该值的使用。 开发人员必须自己决定在何处使用该值,并且必须确保想要订阅该值更改的任何 Effects、Memos 或 Callbacks 都在其依赖列表中提到该值。

图片来自:https://www.YouTube.com/watch?v=0t1tJTh0bLs

除此之外,记住该值以避免不必要的重新渲染,这显然对开发者提出了更高的要求。

  • Signal: 是最简单的原语(Primitive),它们包含值,以及 get 和 set 函数,因此我们可以在读取和写入的时候进行拦截
  • Effects:Effect 是读取 Signal 的封装函数,并且会在依赖的 Signal 值发生变化时重新执行。这对于创建诸如渲染之类副作用很有用。
  • Memos:Memo 是缓存的派生值,有着 Signal 和 Effect 相同的属性。Memo 跟踪自己的 Signal,仅在这些 Signal 发生变化时重新执行,并且本身是可跟踪的 Signal

比如下面的ParentComponent组件:

function ParentComponent() {
  const [state, setState] = useState(0);
  const stateVal = useMemo(() => {
    return doSomeExpensiveStateCalculation(state);
  }, [state]);
  // 显式记忆并确保依赖关系准确

  useEffect(() => {
    sendDataToServer(state);
  }, [state]);
  // 显式调用状态订阅

  return (
    <div>
      <ChildComponent stateVal={stateVal} />
    </div>
  );
}

createSignal 返回一个 getter 函数,因为信号本质上是反应性的。 为了进一步分解,信号跟踪谁对状态的变化感兴趣,如果发生变化,它会通知这些订阅者。

为了获得此订阅者信息,信号会跟踪调用这些状态获取器(本质上是一个函数)的上下文,调用 getter 创建订阅。这非常有用,因为库本身可以自行管理订阅状态更改的订阅者,并在更改后通知他们,而无需开发人员明确调用它。

createEffect(() => {
  updateDataElswhere(state());
});
// effect 仅在 `state` 改变时运行 - 自动订阅

调用 getter 的上下文(不要与 React Context API 混淆)是库唯一会通知的上下文,这意味着记忆、显式填充大型依赖项数组以及修复不必要的重新渲染都可以有效避免。有助于避免使用大量用于此目的的额外 Hooks,例如 useRef、useCallback、useMemo 和大量重新渲染。

Signal 极大地增强了开发人员的体验,并将重点转移回为 UI 构建组件,而不是花费额外的 10% 的开发人员精力来遵守严格的性能优化语法规则。

function ParentComponent() {
  const [state, setState] = createSignal(0);
  const stateVal = doSomeExpensiveStateCalculation(state());
  // 不需要显式记忆

  createEffect(() => {
    sendDataToServer(state());
  });
  // 只有在状态改变时才会被触发 - 效果会自动添加为订阅者

  return (
    <div>
      <ChildComponent stateVal={stateVal} />
    </div>
  );
}

5.本文总结

一般而言,使用信号和反应式编程似乎存在非常偏见的立场。 然而,事实并非如此。

React 是一个高性能、优化的库。尽管在以最佳方式使用状态方面存在一些差距或遗漏,从而导致不必要的重新渲染,但它仍然非常快。 在以某种方式使用 React 多年之后,前端开发人员已经习惯于将特定的数据流可视化并重新渲染,用反应式编程思维完全取代它在一定程度上显得跛脚。但是,React 仍然是构建用户界面的优秀选择。

反应式编程,除了性能增强之外,还通过归结为三个主要原语:Signal、Memo 和 Effects,使开发人员的体验更加简单。 这有助于开发者更多地关注为 UI 构建组件,而不是担心显式处理性能优化。

目前,Signal 也越来越受欢迎,并且是许多现代 Web 框架的一部分,例如 Solid.js、Preact、Qwik 和 Vue.js。

因为篇幅有限,文章并没有过多展开,如果有兴趣,可以在我的主页继续阅读,同时文末的参考资料提供了大量优秀文档以供学习。最后,欢迎大家点赞、评论、转发、收藏!

参考资料

https://www.velotio.com/engineering-blog/why-signals-could-be-the-future-for-modern-web-frameworks

https://www.solidjs.com/guides/reactivity#how-it-works

https://www.builder.io/blog/usesignal-is-the-future-of-web-frameworks#what-signal-is

https://vived.io/signal-a-new-way-to-manage-Application-state-frontend-weekly-vol-104/

https://cloud.tencent.com/developer/article/1602301

https://blog.csdn.NET/wumu0927/article/detAIls/122288050

https://preactjs.com/guide/v10/signals/

https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob



Tags:Signals   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
浅析 Preact Signals 及实现原理
介绍Preact Signals 是 Preact 团队在22年9月引入的一个特性。我们可以将它理解为一种细粒度响应式数据管理的方式,这个在很多前端框架中都会有类似的概念,例如 SolidJS、Vue3...【详细内容】
2023-12-18  Search: Signals  点击:(109)  评论:(0)  加入收藏
Signals大火!为何众多前端主流框架都要实现它?
话不多说,直接开始!1.什么是反应式编程反应式编程是一种编程思想与方式,是为了简化并发编程而出现的。与传统的处理方式相比,反应式编程能够基于数据流中的事件进行反应处理。例...【详细内容】
2023-06-06  Search: Signals  点击:(390)  评论:(0)  加入收藏
▌简易百科推荐
20k级别前端是怎么使用LocalStorage的,想知道吗?
当咱们把咱们想缓存的东西,存在localStorage、sessionStorage中,在开发过程中,确实有利于咱们的开发,咱们想看的时候也是一目了然,点击Application就可以看到。前言大家好,我是林...【详细内容】
2024-03-26  前端之神  微信公众号  Tags:前端   点击:(12)  评论:(0)  加入收藏
前端不存在了?盲测64%的人更喜欢GPT-4V的设计,杨笛一等团队新作
3 月 9 日央视的一档节目上,百度创始人、董事长兼 CEO 李彦宏指出,以后不会存在「程序员」这种职业了,因为只要会说话,人人都会具备程序员的能力。「未来的编程语言只会剩下两种...【详细内容】
2024-03-11  机器之心Pro    Tags:前端   点击:(9)  评论:(0)  加入收藏
前端开始“锈化”?Vue团队开源JS打包工具:基于Rust、速度极快、尤雨溪主导
Vue 团队已正式开源Rolldown &mdash;&mdash; 基于 Rust 的 JavaScrip 打包工具。Rolldown 是使用 Rust 开发的 Rollup 替代品,它提供与 Rollup 兼容的应用程序接口和插件接口...【详细内容】
2024-03-09  OSC开源社区    Tags:Vue   点击:(11)  评论:(0)  加入收藏
两年前端经验还不会手写Promise?
什么是promise?当我们处理异步操作时,我们经常需要进行一系列的操作,如请求数据、处理数据、渲染UI等。在过去,这些操作通常通过回调函数来处理,但是回调函数嵌套过多会导致代码...【详细内容】
2024-03-07  海燕技术栈  微信公众号  Tags:Promise   点击:(23)  评论:(0)  加入收藏
网站开发中的前端和后端开发有什么区别
前端开发和后端开发都是干什么的?有哪些区别?通俗地讲,前端干的工作是用户可以直接看得见的,而后端开发的工作主要在服务端,用户不太能直接看到。虽然前端开发和后端开发的工作有...【详细内容】
2024-02-21  CarryData    Tags:前端   点击:(32)  评论:(0)  加入收藏
网站程序开发中的前后端分离技术
随着互联网的快速发展和技术的不断创新,传统的网站开发模式已经难以满足日益增长的业务需求。为了提高开发效率、增强系统的可维护性和可扩展性,前后端分离技术逐渐成为了网站...【详细内容】
2024-01-31  网站建设派迪星航    Tags:前后端分离   点击:(23)  评论:(0)  加入收藏
如何优雅的实现前端国际化?
JavaScript 中每个常见问题都有许多成熟的解决方案。当然,国际化 (i18n) 也不例外,有很多成熟的 JavaScript i18n 库可供选择,下面就来分享一些热门的前端国际化库!i18nexti18ne...【详细内容】
2024-01-17  前端充电宝  微信公众号  Tags:前端   点击:(69)  评论:(0)  加入收藏
Vue中Scope是怎么做样式隔离的?
scope样式隔离在 Vue 中,样式隔离是通过 scoped 特性实现的。当在一个组件的 <style> 标签上添加 scoped 特性时,Vue 会自动为这个样式块中的所有选择器添加一个唯一的属性,以...【详细内容】
2024-01-04  海燕技术栈  微信公众号  Tags:Vue   点击:(80)  评论:(0)  加入收藏
vue3中 ref和 reactive的区别 ?
最近有朋友在面试过程中经常被问到这么一个问题,vue3 中的ref 和 reactive的区别在哪里,为什么 要定义两个API 一个 api不能实现 响应式更新吗??带着这个疑问 ,我们 接下来进行逐...【详细内容】
2024-01-03  互联网高级架构师  今日头条  Tags:vue3   点击:(38)  评论:(0)  加入收藏
React18 与 Vue3 全方面对比
1. 编程风格 & 视图风格1.1 编程风格 React 语法少、难度大;Vue 语法多,难度小例如指令:Vue<input v-model="username"/><ul> <li v-for="(item,index) in list" :key="inde...【详细内容】
2024-01-03  爱做梦的程序员  今日头条  Tags:Vue3   点击:(72)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条