Flutter状态管理Bloc详解:实现响应式架构

一、Bloc概述

Bloc(Business Logic Component)是一种状态管理模式,将业务逻辑与UI分离。

1.1 添加依赖

dependencies:
  flutter_bloc: ^8.1.3
  equatable: ^2.0.5

1.2 核心概念

组件 作用
Event 触发状态变化的事件
State 当前状态
Bloc 处理事件并输出状态
Cubit 简化版Bloc,适合简单场景

二、创建Bloc

2.1 定义Event和State

part 'counter_event.dart';
part 'counter_state.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(const CounterState()) {
    on<IncrementEvent>(_onIncrement);
    on<DecrementEvent>(_onDecrement);
    on<ResetEvent>(_onReset);
  }
  
  void _onIncrement(IncrementEvent event, Emitter<CounterState> emit) {
    emit(state.copyWith(count: state.count + 1));
  }
  
  void _onDecrement(DecrementEvent event, Emitter<CounterState> emit) {
    emit(state.copyWith(count: state.count - 1));
  }
  
  void _onReset(ResetEvent event, Emitter<CounterState> emit) {
    emit(const CounterState(count: 0));
  }
}

2.2 Event类

part of 'counter_bloc.dart';

abstract class CounterEvent extends Equatable {
  const CounterEvent();
  
  @override
  List<Object> get props => [];
}

class IncrementEvent extends CounterEvent {}

class DecrementEvent extends CounterEvent {}

class ResetEvent extends CounterEvent {}

2.3 State类

part of 'counter_bloc.dart';

class CounterState extends Equatable {
  final int count;
  final bool isLoading;
  
  const CounterState({
    this.count = 0,
    this.isLoading = false,
  });
  
  CounterState copyWith({
    int? count,
    bool? isLoading,
  }) {
    return CounterState(
      count: count ?? this.count,
      isLoading: isLoading ?? this.isLoading,
    );
  }
  
  @override
  List<Object> get props => [count, isLoading];
}

三、在UI中使用Bloc

3.1 BlocProvider

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CounterBloc(),
      child: const MaterialApp(
        home: CounterPage(),
      ),
    );
  }
}

3.2 BlocBuilder

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: BlocBuilder<CounterBloc, CounterState>(
        builder: (context, state) {
          return Center(
            child: Text('Count: ${state.count}'),
          );
        },
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => context.read<CounterBloc>().add(const IncrementEvent()),
            child: const Icon(Icons.add),
          ),
          const SizedBox(height: 8),
          FloatingActionButton(
            onPressed: () => context.read<CounterBloc>().add(const DecrementEvent()),
            child: const Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

四、异步操作

class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository _userRepository;
  
  UserBloc(this._userRepository) : super(const UserState()) {
    on<FetchUserEvent>(_onFetchUser);
  }
  
  Future<void> _onFetchUser(FetchUserEvent event, Emitter<UserState> emit) async {
    emit(state.copyWith(status: UserStatus.loading));
    try {
      final user = await _userRepository.getUser(event.id);
      emit(state.copyWith(
        status: UserStatus.success,
        user: user,
      ));
    } catch (e) {
      emit(state.copyWith(
        status: UserStatus.failure,
        error: e.toString(),
      ));
    }
  }
}

五、状态转换

class UserState extends Equatable {
  final UserStatus status;
  final User? user;
  final String? error;
  
  const UserState({
    this.status = UserStatus.initial,
    this.user,
    this.error,
  });
  
  UserState copyWith({
    UserStatus? status,
    User? user,
    String? error,
  }) {
    return UserState(
      status: status ?? this.status,
      user: user ?? this.user,
      error: error ?? this.error,
    );
  }
  
  @override
  List<Object?> get props => [status, user, error];
}

enum UserStatus {
  initial,
  loading,
  success,
  failure,
}

六、Cubit简化版

class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(const CounterState());
  
  void increment() => emit(state.copyWith(count: state.count + 1));
  void decrement() => emit(state.copyWith(count: state.count - 1));
  void reset() => emit(const CounterState());
}

// 使用
class CounterPage extends StatelessWidget {
  const CounterPage({super.key});
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: BlocBuilder<CounterCubit, CounterState>(
        builder: (context, state) {
          return Center(child: Text('Count: ${state.count}'));
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterCubit>().increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

七、BlocListener

BlocListener<CounterBloc, CounterState>(
  listener: (context, state) {
    if (state.count == 10) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Reached 10!')),
      );
    }
  },
  child: const CounterView(),
)

八、最佳实践

8.1 分离关注点

// Repository层
class UserRepository {
  Future<User> getUser(int id) async {
    // API调用
  }
}

// Bloc层
class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository _repository;
  
  UserBloc(this._repository) : super(const UserState()) {
    on<FetchUserEvent>(_onFetchUser);
  }
  
  Future<void> _onFetchUser(FetchUserEvent event, Emitter<UserState> emit) async {
    emit(state.copyWith(status: UserStatus.loading));
    try {
      final user = await _repository.getUser(event.id);
      emit(state.copyWith(status: UserStatus.success, user: user));
    } catch (e) {
      emit(state.copyWith(status: UserStatus.failure));
    }
  }
}

8.2 测试Bloc

void main() {
  late CounterBloc counterBloc;
  
  setUp(() {
    counterBloc = CounterBloc();
  });
  
  tearDown(() {
    counterBloc.close();
  });
  
  test('initial state is CounterState()', () {
    expect(counterBloc.state, const CounterState());
  });
  
  blocTest<CounterBloc, CounterState>(
    'emits [CounterState(count: 1)] when IncrementEvent is added',
    build: () => counterBloc,
    act: (bloc) => bloc.add(const IncrementEvent()),
    expect: () => [const CounterState(count: 1)],
  );
}

总结

Bloc是Flutter中强大的状态管理方案,通过事件驱动实现响应式架构。

关键要点:

  1. Event:触发状态变化的事件
  2. State:当前状态
  3. Bloc:处理事件并输出状态
  4. Cubit:简化版Bloc
  5. BlocProvider:提供Bloc实例
  6. BlocBuilder:根据状态重建UI
  7. BlocListener:监听状态变化

通过合理使用Bloc,你可以构建清晰、可测试的Flutter应用。

更多推荐