大家好,我是Kason。最近,Angular和Qwik的作者MI?KOHEVERY发帖称,Signal是前端框架的未来,正在考虑在Angular中实现它。在此之前,Vue、Solid.js、Preact、Svelte都实现了Signal。其实signal并不是一个新概念,它有很多别名,比如:responsiveupdatefine-grainedupdate如果你了解Vue响应式更新的实现原理,你对Signal就不陌生了。事实上,Signal的技术在10年前就被用在了Knockout框架中。为什么这项技术受到越来越多的前端框架的青睐?在本文中,让我们一起讨论这个话题。欢迎加入人类优质前端交流群。信号的本质是将状态的引用和状态值的获取分开。这么说可能有点抽象,我们先来看一个非信号的例子。React中状态的定义如下:functionApp(){const[state,dispatch]=useState(0);returndispatch(state+1)}>{state}
}useState的返回值包括两部分:state:状态的值dispatch:状态的setter可以被找到,并且状态耦合了对状态的引用和状态值的获取。让我们看另一个信号示例。以下是用Solid.js编写的相同示例的外观:functionApp(){const[getState,dispatch]=createSignal(0);return
dispatch(getState()+1)}>{getState()}}createSignal的返回值包括两部分:getState:状态的引用dispatch:区别state的setter之间体现在getState中,其中:getState是对state的引用getState()是对state的引用值的获取是指我们不需要立即获取state的值,而是get它在我们需要它的时候执行(即在我们需要它的时候执行getState())。这样做有什么好处?如果我们在需要的时候获取状态的值,就可以感知当前的上下文。举个粗略的例子,在下面的代码中,组件实例(Componentinstance)在渲染时会将全局变量cpnContext指向自身:letcpnContext=null;类组件{render(){cpnContext=this;//...省略逻辑}}然后在createSignal返回的getState方法内部,可以通过获取全局变量cpnContext来感知当前处于哪个组件的渲染进程:functioncreateSignal(){//...省略逻辑functiongetState(){constcurContext=cpnContext;//...}functiondispatch{}return[getState,dispatch]}这样做的目的是在状态变化和哪些组件需要更新之间建立联系。与React相比,基于Signal的框架会有两个优势:更好的细粒度更新性能更好的DX(开发者体验)更好的细粒度更新性能因为Signal建立了状态和组件之间的联系,所以比React有性能优势.例如,在我的电脑上,使用Svelte渲染1w个li,然后点击一个li来改变它的内容:{#eachitemsasitem(item.id)}items[item.id].name='change!'}>{item.name}{/each}
从点击事件的触发,到Svelte逻辑的完成,再到浏览器重排重绘一共耗时18.88ms,其中Svelte的逻辑执行只用了9.5ms:同样的例子用React实现,触发点击后总耗时98.5ms,其中React的逻辑执行89.38ms:在这个例子中,React性能比Svelte差了一个数量级。造成这种差异的很大一部分原因是Svlete在更新之前知道状态发生变化时需要更新哪个组件。而这一切的源头就在于Signal。更好的DX和更好的开发者体验主要体现在两个方面:Signal对上下文环境的感知能力降低了代码的精神负担。比如在React中,useEffect在使用时需要指明依赖的状态:useEffect(()=>{//...state1,state2变化后的逻辑},[state1,state2])如果执行Signal采用了,state可以感知到自己是在useEffect上下文中,可以自动建立两者之间的联系,不用担心写的少依赖state和闭包陷阱等问题,减少精神负担。比如在Vue中类似useEffect的watch(只是功能相似,两者的用途其实不同)不需要显式指定依赖:减轻开发者性能优化的精神负担使用Signal框架通常可以获得良好的runtimeperformance,因此不需要额外的性能优化API。相对于React,如果开发者遇到性能问题,需要手动调用性能优化API(如React.memo、useMemo、PureComponent)。综上所述,有上面这么多优点,难怪很多框架都用Signal。那么React对Signal的态度是什么?React团队成员对此的观点是:可以引入像Signal这样的原语。Signal在性能上确实不错,但是不符合React的理念。React的哲学可以用一句话来概括:UI反映了某一时刻状态的快照。既然是快照,就不是局部的,而是一个整体的概念。在React中,状态更新会导致整个应用重新渲染,这是对React快照概念的最好诠释。现阶段React的所有实现都是基于快照的概念。因此,即使引入了Signal这样的原语,也可能是像Mobx这样的上层实现,而不是从底层重构。我个人倾向于认为:React团队认可Signal的优势,但由于工作艰苦,而且现代设备的性能通常是多余的,性能问题不是首要问题。如果这个观点是正确的,那么React可能会在开发者体验方面陷入困境(另一个信号强度)。比如去年提出的RFC:useEvent,可能就是这方面的尝试。