Flutter鸿蒙开发指南(二):组件类型与状态管理

前言

前面学习了Flutter的组件化。这篇文章学习无状态组件和有状态组件的区别,为后面的项目开发巩固基础。前面的基础章节都打算采用Vscode 进行编写,后续可能会改为Android Studio

一、一个"失灵"的计数器

1.1 计数器效果

以下代码采用组件化的形式展示。这是一个计数器的案例:如果点击悬浮按钮的+号应该会进行自增,从99->100->101。但是点击完后发现并没有进行自增。为了弄清楚程序是否执行了自增,我们添加了这段代码在DevEco Studio的控制台进行查看。在DevEco Studio的log可以看见,程序确实已经执行了,但是UI为什么没有进行更新呢?这涉及到Flutter的两大组件特性:

无状态组件和有状态组件

无状态组件:StatelessWidget

有状态组件:StatefulWidget

Dart 复制代码
   print('今年$age岁');

1.2 计数器代码

main.dart代码:

Dart 复制代码
import 'package:flutter/material.dart';
import 'package:wan_android/components/01.%E6%8A%BD%E7%A6%BB%E7%BB%84%E4%BB%B6.dart';
import 'package:wan_android/components/02.%E6%97%A0%E7%8A%B6%E6%80%81%E7%BB%84%E4%BB%B6.dart';


void main() {
  runApp(MaterialApp(
    home: MyApp2(age:99),
  ));
}

MyApp2.dart代码:

Dart 复制代码
// ignore_for_file: must_be_immutable

import 'package:flutter/material.dart';

//快捷键:stless
class MyApp2 extends StatelessWidget {
  MyApp2({super.key, this.age});

  int? age;
  final String name = '张三';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //导航条
      appBar: AppBar(
        title: const Text('无状态组件',
            style: TextStyle(color: Colors.white, fontSize: 18)),
        backgroundColor: Colors.pink,
        centerTitle: true,
      ),
      //body一般放Container()
      body: Center(
        child: Text('我叫$name,今年$age岁', style: TextStyle(fontSize: 30)),
      ),
      //floatingActionButton:浮动按钮
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          age = age! + 1;
          print('今年$age岁');
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

二、无状态组件:StatelessWidget

2.1 什么是StatelessWidget?

我们上面1.1计数器的效果就是使用了StatelessWidget。如果你还不能够理解,那么我举个具体的案例帮助你理解。

**一句话定义StatelessWidget:**StatelessWidget就像一个静态的照片------拍完照片是什么样子,就永远是什么样子,不会自己变化。

Dart 复制代码
// 最简单的StatelessWidget示例
class WelcomeText extends StatelessWidget {
  const WelcomeText({super.key});

  @override
  Widget build(BuildContext context) {
    return const Text('欢迎来到鸿蒙Flutter开发!');
  }
}

2.2 三大核心特征

特征1:不可变性(所有属性必须是final)

Dart 复制代码
// ✅ 正确写法:属性都是final
class UserCard extends StatelessWidget {
  final String name;  // final:不可变
  final int age;      // final:不可变
  
  const UserCard({super.key, required this.name, required this.age});
  
  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: [
          Text('姓名: $name'),
          Text('年龄: $age'),
        ],
      ),
    );
  }
}

// ❌ 错误写法:属性不是final
class BadUserCard extends StatelessWidget {
  String name;  // 不是final → 编译警告
  int age;      // 不是final → 编译警告
  // 会看到提示:'name' should be final

特征2纯函数式(相同的输入,相同的输出)

Dart 复制代码
// 无论调用多少次,相同参数得到相同结果
UserCard(name: '张三', age: 20)  // 总是显示"张三, 20岁"
UserCard(name: '张三', age: 20)  // 再次调用,还是"张三, 20岁"
UserCard(name: '张三', age: 20)  // 永远都是"张三, 20岁"

特征3:依赖外部数据(自己不保存状态)

Dart 复制代码
// StatelessWidget就像餐厅的菜单
// 厨房(父组件)做什么菜,菜单就显示什么
class MenuItem extends StatelessWidget {
  final String dishName;      // 菜名来自厨房
  final double price;         // 价格来自厨房
  final bool isAvailable;     // 是否可点来自厨房
  
  const MenuItem({
    super.key,
    required this.dishName,
    required this.price,
    required this.isAvailable,
  });
  
  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(dishName),
      subtitle: Text('¥$price'),
      enabled: isAvailable,
    );
  }
}

2.3 为什么刚才的计数器会"失灵"

让我们回头看计数器的问题代码:

Dart 复制代码
class MyApp2 extends StatelessWidget {
  MyApp2({super.key, this.age});
  
