东营企业网站排名优化包装设计公司哪个好
web/
2025/9/27 14:21:58/
文章来源:
东营企业网站排名优化,包装设计公司哪个好,商城网站平台怎么做的,成都工装设计公司起因
每隔一段时间#xff0c;都会出现一个新的状态管理框架#xff0c;最近在YouTube上也发现了有人在推signals, 一个起源于React的状态管理框架#xff0c;人们总是乐此不疲的发明各种好用或者为了解决特定问题而产生的方案#xff0c;比如Bloc, 工具会推陈出新#x…起因
每隔一段时间都会出现一个新的状态管理框架最近在YouTube上也发现了有人在推signals, 一个起源于React的状态管理框架人们总是乐此不疲的发明各种好用或者为了解决特定问题而产生的方案比如Bloc, 工具会推陈出新新的语法会带来更便捷的方式但原理和优缺点是更重要的一面我们接下来聊聊这一点。
原理
状态管理的起点是值的改变也就是通常代码中的set方法, 状态的终点在Flutter或者其他UI框架中对单个绘制节点进行setDirty来标记一个需要重新绘制节点最后生成新的持续帧来承接新的数据在Flutter中通过setState 来实现。当我们明确了状态管理的起点和终点, 中间对于值的操作和缓存diff算法状态传递状态转移过程等是这些状态管理框架们根据要解决的实际问题来做的权衡trade off。
figure 1 状态管理的空间 需要权衡什么
易用性
在Flutter开发中有一句著名的话你可以不了解Flutter但不能不知Getx, 这点很像Java服务端的spring框架, 提供了一套完备的开发工具也许不是最合适于特定项目但一定合适于简单项目。那Getx的权衡是什么 笔者觉得托管,完备、简单是Getx的最大权衡比如你需要全局使用Getx的组件来包裹所有的Widget这样Getx就可以托管你的程序依赖注入主题屏幕尺寸路由都可以被Getx管理。比如简单的Obx函数将StatefulWidget封装通过全局的NotifyManager来注册管理被捕获的值。
总的来说Getx通过巧妙的架构设计侵入式的托管式框架确实做到了在简单项目中的面向Getx开发。
所以我们知道在状态管理时第一个权衡简单好上手。
与Getx简单相反的是Bloc的复杂, Bloc 提供了全面且复杂的状态管理模式Predictable可预测的以及Bloc的设计模式的结合使框架有很多的模版是的更细致的状态更新管理隔离的状态称为可能, 后面会结合Riverpod来解释这一优势。
状态传递
第一个需要权衡的点是状态转移如果使用原生StatefulWidget当我们Widget数开始庞大起来不可避免的要进行组件的拆分降低维护难度或者出于重用的目的这样的操作势必会进行状态的转移在这个维度就是百花齐放了Getx和Rivierpod是将依赖管理和状态相结合通过全局的状态声明和状态获取来达到这个目的例如
/// 声明并加入到全局Scope
riverpod
class DayChecked extends _$DayChecked {overrideDateTime build() {return DateTime.now();}
}
/// 任意地方使用
final focusedDay ref.watch(dayCheckedProvider); // 读取
ref.read(dayCheckedProvider.notifier).set(focusedDay) // 修改// Getx用法
class ScheduleState {RxDateTime focusedDay DateTime.now().obs;
}
class ScheduleLogic extends GetxController {final ScheduleState state ScheduleState();
}
// 使用
Get.findScheduleLogic().state.focusedDay全局的状态管理易用程度是中等的Getx和flutter_hooks 都提供了局部的状态声明这在Widget内部状态处理时更方便的hook的概念起源于React状态管理相对于Getx是一种更轻量的解决方案稍后我们提及其他方面的优势。我们可以认为Hook更适合内部状态变化。
全局的状态管理带来一个较为严重的问题虽然可以通过组合别的方式来解决但确实不够完备。
例如TodoList中任务的子父级关系的虽然是同一个页面使用同一个状态管理类但状态应该是独自的全局的状态管理解决这个问题的方法是通过入参这种形式例如Getx中Get.find(tag)和riverpod的build(params)来生成多个状态管理. // 使用参数化区分不同状态logic Get.findTaskDetailLogic(tag: task.id);ref.watch(taskDetailProvider(task.id));这样会带来一个比较棘手的问题我们必须将logic或者参数tag一级一级的传递或者通过callback一级一级的回传操作来保证状态的正确读取和修改这点在复杂项目当中是很棘手的当锚定的入参过多时我们会考虑父子Widget传递logic和state保证状态的正确引用但这丧失了灵活性和Widget重用的范围。
这种情况下在全局状态和Widget内部状态中间需要一种状态管理, 局部状态管理的概念就能够很好的解决这个问题Bloc是这种方式的代表通过BlocProvider提供一个局部, 被挂载到局部的状态可以被context.readSignInBloc()获取相关联的状态。riverpod也能做到局部状态只是他的设计初衷并不是如此所以使用局部状态时会显得很蹩脚分离context使用ref, 也导致官方并不推荐使用局部状态的方式这将会造成混乱。
figure 2 状态范围Scope 状态操作
使用Riverpod的同学会特别喜欢Flutter Hook, 因为riverpod对状态的声明和操作是分离的尽管代码上他们封装在一起但我们知道这是OOP的写法, 便于理解因为在一些语言中方法和状态是分离的对象中内聚函数第一个参数是Self, 同样生成式的Riverpod通过另一种方式实现了分离。如下
internal
abstract class NotifierBaseState {NotifierProviderElementNotifierBaseState, State get _element;protectedvisibleForTestingState get state {_element.flush();return _element.requireState;}protectedvisibleForTestingset state(State value) {// ignore: invalid_use_of_protected_member_element.setState(value);}protectedRefState get ref;我们暂时关心如何通过设计达到这个目的我们需要关心的是状态的操作是内部的也就是被限制的在ReactHook中类似于[set, get], 返回的值并没有直接修改的能力相较于Getx的 1.obs和 flutter hook的useState(1)是一个更加内聚的设计但丧失了灵活性两者需要在不同场景下结合使用。
状态的声明有两种基于Stream或者Listenable两者都是响应式中重要的组成部分不同的状态管理框架会权衡选择或者组合使用例如riverpod是基于Listenable, Bloc基于Stream, Getx则是混合使用的我们大部分情况下不需要考虑状态的声明, 状态的修改是更影响开发体验例如我们使用riverpod必须使用模版方法改变内部的值然后借用freezed生成不可变对象, 保证值被完整的更新。或者实现equal方法主动进行diff并通知值的变化。例如blocfreezed
freezed
class SignInState with _$SignInState {const factory SignInState({String? password,required OptionString passwordError,}) _SignInState;factory SignInState.initial() SignInState(passwordError: none(),);
}
class SignInBloc extends BlocSignInEvent, SignInState {SignInBloc() : super(SignInState.initial()) {onSignInEvent((event, emit) async {await event.map(passwordChanged: (PasswordChanged value) {emit(state.copyWith(password: value.password,emailError: none(),),);},);});}
}这种状态的修改对简单状态修改来说比较冗余对层级复杂状态的修改freezed就会很累赘试想一下一个三级以上的嵌套, 例如
freezed
class Task with _$Task {const factory Task({String? id,Alert? alert,}) _Task;
}freezed
class Alert with _$Alert {const factory Alert({String? id,Trigger? trigger,}) _Alert;
}freezed
class Trigger with _$Trigger {const factory Trigger({String? id,required DateTime time,}) _Trigger;
}riverpod
class TaskDetail extends _$TaskDetail {overrideTask build() {return Task();}/// 修改其中的一个值changeTrigger(DateTime dateTime) {state state.copyWith(alert: state.alert?.copyWith(trigger: state.alert?.trigger?.copyWith(time: dateTime)));}
}这是一个很常见的场景在riverpod的官方文档中我们可以看到将网络请求作为状态返回。这种在实际的复杂项目中是极其不推荐使用freezed或者说引入不可变性, 这会导致灾难虽然可以使用unfreezed放开部分权限但这违背了freezed的设计初衷。多层级且要修改 的Http请求并不适合作为状态返回我们的状态管理需要针对是ViewModel, 这种情况下一般解决方案会有两种。
主动通知 ref.notifyListener将多层级数据结构展开(flat)
这取决于对数据操作的频率和数据字段的难度需要在实际使用当中进行权衡需要额外关注的是如果我们需要记录状态的变化则freezed的不可变性会是更好的解决方案需要优先考虑。
第二个常见的场景是需要持久化存储的时候, 不可变性限制了很多比如不能修改内部变量不能添加方法可以通过extension不能够使用继承, 例如 Hive 的Object就不能被继承来实现更改当然因为不可变的特性也无法修改。
总的来说在Getx中模版中的state概念类似ViewModel的载体内部是单独的小的state, 我们可以直接监听小的state这也是Getx很灵活的一点。其他的框架则更多偏向于state整体的管理和更新bloc中cubit很像但需要太多模版代码freezed的限制和使用可以参考我的另一篇文章不再赘述因为这种生成式或者将来添加的宏都会遇到类似的问题所以多聊一点。
状态的结束
riverpod在官网提及自己的优势是自动生命周期管理。这点确实是状态管理的一个难点也是开发者经常会忽略的一个点当我们使用StatefulWidget时我们并不需要考虑这个问题因为state随着Widget结束而释放但我们使用Stream或者Listenable时如何释放变成了一个棘手的问题flutter hook通过包装的Element, LinkedList_EntryHookStateObject?, HookObject?? _needDispose;, 调用use时加入dispose列表。riverpod通过侵入Widget树通过Element监听生命周期并管理依赖。 protectedmustCallSupervoid dispose() {runOnDispose(); // 魔法for (final sub in _dependencies.entries) {sub.key._providerDependents.remove(this);sub.key._onRemoveListener();}_dependencies.clear();_externalDependents.clear();}其中runOnDispose() 就是riverpod的魔法, 可以通过他来自动取消Http请求的UI相关的耗时作业。 对于状态结束的监听和操作每个框架的实现细节不一致但本质上却是要回归到Element 的生命周期函数以及针对Stream和Listenable两种方式的Dispose的处理。
状态依赖和传递
我们大部分时间大部分需求都是在处理简单任务例如将数据库数据或服务接口整合然后布局填充样式最终渲染到页面。简单任务通常是简单的流程这也是为什么官方更推荐riverpod的原因因为它更契合这种场景我们不需要做业务层级的分离只需要在某个地方声明某个网络请求provider然后监听结果或者修改结果。这是非常理想的情况稍微复杂的情况就需要状态之间的依赖和传递了例如这个场景 这个场景下任务列表是依赖于选中日期的不同的状态处理框架提供了不同的解决方法虽然本质上都是对Stream和Listenable的监听但对于状态的依赖管理和易用性是有很大差别的Getx的方式会比较普通扩展了Stream的函数但仍需要主动Listen, riverpod的写法更巧妙在语法上有很大的改善比如当前这个场景。我们如果使用riverpod, 代码如下
Riverpod(dependencies: [DayChecked, TaskList])
class DayTasks extends _$DayTasks {overrideFutureListTaskModel build() async {final checkedDay ref.watch(dayCheckedProvider);// get task by checkDayretunr await serivce.get(checkedDay);}
}区别于flutter_hook的函数是不明确的例如实现相同功能可能需要如下代码 final checkedDate useRef(DateTime.now());final taskList useState(TaskModel[]);useEffect(() {taskList.value service.get(checkedDate);}, [checkedDate.value]);对于状态的传递方程式的传递是相对优雅的在数学上我们知道y 2x, 或者 z 3x 2y 1 这样的简单等式 能够优雅的以纯函数式来实现状态的转移是极其优雅的对于这样的依赖传递形式的状态转移方程riverpod的实现要优雅且严谨很多更不容易出错。设想一个场景用户是否为VIP 这是一个简单状态大多数情况下比用户信息更频繁的去读取这个字段也需要被设计到用户信息接口如果我们对UI层只提供简单的VIP状态我们可以使用如下写法。
Riverpod()
class UserConifg extends _$UserConifg {overrideUserModel build() {return UserModel(avatar: , id: 1, nickName: nickName, pay: true);}
}Riverpod(dependencies: [UserConifg])
class Vip extends _$Vip {overridebool build() {final user ref.watch(userConifgProvider);return user.pay;}
}
上述方式体现了这种方式的优雅我们甚至可以将UserConifg放到别的包下将Vip状态和UI关联这种方式优雅的结构了复杂场景下的状态转移和高内聚低耦合的原则。回到线性方程它也符合y 2x代数方程的思维。
总结
任何领域都大概率都没有银弹, 软件开发领域也是如此我们创造工具使用工具改进工具才有软件的繁荣。不一定非要讨论那个框架或技术有高低差异。在实际开发中稳定熟悉是稳定三角的另外两个重要的方面不同的框架的缺点总会有一些或者优雅或者败絮其中的解决方案在项目中最重要的适合合适的工具会让我们开发过程事半功倍其次是稳定性和学习难度, 不过一切都需要合适的权衡trade off动态的去匹配当下最重要的事。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/82810.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!