Flutter 开发之第一个Flutter应用

0 阅读2分钟

1. 在flutter中实现类似Vue的自适应更新数据,而不是每次都需要setState的方式

方案一:Riverpod(目前最主流)

Riverpod 是 Provider 作者的新作,2023-2024 年火起来的。它虽然还是要调用方法更新,但配合 ref.watch 可以实现类似自动响应的体验:

final counterProvider = StateProvider((ref) => 0);

// 在 widget 中使用
class CounterWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider); // 自动监听,变化时重建
    return Text('$count');
  }
}

Riverpod 的特点是编译安全、不依赖 BuildContext、支持自动销毁状态

void main() {
  runApp(
    // 添加这个ProviderScope
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

ProviderScope 就像一个全局的"状态容器",它负责存储和管理我们接下来要创建的所有Provider的状态,之所以写在根组件上是保证所有他的子组件可以共享一个状态,类似于单例,当然也可以不写,而写在其他地方,但要注意,这样的话会导致平级或者上级无法响应数据变化,一旦销毁了,子组件之间的关系也解除了。

如果要监听的数据很多,可以包装成一个对象,单独监听这个对象或者这个对象的某个属性。

// 定义一个用户信息类
class UserProfile {
  UserProfile({this.name = '', this.age = 0, this.email = ''});
  String name;
  int age;
  String email;
}

// 用一个 provider 管理整个用户信息
final userProfileProvider = StateProvider<UserProfile>((ref) => UserProfile());
final name = ref.watch(userProfileProvider.select((user) => user.name));

方案二:GetX(极简风格)

  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    // return MaterialApp(
    //   title: 'Flutter Demo',
    //   theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)),
    //   home: const MyHomePage(title: 'Flutter Demo Home Page1223'),
    // );
    // 将 MaterialApp 替换为 GetMaterialApp
    return GetMaterialApp(
      title: 'Flutter GetX Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: HomePage(), // 指向我们的新首页
    );
  }
}
1.为什么在顶层要用GetMaterialApp? 如果不替换,GetX 的路由和全局交互功能可能无法正常工作。但如果你只使用状态管理(Obx.obs),不涉及路由和对话框,理论上普通 MaterialApp 也能运行(因为 Obx 依赖于 Get 的上下文,而 Get 可以通过其他方式初始化)。不过官方强烈建议替换,以享受完整的 GetX 生态。
2. 为什么是 0.obs?它背后是什么?

0.obs 是 GetX 提供的一种简洁的响应式变量创建方式。它的本质是 Dart 扩展方法 + 响应式包装类

  • .obs 是 GetX 为 Dart 的基础类型(如 intStringboolList 等)添加的扩展方法。当你写 0.obs 时,实际上调用的是 RxInt(0),它返回一个 RxInt 对象。
  • RxInt 继承自 Rx<T>,内部维护了一个值(_value)和一个订阅者列表(_observers)。
  • 当你读取 .value 时,RxInt 会记录当前正在运行的 Obx 闭包(如果有的话),将其添加到订阅者列表中。
  • 当你修改 .value 时(比如 count.value++),RxInt 会遍历所有订阅者,通知它们重新执行闭包,从而更新 UI。

这种设计被称为响应式编程(类似 Vue 的 ref 或 MobX 的 observable),通过 .obs 让普通变量变得可观察。


3. Obx(() => Text('${controller.count.value}')) 和 setState 真的是一样的吗?

完全不同!  虽然写起来确实需要明确告诉框架“我要监听 count”,但背后的机制和性能表现截然不同:

相同点

  • 都需要“标记”哪些数据会影响 UI(在 GetX 里是 .obs 变量,在 setState 里是整个 State 的成员变量)。
  • 都是响应式的——数据变化驱动 UI 更新。

方案三:信号机制(Signals)- 2025 年新趋势

尚未有稳定版本推出,dart版本需要3.5以上,目前最新的flutter稳定版本不支持

方案四:Bloc(事件驱动)

2. 关于Widget

state

image.png

获取state的方式一:通过Context获取

context对象有一个findAncestorStateOfType()方法,该方法可以从当前节点沿着 widget 树向上查找指定类型的 StatefulWidget 对应的 State 对象。

context

在子树中获取父级 widget 的一个示例:

class ContextRoute extends StatelessWidget  {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Context测试"),
      ),
      body: Container(
        child: Builder(builder: (context) {
          // 在 widget 树中向上查找最近的父级`Scaffold`  widget 
          Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
          // 直接返回 AppBar的title, 此处实际上是Text("Context测试")
          return (scaffold.appBar as AppBar).title;
        }),
      ),
    );
  }
}
  • 从当前 Element 开始,通过 parent 指针依次访问每一个祖先 Element
  • 对于每个祖先 Element,检查它是否是 StatefulElement(即对应的 widget 是 StatefulWidget)。
  • 如果是 StatefulElement,再检查其 widget 的类型是否与传入的类型参数 T 匹配。
  • 如果匹配,则返回该 StatefulElement 的 state 属性(即 State 对象)。
  • 如果遍历到根节点仍未找到,返回 null
通过GlobalKey
  1. 给目标StatefulWidget添加GlobalKey

    //定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
    static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
    ...
    Scaffold(
        key: _globalKey , //设置key
        ...  
    )
    
  2. 通过GlobalKey来获取State对象

    _globalKey.currentState.openDrawer()
    

GlobalKey 是 Flutter 提供的一种在整个 App 中引用 element 的机制。如果一个 widget 设置了GlobalKey,那么我们便可以通过globalKey.currentWidget获得该 widget 对象、globalKey.currentElement来获得 widget 对应的element对象,如果当前 widget 是StatefulWidget,则可以通过globalKey.currentState来获得该 widget 对应的state对象。

注意:使用 GlobalKey 开销较大,如果有其他可选方案,应尽量避免使用它。另外,同一个 GlobalKey 在整个 widget 树中必须是唯一的,不能重复。