  int? age;  // ❌ 问题1:不是final
  final String name = '张三';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          age = age! + 1;  // ❌ 问题2:试图修改属性
          print('今年$age岁');
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

根本原因:

**(1)**StatelessWidget被设计的初衷就是不可变的。

**(2)**即使修改了age的值,Flutter也不会重新调用build()方法。

**(3)**没有build()的重新执行,UI就不会进行更新

打个比方:

就像你想更新一张纸质照片的内容。你在照片反面上写"age+1",但是照片正面的画面依旧不会改变,要想改变照片正面的画面,必须重新拍一张照片。(重新执行build()方法)

使用场景:没有状态改变的Widget,通常这种Widget仅仅是做一些展示工作,如发送网络请求,拿到数据,进行渲染。

那么上面计数器的UI不能被更新吗?答案是能的,使用有状态组件StatefulWidget即可。

三、有状态组件:StatefulWidget

3.1 什么是StatefulWidget?

让我们回顾一下刚刚的问题:

无状态组件程序输出了,但是UI没进行更新,那么我要更新怎么更新?

答:使用StatefulWidget有组件状态进行更新。

StatelessWidget就像一张静态照片,拍完就固定了。

StatefulWidget则像一段动态视频,可以记录变化的过程。

Dart 复制代码
// 需求:点击按钮,数字要实时更新
// ❌ StatelessWidget做不到:修改数据,UI不更新
// ✅ StatefulWidget能做到:修改数据,UI同步更新

接下来,让我们一起使用Statefulwidget来修正这个计数器。

3.2 setState():让UI动起来的魔法

main.dart代码:

Dart 复制代码
import 'package:flutter/material.dart';
import 'package:wan_android/components/03.%E6%9C%89%E7%8A%B6%E6%80%81%E7%BB%84%E4%BB%B6.dart';
import 

void main() {
  runApp(MaterialApp(
      home: MyApp3();
  ));
}

MyApp3.dart代码:

Dart 复制代码
import 'package:flutter/material.dart';

//有状态组件:stful 命名遵循大驼峰命名法
class MyApp3 extends StatefulWidget {
  const MyApp3({super.key});

  @override
  State<MyApp3> createState() => _MyApp3State();
}

//以下划线 _ 开头的类名、变量名或方法名,表示它是"库私有"的。
class _MyApp3State extends State<MyApp3> {
  //状态变量(数据变化,视图会更新)
  String name = '张三';
  int age = 20;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //导航条
      appBar: AppBar(
        title: const Text('无状态组件',
            style: TextStyle(color: Colors.white, fontSize: 18)),
        backgroundColor: Colors.pink,
        centerTitle: true,
      ),
      //body一般放Container()
      body: Center(
        child: Text('我叫$name,今年$age岁', style: TextStyle(fontSize: 30)),
      ),
      //floatingActionButton:浮动按钮
      floatingActionButton: FloatingActionButton(
        onPressed: () {
        
               age = age + 1;
          print('今年$age岁');
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

运行效果:点击悬浮按钮的+号,你会发现UI依然没有进行更改。为什么呢?因为没有使用setState()方法包裹。

为什么需要setState()?因为我们需要一种方式告诉Flutter:"状态已经改变了,请根据新的状态重新构建UI。"SetState方法会标记该State对象为"脏"的,然后在下一帧中,Flutter会重新执行该State对象的build方法,从而更新UI。

使用SetState()方法包裹住"age = age + 1;修改MyApp3.dart代码:

Dart 复制代码
    setState(() {
             age = age + 1;
          });

点击+号,你会发现UI可以进行正常更新了。

3.3 setState()的作用

(1)更新状态变量的值

(2)标记这个Widget需要进行重建【执行build()方法】

(3)触发build()方法重新执行

(4)Flutter用新的状态重新绘制UI

四、结语

本次文章到此结束,感谢大家的观看,如果有错误或者更好的建议,可以在评论区指出。这篇文章带大家认识了无状态组件和有状态组件的区别。希望大家可以通过代码实践加以领悟,"纸上谈兵终觉浅",还需要多敲代码,加深印象。

欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net

相关推荐
程序员老刘·6 小时前
Android Studio Otter 3 发布:日常开发选AS还是Cursor?
flutter·android studio·ai编程·跨平台开发·客户端开发
浩辉_6 小时前
Dart - 内存管理与垃圾回收(GC)深度解析
flutter·dart
一起养小猫8 小时前
Flutter for OpenHarmony 实战:记忆棋游戏完整开发指南
flutter·游戏·harmonyos
Betelgeuse7610 小时前
【Flutter For OpenHarmony】TechHub技术资讯界面开发
flutter·ui·华为·交互·harmonyos
铅笔侠_小龙虾10 小时前
Flutter 安装&配置
flutter
mocoding11 小时前
使用已经完成鸿蒙化适配的Flutter本地持久化存储三方库shared_preferences让你的应用能够保存用户偏好设置、缓存数据等
flutter·华为·harmonyos·鸿蒙
无熵~13 小时前
Flutter入门
flutter
hudawei99613 小时前
要控制动画的widget为什么要with SingleTickerProviderStateMixin
flutter·mixin·with·ticker·动画控制
jian1105814 小时前
flutter dio 依赖,dependencies 和 dev_dependencies的区别
flutter
王码码203515 小时前
Flutter for OpenHarmony 实战之基础组件:第十七篇 滚动进阶 ScrollController 与 Scrollbar
flutter·harmonyos