前言

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

一、一个“失灵”的计数器

1.1 计数器效果

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

无状态组件和有状态组件

无状态组件:StatelessWidget

有状态组件:StatefulWidget

   print('今年$age岁');

1.2 计数器代码

main.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代码:

// 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就像一个静态的照片——拍完照片是什么样子,就永远是什么样子,不会自己变化。

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

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

2.2  三大核心特征

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

// ✅ 正确写法:属性都是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纯函数式(相同的输入,相同的输出)

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

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

// 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 为什么刚才的计数器会“失灵”

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

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则像一段动态视频,可以记录变化的过程。

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

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

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

main.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代码:

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代码:

    setState(() {
             age = age + 1;
          });

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

3.3 setState()的作用

(1)更新状态变量的值

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

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

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

四、结语

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

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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