Flutter状态管理插件rx_redux的使用

发布于 1周前 作者 itying888 来自 Flutter

Flutter状态管理插件rx_redux的使用

介绍

rx_redux 是一个为Dart和Flutter设计的响应式Redux Store,由 Petrus Nguyễn Thái Học 创建。它完全基于Dart Stream,并结合了RxDart的强大功能(灵感来源于redux-observable),用于隔离副作用。

Logo

安装

pubspec.yaml文件中添加依赖:

dependencies:
  rx_redux: ^2.0.0

基本概念

Redux Store

Store是状态容器,它通过自定义流转换器ReduxStoreStreamTransformer(或扩展方法reduxStore)创建。它接受初始状态、副作用列表和Reducer。

Action

Action是触发Store执行某些操作的命令。它可以由用户交互或SideEffect触发。

Reducer

Reducer是一个函数 (State, Action) -> State,用于根据当前状态和Action计算新的状态。

Side Effect

Side Effect是类型为 (Stream<Action>, GetState<State>) -> Stream<Action> 的函数,用于处理异步逻辑或副作用。

GetState

GetState是一个函数 () -> State,用于获取最新的状态。

Selector

Selector用于从Store中选择特定部分的状态,支持高效的缓存机制。

使用示例

以下是一个完整的示例,展示了如何使用rx_redux实现一个简单的待办事项应用。

定义State和initialState

class ViewState {
  final List<Todo> todos;

  const ViewState(this.todos);

  @override
  String toString() => 'ViewState { ${todos.length} }';
}

class Todo {
  final int id;
  final String title;
  final bool completed;

  const Todo(this.id, this.title, this.completed);

  @override
  String toString() => 'Todo { $id, $completed }';
}

定义Actions

class Action {
  final Todo todo;
  final ActionType type;

  const Action(this.todo, this.type);

  @override
  String toString() => 'Action { ${todo.id}, $type }';
}

enum ActionType {
  add,
  remove,
  toggle,
  added,
  removed,
  toggled,
}

定义Reducer

ViewState reducer(ViewState vs, Action action) {
  switch (action.type) {
    case ActionType.add:
      return vs;
    case ActionType.remove:
      return vs;
    case ActionType.toggle:
      return vs;
    case ActionType.added:
      return ViewState([...vs.todos, action.todo]);
    case ActionType.removed:
      return ViewState(
        vs.todos.where((t) => t.id != action.todo.id).toList(),
      );
    case ActionType.toggled:
      final todos = vs.todos
          .map((t) =>
              t.id != action.todo.id ? t : Todo(t.id, t.title, !t.completed))
          .toList(growable: false);
      return ViewState(todos);
    default:
      return vs;
  }
}

定义SideEffects

final SideEffect<Action, ViewState> addTodoEffect = (action$, state) => action$
    .where((event) => event.type == ActionType.add)
    .map((event) => event.todo)
    .flatMap(
      (todo) => Rx.timer(
        Action(todo, ActionType.added),
        const Duration(milliseconds: 300),
      ),
    );

Stream<Action> removeTodoEffect(
  Stream<Action> action$,
  GetState<ViewState> state,
) {
  final executeRemove = (Todo todo) async* {
    await Future.delayed(const Duration(milliseconds: 200));
    yield Action(todo, ActionType.removed);
  };
  return action$
      .where((event) => event.type == ActionType.remove)
      .map((action) => action.todo)
      .flatMap(executeRemove);
}

final SideEffect<Action, ViewState> toggleTodoEffect = (action$, state) {
  final executeToggle = (Todo todo) async* {
    await Future.delayed(const Duration(milliseconds: 500));
    yield Action(todo, ActionType.toggled);
  };
  return action$
      .where((event) => event.type == ActionType.toggle)
      .map((action) => action.todo)
      .flatMap(executeToggle);
};

组合并使用

void main() async {
  final store = RxReduxStore(
    initialState: ViewState([]),
    sideEffects: [addTodoEffect, removeTodoEffect, toggleTodoEffect],
    reducer: reducer,
    logger: rxReduxDefaultLogger,
  );

  store.stateStream.listen((event) => print('~> $event'));

  for (var i = 0; i < 5; i++) {
    store.dispatch(Action(Todo(i, 'Title $i', i.isEven), ActionType.add));
  }
  await Future.delayed(const Duration(seconds: 1));

  for (var i = 0; i < 5; i++) {
    store.dispatch(Action(Todo(i, 'Title $i', i.isEven), ActionType.toggle));
  }
  await Future.delayed(const Duration(seconds: 1));

  for (var i = 0; i < 5; i++) {
    store.dispatch(Action(Todo(i, 'Title $i', i.isEven), ActionType.remove));
  }
  await Future.delayed(const Duration(seconds: 1));
}

