Flutter:五分钟学会使用动态管理BLoC(Business Logic Component)

581 阅读5分钟

BLoC(Business Logic Component,业务逻辑组件)是 Flutter 中常用的一种状态管理模式。它的核心思想是将 业务逻辑UI 逻辑 分离,使得应用的状态管理更加清晰和易于维护。下面是 BLoC 的基本概念及其工作原理

1. BLoC 模式的核心思想

BLoC 模式的核心思想就是将 事件(Events)状态(States) 分开,并通过 事件 来驱动 状态的变化,然后 通过新的状态来更新界面。这种模式能够帮助你把业务逻辑与 UI 逻辑隔离开来,让代码更加模块化和可维护

2. 主要组成部分

在 BLoC 模式中,主要有三个组成部分:

  • 事件(Event)
  • 状态(State)
  • BLoC(业务逻辑组件)

3. 事件(Event)

  • 事件 是用户操作或外部系统的输入,通常由 UI 触发。比如:点击按钮、获取数据、输入文本等。
  • 事件通常是不可变的(immutable),一旦创建就无法更改。
  • 在 BLoC 模式中,事件用于通知 BLoC 执行某种操作。

例如:

  • 用户点击了按钮:ButtonPressedEvent
  • 用户请求加载数据:FetchDataEvent

4. 状态(State)

  • 状态 描述了应用的某一时刻的显示或数据状态。UI 的显示是基于状态的,状态的变化决定了 UI 的变化。
  • 每次 BLoC 处理完事件并做出相应的更新后,都会生成一个新的状态,并通过 emit 发出这个状态,供 UI 更新。

例如:

  • 计数器应用中的当前计数:CounterState(count: 5)
  • 数据加载中:LoadingState()
  • 数据加载成功:LoadedState(data: [1, 2, 3])

5. BLoC(业务逻辑组件)

  • BLoC 是处理事件和生成状态的核心部分,它接收事件并执行相关的业务逻辑,然后根据需要生成新的状态。

  • BLoC 类通常包含以下两部分:

    • 监听事件:BLoC 类会监听各种类型的事件。
    • 更新状态:每当一个事件触发时,BLoC 会根据事件的内容计算出一个新的状态并将其发射出来。

6. 如何工作?

BLoC 的工作流程通常是这样的:

  1. 事件(Event) 触发:UI 中的用户操作或其他条件触发了某个事件(例如按钮点击、数据请求)。
  2. BLoC 处理事件:BLoC 接收到这个事件后,根据事件中的数据进行业务逻辑处理(例如,更新数据、做某些计算等)。
  3. BLoC 发出新的状态(State) :BLoC 根据事件的处理结果,生成一个新的状态,并通过 emit 发出这个状态。
// 注册事件与事件处理函数的映射
on<PainterSourceChooseEvent>(_onPainterSourceChooseEvent);
void _onPainterSourceChooseEvent(PainterSourceChooseEvent event, Emitter<PopupPaintSettingState> emit) {
  debugPrint('PainterSourceChooseEvent: ${event.operationMode.toString()}');
  emit(state.clone());
}
  1. UI 更新:UI 监听状态的变化,收到新的状态后更新界面,显示新的内容。

7. 示例:简单的计数器应用

假设我们有一个简单的计数器应用,用户点击按钮增加计数。使用 BLoC 模式的流程如下:

 //事件
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}


//状态
abstract class CounterState {}

class CounterInitialState extends CounterState {
  final int count;
  CounterInitialState(this.count);
 }
 
 
 //BLOC类
class CounterBloc extends Bloc<CounterEvent, CounterState> {
     CounterBloc() : super(CounterInitialState(0));

   @override
   Stream<CounterState> mapEventToState(CounterEvent event) async* {
   if (event is IncrementEvent) {
      final currentState = state as CounterInitialState;
      yield CounterInitialState(currentState.count + 1);// 新的状态
   }
 }
}

//UI页面
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
return BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    if (state is CounterInitialState) {
      return Scaffold(
        appBar: AppBar(title: Text("Counter")),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text("Count: ${state.count}"),
              ElevatedButton(
                onPressed: () {
                  // 触发事件,增加计数
                  context.read<CounterBloc>().add(IncrementEvent());
                },
                child: Text("Increment"),
              ),
            ],
          ),
        ),
      );
    }
    return CircularProgressIndicator();
  },
);
 }
 ---------------------------------------------------------------------------------------
 1.使用抽象类来定义事件(如 `CounterEvent` CounterState)的主要原因是为了实现 **事件类型的封装****继承扩展**。这样做有几个好处
  **扩展性和维护性**
  **事件统一管理**
  // 举个例子,使用一个事件流来管理
  StreamController<CounterEvent> _eventController = StreamController<CounterEvent>();

 // 监听事件
