首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【Flutter 状态管理 - 壹】 | 提升对声明式编程的认知

  • 25-04-18 23:46
  • 2657
  • 11509
juejin.cn

image.png

前言

每个Flutter开发者都踩过这样的坑:点了按钮没反应,列表滑动像卡帧,debug半天发现少写个setState。你像个救火队员,到处补状态更新 —— 按下葫芦浮起瓢。传统开发逼你既当业务设计师,又得做视图保姆,这种精神分裂该到头了。

Flutter甩来一剂猛药:别告诉我按钮怎么变色,直接说什么时候该红!把界面写成状态的条件表达式,剩下的脏活累活引擎自己包圆。从此告别setState满天飞,你要做的就是定规矩,框架负责执行。当界面成了状态的影子,代码才能回归它该有的样子。

操千曲而后晓声,观千剑而后识器。虐它千百遍方能通晓其真意。

一、命令式编程

想要提升对声明式编程的认知,离不开的一个话题就是命令式编程。从对已有事物的认知过渡到未知事物,做横纵向对比(没有对比就没有伤害)。方能体现新事物的价值。

1.1、本质定义:精准执行力每一步

命令式编程是一种明确告诉计算机"如何做"的编程范式。需要一步步写出执行细节,如同给计算机下达操作指令的微操大师。

就像按照菜谱炒菜一样:好吃的秘诀在于精准的执行每一步操作,否则做出来的菜就难以下咽。

image.png

1.2、核心特性

特性示例场景典型代码表现
逐步指令实现列表排序手写冒泡/快速排序算法
可变状态更新用户界面view.setText(...) view.setVisibility(...)
显式控制流处理业务逻辑for/if/while 等流程控制语句
副作用依赖读写文件/网络请求交替执行的赋值/函数调用

1.3、代码照妖镜:看透命令式的本质

你以为你在写代码?不,你是在当UI的急诊科医生!先来看一个经典Android场景:

java
代码解读
复制代码
// 传统Android写法:每次改UI都像在抢救病人 TextView titleView = findViewById(R.id.tv_title); ImageView iconView = findViewById(R.id.iv_icon); void updateUI(User user) { // 第一步:找到病人(findViewById) titleView.setText(user.name); // 第二步:打针吃药(setText/setVisibility) iconView.setVisibility(user.isVip ? View.VISIBLE : View.GONE); // 第三步:处理并发症(可能的内存泄漏) iconView.setOnClickListener(v -> showVipDialog()); }

这种写法有三大致命伤:

image.png
  • 1、找View找到手抽筋:每次操作都要先findViewById,代码里遍布着R.id.*的魔法数字。
  • 2、状态管理堪比走钢丝:当页面复杂时,你永远不知道某个View是否已经被修改过。
  • 3、内存泄漏重灾区:匿名内部类持有外部引用,稍不留神就埋下炸弹。

更可怕的是动态布局场景:

java
代码解读
复制代码
// 动态添加View的噩梦 LinearLayout container = findViewById(R.id.container); for (int i = 0; i < 100; i++) { TextView tv = new TextView(context); tv.setText("Item " + i); container.addView(tv); // 内存警告:这里可能瞬间创建100个View! }

小结:这种命令式写法就像用镊子组装火箭 —— 每个零件都要亲手拧,效率低还容易出错。


1.4、命令式的双刃剑:优势与软肋

✅ 优势❌ 软肋
1、精细控制: 可精确控制每个对象的状态1、代码膨胀: 简单UI需大量显式操作代码
2、直观易懂: 代码顺序即执行流程,符合直觉2、状态失控: 跨组件状态同步困难,易引发不一致
3、性能调优: 可直接优化关键代码路径3. 维护成本高: 修改UI需手动调整多处关联逻辑

1.5、最后送命题

下次有人跟你说:"声明式编程是未来的唯一方向",请优雅回应:

乌克兰谚语:"你用叉子喝汤吗?不,但叉子依然存在"

编程范式如同餐具:

  • 喝汤用勺子(声明式)
  • 切牛排用刀(命令式)

真正的开发者应当 善用工具,而非迷信工具。


二、声明式编程

2.1、本质定义:用数学函数描述界面

声明式编程是一种通过描述目标状态(What)而非具体步骤(How)来构建界面的编程范式。

在Flutter中,整个UI被抽象为状态(State)的函数,公式可简化为:

image.png

说人话版解释:想象你点外卖

  • 命令式:得告诉小哥先左转再右转,走318步按门铃3下(迟早被当成神经病)。
  • 声明式:直接给地址"北京市朝阳区xx大厦18层",管他骑电动车还是开飞机。

Flutter就是这个外卖平台,Widget树就是你的订单地址。你只管说"要什么",别操心"怎么送" ,这才是程序员该干的活!


