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

前言

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

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

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

一、命令式编程

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

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

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

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


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());
}

这种写法有三大致命伤

  • 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)的函数,公式可简化为:

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

  • 命令式 :得告诉小哥先左转再右转,走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、界面开发:别告诉我怎么做,直接说想要啥样?

见过新手写界面吗?在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树的生存法则

刚学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、声明式编程认知的五重境界:程序员的修真传

①、第一层:青铜泥潭

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'),
        ),
      ),
    );
  }
}

②、第二层:白银初悟

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

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<CounterModel>(
  builder: (_, model, __) => Text('全局计数: ${model.count}'),
)

④、第四层:钻石窥道

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

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<CounterScope>();

  @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 前者像给汽车边跑边换轮胎,后者直接换新车但只改零件⚠️
代码结构 过程式代码(先ABC 状态映射方程(当X时显示Y 流水线工人 vs 数学老师,维度碾压🔥
思维模式 时间轴操作(点击→改数据→找控件→更新) 状态空间映射(数据变→界面自动变) 前者需要记住所有操作步骤,后者只要定义好对应关系💡
实战场景 改完列表项忘记更新详情页 状态源一改全家爆炸更新 命令式是扫雷游戏,声明式是自动排雷🚩
性能陷阱 频繁findView耗性能 Widget树重建但有智能diff 你以为右栏更耗性能?框架比你懂优化🚀
调试难度 漏更新时像捉迷藏 状态快照直接看时间轴 左栏调试像破案,右栏直接看监控录像📸
代码传染性 改个需求得满世界找关联代码 改状态定义自动波及相关UI 前者是病毒传播,后者是精准核爆💥

四、暴言金句总结

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

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

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

相关推荐
getapi41 分钟前
flutter底部导航代码解释
前端·javascript·flutter
初遇你时动了情43 分钟前
安装fvm可以让电脑同时管理多个版本的flutter、flutter常用命令、vscode连接模拟器
flutter
QING6183 小时前
详解:Kotlin 类的继承与方法重载
android·kotlin·app
QING6183 小时前
Kotlin 伴生对象(Companion Object)详解 —— 使用指南
android·kotlin·app
一一Null3 小时前
Android studio 动态布局
android·java·android studio
AD钙奶-lalala11 小时前
某车企面试备忘
android
我爱拉臭臭11 小时前
kotlin音乐app之自定义点击缩放组件Shrink Layout
android·java·kotlin
匹马夕阳12 小时前
(二十五)安卓开发一个完整的登录页面-支持密码登录和手机验证码登录
android·智能手机
吃饭了呀呀呀13 小时前
🐳 深度解析:Android 下拉选择控件优化方案——NiceSpinner 实践指南
android·java
吃饭了呀呀呀13 小时前
🐳 《Android》 安卓开发教程 - 三级地区联动
android·java·后端