1.【Rematch 源码系列】四、Third-Party plugins
2.JS 不可变数据踩坑, immer 不是最终出路,高性能场景还需自己实现
3.Immutable.js与React,Redux及reselect的实践
4.不可变数据实现-Immer.js
5.在Immutable.js中如何实现撤销重做功能(详细教程)
6.js副作用是什么意思
【Rematch 源码系列】四、Third-Party plugins
本文深入探讨了rematch的两个常用第三方插件:immer与loading。immer插件旨在简化state的在线留言框源码修改过程,通过引入immerjs,允许开发者在reducer中使用mutable状态,进而生成immutable状态,简化了常规操作。immer插件的实现相对简单,只需将常规reducer包裹一层,使之通过immerjs处理即可。
immer插件的核心在于其对reducer的封装,通过immer.produce方法处理draft状态,简化了mutable状态的管理,避免了复杂的clone和赋值操作。当状态为简单数据类型时,不会使用immer.produce,以保持代码的简洁性。更多关于immer.produce和combineReducers的使用和原理可参考官方文档。
然而,immer插件的设计存在缺陷,即许多reducer配置若不能以数组形式存储,而是被替换,则可能导致插件配置失效。rematch v2版本通过引入更细粒度的plugin hooks(如onReducer)解决了这一问题,提升了配置的灵活性。
紧接着是loading插件,专注于管理异步操作的状态,包括网络请求等。其核心在于onModel钩子的使用,定义了全局和模型级别的loading状态,并为特定操作定义了show和hide两个reducer,动态跟踪和控制加载状态。
loading插件的实现通过初始化代码定义了全局和模型级别的loading状态,并使用onModel钩子处理模型操作,xsplit 源码对特定的effect动作进行管理,包装原始动作以实现状态控制。两个reducer,show和hide,分别用于增加和减少操作状态的计数,以此实现对加载状态的动态更新。
本文综述了rematch的immer和loading插件的实现原理、使用场景及优化策略,为开发者提供了深入理解这些工具的框架。后续文章将探讨rematch v1升级到v2的设计变化以及TypeScript支持的实现,期待与开发者共同探索rematch的最新进展和优化。
JS 不可变数据踩坑, immer 不是最终出路,高性能场景还需自己实现
不可变数据在函数式编程中扮演关键角色,尤其在React和Redux中。随之出现了如immutable.js、immer、immutability-helper等库来操作不可变数据。不可变数据的特性在于一旦创建,不可更改,每次操作都会生成新的对象,避免了深层拷贝的性能损耗,利用结构共享机制,只修改影响的节点,其他共享节点保持不变,这降低了复杂度,方便实现撤销、重做、时间旅行功能。在React中,state是不可变的,不允许直接修改,只能通过setState返回新的state。immutable.js提供持久化的不可变数据结构,简化了操作流程,配合React.PureComponent实现浅比较,地址源码提高应用性能。常用数据结构包括List、Map、OrderedMap、Set、OrderedSet和Record等。然而,immutable.js在大型应用中类型支持不佳,被ts类型系统排斥,如操作后数据类型变为any,带来困扰。immer的出现作为mobx的基础操作库,以新颖方式简化不可变数据处理,通过draft草稿机制,开发者可像操作普通对象一样修改数据,同时保留不可变数据的所有优点。然而,immer在极端情况下性能不如普通浅拷贝,特别是在大量数据修改时。作者提出了优化策略,包括关闭Object.freeze、去除特定场景实现以及性能测试,最终实现了自定义的immer拷贝,通过类型安全、简单设计,提供与immutable-js相似的功能,体积小,兼容性强,适合体积要求高的项目。整体而言,不可变数据提供了高性能和易于维护的解决方案,但具体选择库还需根据项目需求和性能要求综合考虑。
Immutable.js与React,Redux及reselect的实践
欢迎访问我的个人博客- Immutable.js与React,Redux及reselect的实践
本篇文章将聚焦Immutable与Redux:包括什么是Immutable,为什么需要使用Immutable,Immutable.js与React,Redux及reselect的BThave源码组合实践及优化,最后总结使用Immutable可能遇到的一些问题及解决方式。
什么是Immutable?
Immutable来自于函数式编程的世界,可以称它为不可变。在编程中,我们经常需要修改数据,但Immutable提倡在修改数据时,创建新的对象,而不是改变原始对象。这样可以避免一些常见的编程问题,如:引用检查和值检查,提高代码的可预测性和可维护性。
为什么需要使用Immutable?
在React中,每次组件的props或状态改变时,React都会重新渲染组件,这在复杂的应用中可能会导致性能问题。而Immutable提供了一种方式,使得我们可以在不影响原始数据的情况下,改变数据结构。这样可以提高组件的性能,减少不必要的渲染。
使用Immutable的实践
在实际的开发中,我们可以将JavaScript对象转换为Immutable对象,然后在需要修改数据时,使用Immutable提供的API进行操作。在React中,我们可以将Immutable对象传递给组件的props,而不需要担心组件在渲染时会重新创建新的数据副本。
如何使用Immutable?
目前已经有了一些与React集成的库,如Immutable.js。我们可以通过使用Immutable.js来创建不可变的对象,并在需要修改数据时,使用Immutable.js提供的API。例如,我们可以使用Immutable.js的map()、reduce()等方法来创建React组件的子元素。
Immutable与Redux的flsah源码协作
在使用Redux进行状态管理时,我们可以将状态树表示为一个不可变的对象。这样,我们可以使用Immutable.js提供的API来修改状态树,而不会影响到原始的状态对象。这样可以提高状态管理的效率,减少不必要的状态更新。
使用Immutable的优化
在使用Immutable时,我们需要注意一些细节。例如,我们可以在需要修改数据时,使用Immutable.js的API来创建新的对象,而不是直接修改原始对象。这样可以避免一些性能问题。另外,我们还可以使用reselect库来缓存一些选择器的计算结果,避免不必要的计算。
在实际的开发中,我们需要注意一些问题,例如:Immutable与JavaScript对象之间的协作问题,Immutable的强依赖性,以及在使用Immutable时可能会遇到的性能问题等。在使用Immutable时,我们应该尽量避免这些问题,发挥其带来的优势。
不可变数据实现-Immer.js
不可变数据实现-Immer.js
Immer.js是mobx作者开发的JavaScript不可变数据库,因其在年荣获JavaScript Open Source Award而备受关注。它巧妙利用ES6的proxy机制,以极低成本实现不可变数据结构,轻量且易于使用。它能满足我们在JavaScript中对不可变数据结构的需求。 不可变数据,即函数式编程中的概念,不允许对初始化数据进行更改,每次修改都会创建新的数据副本,确保数据独立性。JavaScript原生不支持,需通过库如Immer.js或Immutable.js来实现。比如,React利用不可变数据加速diff算法,仅需检查对象索引变化即可判断是否更新。 追求不可变数据的原因在于,它能简化数据比较,减少组件的重新渲染,如在React的生命周期函数中,只对状态值有变化的部分进行更新,避免不必要的性能开销。 实现不可变数据的方法有多种,如深拷贝,但成本高且处理复杂。Immutable.js虽然强大,但Immer.js由于其轻量级和灵活性,相比而言更受欢迎。 Immer.js的核心API是produce函数,它接收当前状态和修改逻辑,创建临时的draftState,完成所有变更后生成新的nextState。produce有多种形式,如直接调用或作为高阶函数返回。其核心是利用proxy实现数据的不可变,通过拦截读写操作,确保数据的修改不会影响原始状态。 在React项目中,use-Immer Hook提供了将Immer集成到组件的便捷方式,与useState类似,简化了状态管理。 总结Immer.js,它具有轻量、高效和易于集成的优点,尤其在性能优化方面表现优秀。然而,如果需要兼容性或更全面的功能,大型库如lodash可能是更好的选择。总之,Immer.js是不可变数据实现的一个理想选择。在Immutable.js中如何实现撤销重做功能(详细教程)
这篇文章主要介绍了基于 Immutable.js 实现撤销重做功能及一些需要注意的地方,需要的朋友可以参考下
浏览器的功能越来越强大,许多原来由其他客户端提供的功能渐渐转移到了前端,前端应用也越来越复杂。许多前端应用,尤其是一些在线编辑软件,运行时需要不断处理用户的交互,提供了撤消重做功能来保证交互的流畅性。不过为一个应用实现撤销重做功能并不是一件容易的事情。 Redux官方文档中 介绍了如何在 redux 应用中实现撤销重做功能。基于 redux 的撤销功能是一个自顶向下的方案:引入 redux-undo 之后所有的操作都变为了「可撤销的」,然后我们不断修改其配置使得撤销功能变得越来越好用(这也是
redux-undo 有那么多配置项 的原因)。
本文将采用自底向上的思路,以一个简易的在线画图工具为例子,使用TypeScript 、 Immutable.js 实现一个实用的「撤消重做」功能。大致效果如下图所示:
第一步:确定哪些状态需要历史记录,创建自定义的 State 类
并非所有的状态都需要历史记录。许多状态是非常琐碎的,尤其是一些与鼠标或者键盘交互相关的状态,例如在画图工具中拖拽一个图形时我们需要设置一个「正在进行拖拽」的标记,页面会根据该标记显示对应的拖拽提示,显然该拖拽标记不应该出现在历史记录中;而另一些状态无法被撤销或是不需要被撤销,例如网页窗口大小,向后台发送过的请求列表等。
排除那些不需要历史记录的状态,我们将剩下的状态用 Immutable Record 封装起来,并定义 State 类:
这里我们的例子是一个简易的在线画图工具,所以上面的 State 类中包含了三个字段,items 用来记录已经绘制的图形,transform 用来记录画板的平移和缩放状态,selection 则表示目前选中的图形的 ID。而画图工具中的其他状态,例如图形绘制预览,自动对齐配置,操作提示文本等,则没有放在 State 类中。
第二步:定义 Action 基类,并为每种不同的操作创建对应的 Action 子类
与 redux-undo 不同的是,我们仍然采用 命令模式 :定义基类 Action,所有对 State 的操作都被封装为一个 Action 的实例;定义若干 Action 的子类,对应于不同类型的操作。
在 TypeScript 中,Action 基类用 Abstract Class 来定义比较方便。
Action 对象的 next 方法用来计算「下一个状态」,prev 方法用来计算「上一个状态」。getMessage 方法用来获取 Action 对象的简短描述。通过 getMessage 方法,我们可以将用户的操作记录显示在页面上,让用户更方便地了解最近发生了什么。prepare 方法用来在 Action 第一次被应用之前,使其「准备好」,AppHistory 的定义在本文后面会给出。
Action 子类举例
下面的 AddItemAction 是一个典型的 Action 子类,用于表达「添加一个新的图形」。
运行时行为
应用运行时,用户交互产生一个 Action 流,每次产生 Action 对象时,我们调用该对象的 next 方法来计算后一个状态,然后将该 action 保存到一个列表中以备后用;用户进行撤销操作时,我们从 action 列表中取出最近一个 Action 并调用其 prev 方法。应用运行时,next/prev 方法被调用的情况大致如下:
为了方便后面的说明,我们对 Applied-Action 进行一个简单的定义:Applied-Action 是指那些操作结果已经反映在当前应用状态中的 action;当 action 的 next 方法执行时,该 action 变为 applied;当 prev 方法被执行时,该 action 变为 unapplied。
第三步:创建历史记录容器 AppHistory
前面的 State 类用于表示某个时刻应用的状态,接下来我们定义 AppHistory 类用来表示应用的历史记录。同样的,我们仍然使用 Immutable Record 来定义历史记录。其中 state 字段用来表达当前的应用状态,list 字段用来存放所有的 action,而 index 字段用来记录最近的 applied-action 的下标。应用的历史状态可以通过 undo/redo 方法计算得到。apply 方法用来向 AppHistory 中添加并执行具体的 Action。具体代码如下:
第四步:添加「撤销重做」功能
假设应用中的其他代码已经将网页上的交互转换为了一系列的 Action 对象,那么给应用添上「撤销重做」功能的大致代码如下:
第五步:合并 Action,完善用户交互体验
通过上面这四个步骤,画图工具拥有了撤消重做功能,但是该功能用户体验并不好。在画图工具中拖动一个图形时,MoveItemAction 的产生频率和 mousemove 事件的发生频率相同,如果我们不对该情况进行处理,MoveItemAction 马上会污染整个历史记录。我们需要合并那些频率过高的 action,使得每个被记录下来的 action 有合理的撤销粒度。
每个 Action 在被应用之前,其 prepare 方法都会被调用,我们可以在 prepare 方法中对历史记录进行修改。例如,对于 MoveItemAction,我们判断上一个 action 是否和当前 action 属于同一次移动操作,然后来决定在应用当前 action 之前是否移除上一个 action。代码如下:
从上面的代码中可以看到,prepare 方法除了使 action 自身准备好之外,它还可以让历史记录准备好。不同的 Action 类型有不同的合并规则,为每种 Action 实现合理的 prepare 函数之后,撤消重做功能的用户体验能够大大提升。
一些其他需要注意的地方
撤销重做功能是非常依赖于不可变性的,一个 Action 对象在放入 AppHistory.list 之后,其所引用的对象都应该是不可变的。如果 action 所引用的对象发生了变化,那么在后续撤销时可能发生错误。本方案中,为了方便记录操作发生时的一些必要信息,Action 对象的 prepare 方法中允许出现原地修改操作,但是 prepare 方法只会在 action 被放入历史记录之前调用一次,action 一旦进入纪录列表就是不可变的了。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
vue页面离开后执行函数的实例
vue轮播图插件vue-concise-slider的使用
vue加载自定义的js文件方法
js副作用是什么意思
在编写JavaScript代码时,我们经常会遇到“副作用”这个术语,那么这个术语的含义是什么呢?在简单的术语中,副作用是指函数除了返回值外,对其他的部分产生的影响,这种影响可能包括修改变量、在页面上呈现数据等等。这种副作用给代码的编写和调试带来了许多困难。 作为一种面向对象的语言,JavaScript存在几种不同类型的副作用,比如改变函数外的变量、调用其他的函数、发出网络请求等等。这种多样化的副作用使得JavaScript难以理解和调试,但同时也增加了代码的灵活性和复用性。因此,我们需要充分理解副作用的本质,以便更好地编写高质量的JavaScript代码。 为了解决框架和类库中的副作用问题,一些流行的解决方案已被开发并广泛采用。比如,React和VueJS框架中采用了“单向数据流”原则,以确保组件对外部环境的影响最小化。借助Immutable.js和Ramda等库,我们可以避免修改数据结构和变量,并充分利用函数式编程的优势。这些工具和技术能够帮助我们减少编写和维护过程中出现的错误和问题,为我们提供更加健壮的代码。