2.2、核心特性:三条军规记死了

①、幂等性:说一不二原则

幂等性是指某个操作或函数可以多次执行,但其结果与执行一次相同。换言之,即 相同输入必须输出相同界面,就像你妈喊你全名时,甭管正在打游戏还是拉屎,都得立马回话。

dart
代码解读
复制代码
// 坏代码:今天晴天明天暴雨 Widget buildWeather() { return isSunny ? Sun() : Rain(); // 这个isSunny要是外部变量就完犊子 } // 好代码:老天爷说了算 Widget buildWeather(bool isSunny) { return isSunny ? Sun() : Rain(); }

②、无副作用:别碰我的组件

侧重于使用不可变数据结构及纯函数来处理数据,以避免副作用。换言之,状态变更不会直接修改现有界面元素,而是生成新的描述。

dart
代码解读
复制代码
// 作死写法:直接改旧对象 void updateProfile() { currentUser.name = '王二狗'; // 等着界面装死吧 } // 专业写法:换人换到底 void updateProfile() { userState.value = currentUser.copyWith(name: '王二狗'); }

③、自动同步:框架是你小弟

框架负责将最新的状态描述同步到实际渲染层,别自己吭哧吭哧调setState,把状态往Riverpod/Provider一扔。

dart
代码解读
复制代码
final weatherProvider = StateProvider((ref) => '晴天'); class WeatherScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final weather = ref.watch(weatherProvider); return Text('今天天气:$weather'); } }

2.3、界面开发:别告诉我怎么做,直接说想要啥样?

image.png

见过新手写界面吗?在onPressed里疯狂操作:改文本颜色、调图片尺寸、切组件显隐...代码写成八爪鱼,最后发现漏改了个Container透明度。这就是命令式编程的日常 —— 你既当老板又当小弟,累成狗还容易翻车。

Flutter甩过来一巴掌:把界面写成数学公式会不会?  管他用户怎么点怎么滑,你只要搞清楚:

  • 1、当前这个界面有多少种状态?
  • 2、每个状态对应的界面长啥样?

剩下的交给框架自己算!

举个真代码你细品:

dart
代码解读
复制代码
// 传统命令式:操作具体控件(当保姆) void updateUI(bool isError) { if (isError) { submitButton.style = redStyle; errorText.visible = true; } else { submitButton.style = blueStyle; errorText.visible = false; } } // 声明式:定义状态与界面的映射(当老板) Widget buildButton(bool isError) { return Column( children: [ ElevatedButton( style: isError ? redStyle : blueStyle, onPressed: handleSubmit, child: const Text('提交'), ), if (isError) const Text('出错了老铁!', style: errorStyle) ] ); }

看出门道了吗?声明式编程让你从操作工变成设计师,只定规则不干脏活。


2.4、Widget树的生存法则

image.png

刚学Flutter的新手最困惑:每次都重建整个Widget树,性能不得炸?这就是没吃透Flutter的三棵树:

  • 1、Widget树:轻量级配置描述(你的代码)。
  • 2、Element树:内存中的控件管家(框架维护)。
  • 3、RenderObject树:真正的渲染猛将(GPU打交道)。

举个栗子:你写了十个Text组件