更多示例

分页加载示例

分页加载示例

GitHub搜索示例

GitHub搜索示例

简单待办事项示例

简单待办事项示例

FAQ

我遇到了StackOverflowError怎么办?

这通常是由于SideEffect发出的动作再次被同一个SideEffect消费导致的无限循环。确保你的SideEffect不会产生这样的循环。

Reducer和SideEffect谁先处理Action?

技术上,Reducer会先处理Action,然后再传递给注册的SideEffect。这样可以确保Reducer已经更新状态后再执行SideEffect。

是否需要使用distinct来避免重复状态?

是的,建议使用.distinct()来确保只发出状态变化的部分,避免不必要的UI更新。

如何取消正在进行的SideEffect?

可以使用RxDart中的.takeUntil()操作符来取消SideEffect。

是否需要一个Action来启动数据观察?

不需要,可以直接在Store初始化时开始观察数据,例如数据库查询。

希望这些信息能帮助你更好地理解和使用rx_redux!如果有任何问题,欢迎在issue tracker中提出。


更多关于Flutter状态管理插件rx_redux的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter状态管理插件rx_redux的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter开发中,rx_redux 是一个结合了 GetXRedux 理念的轻量级状态管理库。它利用 rxdart 包中的 BehaviorSubject 来管理应用的状态,使得状态更新和监听变得相对简单。下面是一个简单的示例,展示如何在Flutter应用中使用 rx_redux 进行状态管理。

1. 添加依赖

首先,在你的 pubspec.yaml 文件中添加 rx_redux 的依赖:

dependencies:
  flutter:
    sdk: flutter
  rx_redux: ^x.y.z  # 请替换为最新版本号

然后运行 flutter pub get 来安装依赖。

2. 定义状态模型

定义一个简单的状态模型,例如一个计数器应用的状态:

import 'package:rx_redux/rx_redux.dart';

class CounterState implements RxReduxState {
  final int count;

  CounterState({required this.count});

  CounterState copyWith({int? count}) {
    return CounterState(count: count ?? this.count);
  }

  @override
  List<Object?> get props => [count];
}

3. 创建Redux Store

接下来,创建一个Redux Store来管理这个状态:

import 'package:rx_redux/rx_redux.dart';
import 'counter_state.dart'; // 假设状态模型文件名为counter_state.dart

final counterReducer = combineReducers<CounterState>([
  TypedReducer<CounterState, IncrementAction>(_increment),
  TypedReducer<CounterState, DecrementAction>(_decrement),
]);

CounterState initialState() => CounterState(count: 0);

Store<CounterState> createStore() {
  return Store<CounterState>(
    initialState: initialState(),
    reducer: counterReducer,
  );
}

class IncrementAction {}
class DecrementAction {}

CounterState _increment(CounterState state, IncrementAction action) {
  return state.copyWith(count: state.count + 1);
}

CounterState _decrement(CounterState state, DecrementAction action) {
  return state.copyWith(count: state.count - 1);
}

4. 在应用中使用Store

在你的Flutter组件中使用这个Store来监听和更新状态:

import 'package:flutter/material.dart';
import 'package:rx_redux/rx_redux.dart';
import 'store.dart'; // 假设Redux Store文件名为store.dart

void main() {
  final store = createStore();
  runApp(MyApp(store: store));
}

class MyApp extends StatelessWidget {
  final Store<CounterState> store;

  MyApp({required this.store});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(store: store),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final Store<CounterState> store;

  MyHomePage({required this.store});

  void _increment() {
    store.dispatch(IncrementAction());
  }

  void _decrement() {
    store.dispatch(DecrementAction());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${store.state.count}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            onPressed: _decrement,
            tooltip: 'Decrement',
            child: Icon(Icons.remove),
          ),
          SizedBox(width: 10),
          FloatingActionButton(
            onPressed: _increment,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

在这个示例中,我们创建了一个简单的计数器应用,使用 rx_redux 管理计数器的状态。我们定义了状态模型 CounterState,创建了Redux Store,并在Flutter组件中通过Store监听和更新状态。

请注意,这个例子是一个简化的演示,实际应用中你可能需要处理更复杂的状态逻辑和更多的Action类型。此外,根据项目的需要,你可能还需要考虑持久化状态、中间件等高级特性。

回到顶部