_eventController.stream.listen((event) {
 if (event is IncrementEvent) {
   // 处理 IncrementEvent
 } else if (event is DecrementEvent) {
   // 处理 DecrementEvent
 }
});
2.mapEventToState
-   **`mapEventToState`**:是处理事件的核心方法,它会接收事件并生成新的状态你不需要在
这里注册事件,`mapEventToState` 会监听所有的事件类型并根据事件进行处理

-   **`on<EventType>`** :是注册事件和事件处理函数的简便方法,它可以让你将事件处理逻辑
拆分到单独的函数中,增强代码的可读性和可维护性

3.在 Flutter 中,`Bloc` 状态管理的常见使用方法包括 `BlocProvider``BlocBuilder` 和
`MultiBlocProvider`,这些是最常用的,但还有其他一些方法和工具可以帮助你在应用中使用 
`Bloc` 更加高效
 1).
 BlocProvider<CounterBloc>(
  create: (BuildContext context) => CounterBloc(),
  child: CounterPage(),
 );
 -   `create` 是用来创建 `Bloc` 的方法

 -   `child` 是你想要传递 `Bloc` 的 widget
 2).
 BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    if (state is CounterInitialState) {
      return Text('Count: ${state.count}');
    }
    return CircularProgressIndicator();
  },
);
3).
MultiBlocProvider(
  providers: [
    BlocProvider<CounterBloc>(create: (_) => CounterBloc()),
    BlocProvider<UserBloc>(create: (_) => UserBloc()),
   ],
  child: MyApp(),
);
4).### `BlocListener`

`BlocListener` 用来监听 `Bloc` 的状态并执行副作用操作(例如导航显示 Snackbar 等)与
`BlocBuilder` 不同,`BlocListener` 不会重新构建 UI,只会触发副作用

 BlocListener<CounterBloc, CounterState>(
  listener: (context, state) {
    if (state is CounterInitialState && state.count == 10) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Count is 10')),
      );
    }
  },
  child: CounterPage(),
);
5).BlocConsumer
`BlocConsumer` 是 `BlocBuilder` 和 `BlocListener` 的组合它既可以用来构建 UI,也可
以用来监听状态并执行副作用操作适合在同一个组件中需要同时进行 UI 更新和副作用处理的场景
-   `listener` 用来处理副作用

-   `builder` 用来构建 UI
BlocConsumer<CounterBloc, CounterState>(
  listener: (context, state) {
    if (state is CounterInitialState && state.count == 10) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Count is 10')),
      );
    }
   },
  builder: (context, state) {
    if (state is CounterInitialState) {
      return Text('Count: ${state.count}');
    }
    return CircularProgressIndicator();
  },
);
6).BlocSelector
`BlocSelector` 用于从 `Bloc` 中选择和监听特定的状态片段,它能够避免不必要的 UI 更新通
常用于状态比较大的 `Bloc`,你只关心其中的一部分数据时非常有用
-   `selector` 用来选择你关心的 `state` 的一部分

-   `builder` 会在 `state` 变化时重建 UI,但只有当选择的数据发生变化时才会更新
BlocSelector<CounterBloc, CounterState, int>(
  selector: (state) {
    if (state is CounterInitialState) {
      return state.count;
    }
    return 0;
  },
  builder: (context, count) {
    return Text('Count: $count');
  },
);
7).`BlocProvider.of` / `context.read` / `context.watch`
你可以直接通过 `BlocProvider.of<T>(context)` 来访问 `Bloc` 实例并触发事件例如:
BlocProvider.of<CounterBloc>(context).add(IncrementEvent());

`context.read<T>()`:
context.read<CounterBloc>().add(IncrementEvent());

#### `context.watch<T>()`:
 `context.watch<T>()` 会在 `T` 发生变化时自动重新构建 UI,相当于使用 `BlocBuilder`     来 
 监听 `Bloc` 的变化
 final state = context.watch<CounterBloc>().state;
8).BlocProvider.value
 是一种通过已存在的 `Bloc` 实例来提供 `Bloc` 给 widget 树的方式它通常用于已经创建并管
 理好的 `Bloc` 实例,避免每次创建新的 `Bloc` 实例
 BlocProvider.value(
   value: myExistingBloc,
   child: CounterPage(),
  );

9. 总结

  • BLoC 模式 通过将 事件状态 分开,使得业务逻辑和 UI 逻辑解耦,从而提高代码的可维护性和可测试性。
  • 事件表示用户的行为或其他触发条件,状态表示应用的当前状态。
  • BLoC 负责接收事件,处理并生成新的状态,然后通知 UI 更新。
  • 看到这就可以看懂别人的BLoC代码了。也可以写自己的逻辑了。