dart
代码解读
复制代码
Column( children: [ Text("张三"), Text("李四"), // ...八个重复Text ] )

当某个Text内容变化时:

  • Widget树全部重建(你的代码层面)。
  • Element树对比新旧Widget,发现只有第三个Text不同。
  • RenderObject树只更新第三个文本的绘制指令。

这才是声明式的精髓:你负责大胆描述,框架负责小心求证。


2.5、声明式编程认知的五重境界:程序员的修真传

image.png

①、第一层:青铜泥潭

在build()方法中堆砌业务逻辑,导致视图与逻辑深度耦合,这种反模式常引发代码维护难题(团队协作中的高危操作)。

dart
代码解读
复制代码
/// 青铜段位 - 反例:视图与逻辑混杂 class BadCounter extends StatelessWidget { @override Widget build(BuildContext context) { int count = 0; // 状态直接定义在build内部 return Scaffold( body: Center( child: InkWell( onTap: () { // 直接在视图层修改状态 count++; print('Current count: $count'); }, child: Text('点击次数: $count'), ), ), ); } }

②、第二层:白银初悟

遵循组件设计规范,合理切分StatelessWidget与StatefulWidget,初步建立响应式编程思维。

dart
代码解读
复制代码
/// 白银段位 - 正例:组件职责分离 class CounterButton extends StatelessWidget { final VoidCallback onPressed; const CounterButton({required this.onPressed}); @override Widget build(BuildContext context) { return ElevatedButton( onPressed: onPressed, child: const Text('增加计数'), ); } } class CounterDisplay extends StatelessWidget { final int count; const CounterDisplay({required this.count}); @override Widget build(BuildContext context) { return Text('当前计数: $count'); } }

③、第三层:黄金通玄

精通状态管理范式,能够基于Provider架构实现跨组件通信,完成复杂业务场景下的状态同步。

dart
代码解读
复制代码
/// 黄金段位 - Provider状态管理 final counterProvider = ChangeNotifierProvider((_) => CounterModel()); class CounterModel with ChangeNotifier { int _count = 0; int get count => _count; void increment() { _count++; notifyListeners(); } } // 使用Consumer消费状态 Consumer( builder: (_, model, __) => Text('全局计数: ${model.count}'), )

④、第四层:钻石窥道

深入框架底层,通过继承InheritedWidget实现定制化状态共享方案,理解Widget与Element的绑定机制。

dart
代码解读
复制代码
/// 钻石段位 - 自定义InheritedWidget class CounterScope extends InheritedWidget { final int count; final VoidCallback increment; CounterScope({ required this.count, required this.increment, required Widget child, }) : super(child: child); static CounterScope? of(BuildContext context) => context.dependOnInheritedWidgetOfExactType(); @override bool updateShouldNotify(CounterScope old) => count != old.count; }

⑤、第五层:王者合道

洞悉框架设计哲学,在视觉层面对Widget树进行拓扑分析时,能同步推演出Element树的动态更新过程,达到人机合一的调试境界。

dart
代码解读
复制代码
/// 王者段位 - 状态驱动UI(伪代码示意) // 定义最小化状态 class _PageState { final counterState = Stateful<int>(0); // 声明式状态容器 final loadingState = Stateful<bool>(false); void _handleRefresh() { loadingState.value = true; // 触发加载指示器重建 fetchData().then((res) { counterState.value = res.count; // 触发计数器重建 loadingState.value = false; // 关闭加载指示器 }); } } // UI仅响应状态变化 Builder((ctx) => [ if (_pageState.loadingState.value) LoadingIndicator(), Text('${_pageState.counterState.value}'), Button(onTap: _pageState._handleRefresh), ]);

核心进阶法则:Flutter声明式架构并非简单的语法糖,而是需要我们建立状态驱动思维。当技术视角从"如何操作界面元素"转换为"如何设计状态拓扑",才标志着真正突破编程范式转型的关键节点。


2.6、用函数式思维降维打击

当你用声明式写界面时,本质上是在做界面代数:

  • 定义变量(状态)。
  • 写出方程(build方法)。
  • 交给Flutter解方程(渲染)。

那些还在手动操作DOM的前端兄弟们,就像拿着算盘解微积分。而你已经用上计算器了 —— 这就是维度差距。下次见到setState手忙脚乱的新手,把这篇拍他脸上:

“ 别动那个按钮!先想清楚你的状态变量!”


三、命令式 vs 声明式:暴力对比表

维度命令式编程声明式编程暴言点评
操作对象具体View实例(findViewById找控件)抽象Widget描述(写蓝图不碰实物)一个在工地搬砖,一个在办公室画图纸✅
​更新方式手动改属性(setText() setVisibility())推倒Widget树重建(框架智能diff)前者像给汽车边跑边换轮胎,后者直接换新车但只改零件⚠️
代码结构过程式代码(先A后B再C)状态映射方程(当X时显示Y)流水线工人 vs 数学老师,维度碾压🔥
思维模式时间轴操作(点击→改数据→找控件→更新)状态空间映射(数据变→界面自动变)前者需要记住所有操作步骤,后者只要定义好对应关系💡
实战场景改完列表项忘记更新详情页状态源一改全家爆炸更新命令式是扫雷游戏,声明式是自动排雷🚩
​性能陷阱频繁findView耗性能Widget树重建但有智能diff你以为右栏更耗性能?框架比你懂优化🚀
​调试难度漏更新时像捉迷藏状态快照直接看时间轴左栏调试像破案,右栏直接看监控录像📸
代码传染性改个需求得满世界找关联代码改状态定义自动波及相关UI前者是病毒传播,后者是精准核爆💥

四、暴言金句总结

  • 1、还在手动setText的兄弟,你代码里藏着的findViewById比我的相亲对象还多!
  • 2、声明式编程就是用数学公式干翻体力活,Widget树就是你的尚方宝剑!
  • 3、Flutter框架比你更懂怎么更新界面 —— 不服跑个分?

学习至此,你应该对声明式编程有了一个深入的认知,接下来,我们将继续深入探索状态管理相关的知识!

欢迎一键四连(关注 + 点赞 + 收藏 + 评论)

注:本文转载自juejin.cn的地狱勇士的文章"https://juejin.cn/post/7491155241192177718"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

140
Android
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top