大家好,我卡颂。

最近,AngularQwik的作者MIŠKO HEVERY发文示意Signal是前端框架的将来,并思考在Angular中实现它。

在此之前,VueSolid.jsPreactSvelte都已实现Signal。实际上,signal并不是一个新概念,他还有很多别名,比方:

  • 响应式更新
  • 细粒度更新

如果你理解过Vue响应式更新的实现原理,对Signal就不会生疏。

实际上,Signal的技术在10年前Knockout框架中就有利用。为什么这项技术正受到越来越多前端框架的青眼?

本文,让咱们一起探讨下这个话题。

欢送退出人类高质量前端交换群,带飞

signal的实质

signal的实质,是将对状态的援用以及对状态值的获取分来到。这么说可能有点形象,让咱们先看一个非signal的例子。

以下是React中定义状态的形式:

function App() {  const [state, dispatch] = useState(0);  return <p onClick={    () => dispatch(state + 1)  }>{state}</p>}

useState的返回值包含两局部:

  • state:状态的值
  • dispatch:状态的setter

能够发现,state耦合了对状态的援用以及对状态值的获取这两个含意。

再来看一个signal的例子。以下是同一个例子用Solid.js书写的样子:

function App() {  const [getState, dispatch] = createSignal(0);  return <p onClick={    () => dispatch(getState() + 1)  }>{getState()}</p>}

createSignal的返回值包含两局部:

  • getState:对状态的援用
  • dispatch:状态的setter

区别就体现在getState上,其中:

  • getState是对状态的援用
  • getState()是对状态值的获取

也就是说,咱们能够不用立即获取状态的值,而是在须要的时候再获取(即在须要时再执行getState())。

这么做有什么益处呢?如果咱们在须要的时候再获取状态的值,就能感知以后的上下文环境。

举个很毛糙的例子,在上面的代码中,组件实例(Component实例)在render时会将全局变量cpnContext指向本人:

let cpnContext = null;class Component {  render() {    cpnContext = this;    // ...省略逻辑  }}

那么在createSignal返回的getState办法外部,能够获取全局变量cpnContext来感知以后处于哪个组件的渲染流程:

function createSignal() {  // ...省略逻辑  function getState() {    const curContext = cpnContext;    // ...  }  function dispatch {}  return [getState, dispatch]}

这么做的目标是建设状态变动须要更新哪个组件之间的分割。

相比于React,基于Signal实现的框架会有两个劣势:

  • 更好的细粒度更新性能
  • 更好的DX(开发者体验)

更好的细粒度更新性能

因为Signal建设了状态与组件之间的分割,所以相比于React更有性能劣势。

比方,在我的电脑上,用Svelte渲染1w个li,点击某个li后扭转他的内容:

<ul>  {#each items as item (item.id)}      <li on:click={() => items[item.id].name = 'change!'}>{item.name}</li>  {/each}</ul>

从点击事件触发,到Svelte逻辑运行完,再到浏览器重排重绘,总用时18.88ms,其中Svelte的逻辑执行只花了9.5ms:

同样的例子用React实现,触发点击后总用时98.5ms,其中React的逻辑执行了89.38ms:

在这个例子中,React性能比Svelte差了一个数量级。之所以会有这样的差别,很大一部分起因在于Svlete在更新前就晓得状态变动时须要更新哪个组件

而这所有的源头就在于Signal

更好的DX

更好的开发者体验次要体现在两方面:

  1. Signal感知上下文环境的能力缩小了代码心智累赘

比方在React中,useEffect在应用时须要指明依赖的状态:

useEffect(() => {  // ...state1, state2变动后的逻辑}, [state1, state2])

如果采纳Signal的实现,状态能感知到本人在useEffect上下文环境,能够主动建设两者之间的分割,不必再放心少写依赖状态、闭包陷阱等问题,缩小心智累赘。

比方在Vue中,相似useEffect(仅仅是性能相似,两者的用处其实是不同的)的watch,就不须要显式指明依赖:

<script setup>import { ref, watch } from 'vue'const name = ref('')watch(name, (newName, oldName) => {  // ...省略逻辑})</script>
  1. 缩小开发者性能优化的心智累赘

应用Signal的框架通常能取得不错的运行时性能,所以不须要额定的性能优化API。反观React,开发者如果遇到性能问题,须要手动调用性能优化API(比方React.memouseMemoPureComponent)。

总结

有以上这么多长处,难怪很多框架都应用了Signal。那么ReactSignal是什么态度呢?

React团队成员对此的观点是:

  1. 有可能引入相似Signal的原语
  2. Signal性能的确好,但他不太合乎React的理念

React的理念能够用一句话概括:UI反映状态在某一刻的快照

既然是快照,那就不是部分的,而是个整体概念。在React中,状态更新会引起整个利用从新render,就是对React快照理念的最好诠释。

React现阶段的所有实现都是基于快照理念。所以,即便引入相似Signal的原语,可能也是相似Mobx这样的下层实现,而不是从底层重构。

我集体比拟偏向于认为:React团队抵赖Signal的长处,但因为积重难返,而且古代设施的性能通常是过剩的,所以性能问题并不是首要问题。

如果这个观点是正确的,那么React可能会在开发者体验(Signal的另一个长处)方面努致力。比方去年提出的RFC: useEvent可能就是这方面的一次尝试。