大家好,我是卡松。前端框架概念一共有三个容易混淆的概念:响应式更新单向数据流双向数据绑定在继续阅读本文之前,读者可以想一想自己是否清楚了解三者的含义。这三者之所以容易混淆,是因为它们虽然同属于前端框架的概念,但不是同一抽象层次的概念,所以不好直接比较。本文将从三个抽象层次入手,分别说明三者的区别。欢迎加入人类优质前端交流群,大飞响应式更新响应式更新也叫细粒度更新。同时,最近前端圈比较火的Signal这个概念,也描述了响应式更新。一般来说,响应式更新描述的是状态和UI之间的关系,即状态变化如何映射到UI变化。考虑以下示例(来自什么是信号的示例):)constfilteredTodos=useMemo(()=>{returntodos.filter((todo)=>!todo.completed||showCompleted)},[todos,showCompleted])return()}在TodoApp组件中,定义了两种状态:todostodos是否显示已完成的itemsshowCompleted和由上述状态派生的状态filteredTodos。最后返回组件。如果待办事项的状态发生变化,UI应该如何变化?即,我们如何知道状态变化的影响范围?这时候就有两种思路:推(push)原理和拉(pull)原理。我们可以从变化的状态(例子中的todos)开始,根据状态的推导关系一路推送。例子中:todoschangesfilteredTodos是从todos派生出来的,变化传给他。组件依赖于filteredTodos,变化传给他。确定todos变化的最终影响范围后,更新相应的UI。这与UI建立了状态关系。除了推,还有一种方式叫拉。同拉原理的例子,我们也可以建立状态和可能的UI变化之间的关系,然后依次推导出UI变化的范围。例子中:todos变化可能会有UI变化(因为state和可能的UI变化之间的关系已经建立)UI与组件相关,是否变化组件依赖于filteredTodos,filteredTodos派生自todos,因此filteredTodos正在发生变化。由于filteredTodos发生了变化,组件可能会改变计算变化的影响范围。更新UI在主流框架中,React的更新主要基于push、Vue、Preact、Solid.js等,框架采用pull的方式。本文讨论的响应式更新就是这种方法的一种实现。单向数据流我们可以发现,无论是push还是pull,都需要计算变化的影响范围,即一次状态变化后会影响到多少组件。那么,从框架作者的角度,希望可以加入一些约束条件,降低影响范围计算过程的复杂度。同样,从框架使用者的角度,也希望能增加一些约束条件。当影响范围的计算出现bug时,更容易排查问题。这创建了单向数据流。单向数据流是一种契约,它规定当状态发生变化时,变化的影响只会自上而下传递。考虑以下示例:functionParent(){const[num]=useState(0);return;}functionChild({data}){constisEven=data%2===0;return;}functionGrandChild({data}){return
{data}
;}将
组件的状态num传递给
组件作为props,然后作为props传递给
组件,整个过程只能自上而下。单向数据流不是实现前端框架必须遵循的原则。它的存在主要是为了减轻开发者的心理负担,让状态变化后计算影响范围的过程更加可控。双向数据绑定本文开头讲响应式更新的时候,讨论了state和UI的关系。这是从整体上讨论框架,抽象层次比较高。当我们继续谈论单向数据流时,我们谈论的是组件之间状态变化范围的单向传播。这就是组件之间的关系,抽象层次下降了一级。接下来我们要讨论的是双向数据绑定,它讨论了在单个组件中发生的事情。双向数据绑定是一种语法糖,结合了状态+状态变化后触发的回调。这里先不讨论框架上下文中的“语法糖”这个词是否完全准确和比较知名的双向数据绑定的实现,比如Vue中的v-model语法:
相当于下面的状态+事件回调的组合:早已被遗弃。小结我们可以用一张图来概括本文介绍的3个概念之间的关系:总结起来主要有两点:都是前端框架范围内的概念,属于不同层次的概念其中:双向数据绑定描述了组件中逻辑和视图的关系单向数据流描述了组件之间的关系响应式更新描述了状态和UI的关系