# 状态管理方案-Redux

# 工作原理

Redux是一个应用状态管理框架。换句话说,它的主要目标是管理一个状态。

Redux围绕以下原则构建:

  • 单向数据流

  • Store

    • 只存储一个状态
    • 暴露了一个入口点,称为dispatch,它只接受参数中的Action。
    • 暴露了一个getters来获取当前状态。
    • 允许(不)注册,以便(通过StreamSubscription)通知State的任何变化。
    • 将动作和存储分配给第一个 MiddleWare 的
    • 将动作和当前状态分配给一个Reducer(可能是多个Redducers的门面)。 行动
  • Actions

    Actions是Store访问点接受的唯一输入类型。MiddleWare和Reducer使用Action和当前状态来处理某些功能,这可能导致修改状态。

    Actions只描述发生了什么。

  • MiddleWare

    中间件是一种功能,通常以异步运行(但不一定)为目的,基于一个Action。中间件只是简单地使用一个状态(或一个Action作为触发器),但不改变状态。

  • Reducers

    Reducer通常是一个同步函数,它根据Action-State的组合进行一些处理。处理的结果可能导致一个新的State。 Reducer是唯一一个被允许改变状态的函数。

需要注意的是,根据Redux的建议和良好实践,每个应用程序只有一个单一的Store。为了拆分数据处理逻辑,建议使用 "reducer composition "而不是许多Store。

models_redux_animation

解析:

  • 当UI层发生某些事情时(但实际上不限于UI),会创建一个Action并发送给Store(通过store.dispatch(action))。
  • 如果配置了一个或多个中间件,它们会被依次调用,并将Action和对Store的引用传递给它们(因此,也间接地传递给State)。
  • Middlewares本身也可以在处理过程中向Store发送一个Action。
  • 然后,Action和当前状态也会被发送到Reducer。
  • Reducer是唯一一个有可能改变State的东西
  • 当状态发生变化时,Store会通知所有注册的监听者以告知他们。
  • 然后,UI(但不限于UI)可以采取与状态变化相关联的适当操作。

# 安装依赖

Redux最初是为Javascript开发的,并作为一个包移植到Dart。

一个Flutter专用包(flutter_redux (opens new window))提供了一些Widgets,比如。

  • StoreProvider将Store传递给所有的子代Widgets。
  • StoreBuilder从StoreProvider获取Store,并将其传递给Widget builder函数。
  • StoreConnector从最近的StoreProvider祖先中获取Store,将其转换为ViewModel并传递给构建函数。
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  redux: ^5.0.0
  flutter_redux: ^0.8.2

然后使用flutter pub get安装依赖。

# Redux案例 - 计数器

# 核心概念

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
// 1.删除无用的文件,删除注释

enum Actions { increment }
// 传递进来一个state + action
// 返回去一个新的state
int counterReducer(int state, action) {
  if (action == Actions.increment) {
    // action -> enum
    return state + 1;
  }
  return state;
}

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

class MyApp extends StatelessWidget {
  // 2.创建Store -> state
  final store = Store(counterReducer, initialState: 5);
  MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    // 3.使用StoreProvider
    return StoreProvider(
      store: store,
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHome(),
      ),
    );
  }
}

class MyHome extends StatelessWidget {
  const MyHome({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: [
            Text('计数器的状态:'),
            // 4.添加按钮 -> dispatch -> action -> reducer -> 更新state
            // builder -> 最终返回的widgets
            // StoreConnector -> <初始的state类型, 最终输出到builder的类型>
            // converter -> 取用最近的Provider -> store -> 转化成我们需要的类型数据
            // builder -> 返回一个Widget -> 使用state -> viewModel
            StoreConnector<int, String>(
              builder: (context, countStr) {
                return Text(
                  countStr,
                  style: const TextStyle(
                    fontSize: 24.0,
                  ),
                );
              },
              converter: (store) => store.state.toString(),
            ),
          ],
        ),
      ),
      floatingActionButton: StoreConnector<int, VoidCallback>(
        builder: (context, callback) => FloatingActionButton(
          child: const Icon(Icons.add),
          onPressed: () {
            // 触发dispatch方法 -> action -> Actions.increment
            callback();
          },
        ),
        converter: (store) {
          // 返回store.dispatch -> Funciton -> builder的第二个参数
          return () => store.dispatch(Actions.increment);
        },
      ),
    );
  }
}

# 优化案例&拆分逻辑

新建文件:lib/store/state.dart

class AppState {
  final int count;

  AppState(this.count );

  AppState.initState()
      : count = 0;
}

新建文件:lib/store/actions.dart

enum CountActions {
  increment,
  decrement,
}

新建文件:lib/store/reducers.dart

import 'actions.dart';

int counterReducer(int state, action) {
  if (action == CountActions.increment) {
    // action -> enum
    return state + 1;
  }
  if (action == CountActions.decrement) {
    return state - 1;
  }
  return state;
}

优化main.dart

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/reducers.dart';
import 'package:redux_example/store/state.dart';

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

class MyApp extends StatelessWidget {
  // 2.创建Store -> state
  final store = Store(appStateReducer, initialState: AppState(0));
  MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    // 3.使用StoreProvider
    return StoreProvider<AppState>(
      store: store,
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHome(),
      ),
    );
  }
}

class MyHome extends StatelessWidget {
  const MyHome({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: [
            Text('计数器的状态:'),
            // 4.添加按钮 -> dispatch -> action -> reducer -> 更新state
            // builder -> 最终返回的widgets
            // StoreConnector -> <初始的state类型, 最终输出到builder的类型>
            // converter -> 取用最近的Provider -> store -> 转化成我们需要的类型数据
            // builder -> 返回一个Widget -> 使用state -> viewModel
            StoreConnector<AppState, String>(
              builder: (context, countStr) {
                return Text(
                  countStr,
                  style: const TextStyle(
                    fontSize: 24.0,
                  ),
                );
              },
              converter: (store) => store.state.count.toString(),
            ),
          ],
        ),
      ),
      // 1. 需要添加一个按钮 -> onPress -> new Action
      // 2. 需要添加action -> decrement
      // 3. 需要配置reducer -> dispatch -> new state
      floatingActionButton:
          StoreConnector<AppState, Function(CountActions action)>(
        builder: (context, callback) => Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              child: const Icon(Icons.add),
              onPressed: () {
                // 触发dispatch方法 -> action -> Actions.increment
                callback(CountActions.increment);
              },
            ),
            const Padding(
              padding: EdgeInsets.only(top: 10.0),
            ),
            FloatingActionButton(
              child: const Icon(Icons.remove),
              onPressed: () {
                // 触发dispatch方法 -> action -> Actions.increment
                callback(CountActions.decrement);
              },
            ),
          ],
        ),
        converter: (store) {
          // 返回store.dispatch -> Funciton -> builder的第二个参数
          return (action) => store.dispatch(action);
        },
      ),
    );
  }
}

# Redux案例 - TodoList

# 创建actions,reducers,state

修改文件:lib/store/state.dart

// 定义全局的state
// int count;
// List<Item> lists;
// Item {int id, String value}
class Item {
  final int? id;
  final String value;

  Item({this.id, required this.value});
}

class AppState {
  final int count;
  final List<Item> lists;

  AppState({this.count = 0, this.lists = const <Item>[]});

  AppState.initState()
      : count = 0,
        lists = const <Item>[];
}

Tips:这里要巧用vscode的快捷菜单来创建constructors。

修改文件:lib/store/actions.dart

// 1.删除无用的文件,删除注释
enum CountActions {
  increment,
  decrement,
}

// TodoAddAction, TodoRemoveAction, TodoClearAction
class TodoAddAction {
  // Item -> int id, String text
  static int _id = 0;
  final String? text;

  TodoAddAction(this.text) {
    _id++;
  }

  int get id => _id;
}

修改文件:lib/store/reducers.dart

// 传递进来一个state + action
// 返回去一个新的state
import 'package:redux_example/store/state.dart';

import 'actions.dart';

int counterReducer(int state, action) {
  if (action == CountActions.increment) {
    // action -> enum
    return state + 1;
  }
  if (action == CountActions.decrement) {
    return state - 1;
  }
  return state;
}

List<Item> todoReducer(List<Item> state, action) {
  if (action is TodoAddAction) {
    // 添加item -> lists
    // 新item -> 从传入action来
    // 新lists -> 新item -> 添加到旧的lists后面, add & 扩展运算符 ...
    return [
      ...state,
      Item(id: action.id, value: action.text ?? ''),
    ];
  }
  return state;
}

AppState appStateReducer(AppState state, dynamic action) {
  return AppState(
    count: counterReducer(state.count, action),
    lists: todoReducer(state.lists, action),
    // ...
  );
}
// 1.创建state -> actions -> reducers
// 2.初始化store -> StoreProvider
// 3.添加input -> add button
// 4.分发actions -> store.dispatch -> new state

# 封装ViewModel

目标:

  • 管理所有的state与state相关的actions;
  • 作为一个统一的入口组件;
  • 清晰的逻辑关系,封装StoreConnecter,redux的commit操作;

创建lib/widgets/view_model.dart文件:

// 1.定义所有的reducer方法
// 2.定义工厂函数,返回所有的state状态 -> appState
import 'package:redux/redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/state.dart';

class ViewModel {
  final List<Item> lists;
  final Function addItem;
  // count计数器
  final int count;
  final Function onIncrement;
  final Function onDecrement;

  ViewModel({
    required this.lists,
    required this.addItem,
    required this.count,
    required this.onIncrement,
    required this.onDecrement,
  });

  factory ViewModel.create(Store<AppState> store) {
    _onIncrement() {
      store.dispatch(CountActions.increment);
    }

    _onDecrement() {
      store.dispatch(CountActions.decrement);
    }

    _onAddItem(String value) {
      store.dispatch(TodoAddAction(value));
    }

    return ViewModel(
      lists: store.state.lists,
      addItem: _onAddItem,
      count: store.state.count,
      onIncrement: _onIncrement,
      onDecrement: _onDecrement,
    );
  }
}

# 待办清单应用

学习目标:

  • 学会如何封装组件、组件传参;
  • 学会redux的综合应用:state,actions,reducers的作用,传参;
  • 有(无)状态组件如何取用参数;
  • 学习vscode的快捷菜单的使用;

# 封装add组件

创建lib/widgets/add.dart

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/view_model.dart';

class Add extends StatefulWidget {
  final ViewModel viewModel;
  const Add(
    this.viewModel, {
    Key? key,
  }) : super(key: key);

  
  State<Add> createState() => _AddState();
}

class _AddState extends State<Add> {
  TextEditingController controller = TextEditingController();

  
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
      decoration: const InputDecoration(hintText: '请输入待办事项'),
      onSubmitted: (String s) {
        print('用户输入了$s');
        print('用户输入了${controller.text}');
        // callback();
        widget.viewModel.addItem(controller.text);
        controller.text = '';
      },
    );
  }
}

# 列表组件

添加列表组件lib/widgets/lists_item_view.dart

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/view_model.dart';

class ListsItemView extends StatelessWidget {
  final ViewModel viewModel;
  const ListsItemView(
    this.viewModel, {
    Key? key,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    return ListView(
      children: viewModel.lists
          .map((Item item) => ListTile(
                title: Text('${item.id}-${item.value}'),
              ))
          .toList(),
    );
  }
}

# 计数器显示组件

添加计数器显示组件lib/widgets/count_view.dart

import 'package:flutter/material.dart';
// import 'package:flutter_redux/flutter_redux.dart';
// import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/view_model.dart';

class CountView extends StatelessWidget {
  final ViewModel viewModel;
  const CountView(
    this.viewModel, {
    Key? key,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    return Text(
      viewModel.count.toString(),
      style: const TextStyle(
        fontSize: 24.0,
      ),
    );
  }
}

# 添加功能

修改main.dart

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/reducers.dart';
import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/add.dart';
import 'package:redux_example/widgets/count_view.dart';
import 'package:redux_example/widgets/lists_item_view.dart';
import 'package:redux_example/widgets/view_model.dart';

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

class MyApp extends StatelessWidget {
  // 2.创建Store -> state
  final store = Store(appStateReducer, initialState: AppState());
  MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    // 3.使用StoreProvider
    return StoreProvider<AppState>(
      store: store,
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHome(),
      ),
    );
  }
}

class MyHome extends StatefulWidget {
  const MyHome({Key? key}) : super(key: key);

  
  State<MyHome> createState() => _MyHomeState();
}

class _MyHomeState extends State<MyHome> {
  
  Widget build(BuildContext context) {
    return StoreConnector<AppState, ViewModel>(
      builder: (context, viewModel) => Scaffold(
        appBar: AppBar(),
        body: SizedBox(
          height: MediaQuery.of(context).size.height,
          child: Column(
            children: [
              const Text('计数器的状态:'),
              // 4.添加按钮 -> dispatch -> action -> reducer -> 更新state
              // builder -> 最终返回的widgets
              // StoreConnector -> <初始的state类型, 最终输出到builder的类型>
              // converter -> 取用最近的Provider -> store -> 转化成我们需要的类型数据
              // builder -> 返回一个Widget -> 使用state -> viewModel
              CountView(viewModel),
              Add(viewModel),
              Expanded(
                child: ListsItemView(viewModel),
              )
            ],
          ),
        ),
        // 1. 需要添加一个按钮 -> onPress -> new Action
        // 2. 需要添加action -> decrement
        // 3. 需要配置reducer -> dispatch -> new state
        floatingActionButton: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              child: const Icon(Icons.add),
              onPressed: () {
                // 触发dispatch方法 -> action -> Actions.increment
                viewModel.onIncrement();
              },
            ),
            const Padding(
              padding: EdgeInsets.only(top: 10.0),
            ),
            FloatingActionButton(
              child: const Icon(Icons.remove),
              onPressed: () {
                // 触发dispatch方法 -> action -> Actions.increment
                viewModel.onDecrement();
              },
            ),
          ],
        ),
      ),
      converter: (store) => ViewModel.create(store),
    );
  }
}

# 作业:删除与清空功能

目标:

  • 点击删除,可以删除某个待办清单项;
  • 点击清空,可以清空所有的清单列表

步骤:

  • 添加actions,state,reducers;
  • 添加view_model.dart中的对应的方法;
  • 添加页面上的元素(按钮)与点击回调的事件;
  • 回调viewModel中的方法,同时需要传参的进行参数的传递;

# 修改actions,state,reducers

首先是actions,这里要注意TodoClearAction这个类,没有参数,所以也不需要加构造函数。

// 1.删除无用的文件,删除注释
enum CountActions {
  increment,
  decrement,
}

class TodoAddAction {
  // Item -> int id, String text
  static int _id = 0;
  final String? text;

  TodoAddAction(this.text) {
    _id++;
  }

  int get id => _id;
}

class TodoRemoveAction {
  final int id;

  TodoRemoveAction(this.id);
}

class TodoClearAction {}

然后state可以不变,最后是reducers:

扩展:这里使用flutter_redux中的TypedReducer去减少对于类型的判断。

// 传递进来一个state + action
// 返回去一个新的state
import 'package:redux/redux.dart';
import 'package:redux_example/store/state.dart';

import 'actions.dart';

int counterReducer(int state, action) {
  if (action == CountActions.increment) {
    // action -> enum
    return state + 1;
  }
  if (action == CountActions.decrement) {
    return state - 1;
  }
  return state;
}

final todoReducer = combineReducers<List<Item>>([
  // TypedReducer<state的类型, 判断Action的类型>(真正的store state的处理逻辑)
  TypedReducer<List<Item>, TodoAddAction>((state, action) =>
      [...state, Item(id: action.id, value: action.text ?? '')]),
  TypedReducer<List<Item>, TodoRemoveAction>((state, action) =>
      state..removeWhere((Item item) => item.id == action.id)),
  TypedReducer<List<Item>, TodoClearAction>((state, action) => const <Item>[]),
]);

// List<Item> todoReducer(List<Item> state, action) {
//   if (action is TodoAddAction) {
//     // 添加item -> lists
//     // 新item -> 从传入action来
//     // 新lists -> 新item -> 添加到旧的lists后面, add & 扩展运算符 ...
// return [
//   ...state,
//   Item(id: action.id, value: action.text ?? ''),
// ];
//   }
//   if (action is TodoRemoveAction) {
//     return state..removeWhere((Item item) => item.id == action.id);
//   }
//   if (action is TodoClearAction) {
//     return const <Item>[];
//   }
//   return state;
// }

AppState appStateReducer(AppState state, dynamic action) {
  return AppState(
    count: counterReducer(state.count, action),
    lists: todoReducer(state.lists, action),
    // ...
  );
}
// 1.创建state -> actions -> reducers
// 2.初始化store -> StoreProvider
// 3.添加input -> add button
// 4.分发actions -> store.dispatch -> new state

# 调整ViewModel

// 1.定义所有的reducer方法
// 2.定义工厂函数,返回所有的state状态 -> appState
import 'package:redux/redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/state.dart';

class ViewModel {
  final List<Item> lists;
  final Function addItem;
  // 新增的属性
  final Function removeItem;
  final Function clearItems;
  final int count;
  final Function onIncrement;
  final Function onDecrement;

  ViewModel({
    required this.lists,
    required this.addItem,
    // 新增的属性
    required this.removeItem,
    required this.clearItems,
    required this.count,
    required this.onIncrement,
    required this.onDecrement,
  });

  factory ViewModel.create(Store<AppState> store) {
    _onIncrement() {
      store.dispatch(CountActions.increment);
    }

    _onDecrement() {
      store.dispatch(CountActions.decrement);
    }

    _onAddItem(String value) {
      store.dispatch(TodoAddAction(value));
    }
    
		// 新增的逻辑
    _onRemoveItem(int id) {
      store.dispatch(TodoRemoveAction(id));
    }
    
		// 新增的逻辑
    _onClearItems() {
      store.dispatch(TodoClearAction());
    }

    return ViewModel(
      lists: store.state.lists,
      addItem: _onAddItem,
      // 新增
      removeItem: _onRemoveItem,
      clearItems: _onClearItems,
      count: store.state.count,
      onIncrement: _onIncrement,
      onDecrement: _onDecrement,
    );
  }
}

# 添加页面按钮与回调

修改main.dart

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:redux_example/store/actions.dart';
import 'package:redux_example/store/reducers.dart';
import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/add.dart';
import 'package:redux_example/widgets/count_view.dart';
import 'package:redux_example/widgets/lists_item_view.dart';
import 'package:redux_example/widgets/view_model.dart';

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

class MyApp extends StatelessWidget {
  // 2.创建Store -> state
  final store = Store(appStateReducer, initialState: AppState());
  MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    // 3.使用StoreProvider
    return StoreProvider<AppState>(
      store: store,
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHome(),
      ),
    );
  }
}

class MyHome extends StatefulWidget {
  const MyHome({Key? key}) : super(key: key);

  
  State<MyHome> createState() => _MyHomeState();
}

class _MyHomeState extends State<MyHome> {
  
  Widget build(BuildContext context) {
    return StoreConnector<AppState, ViewModel>(
      builder: (context, viewModel) => Scaffold(
        appBar: AppBar(),
        body: SizedBox(
          height: MediaQuery.of(context).size.height,
          child: Column(
            children: [
              const Text('计数器的状态:'),
              // 4.添加按钮 -> dispatch -> action -> reducer -> 更新state
              // builder -> 最终返回的widgets
              // StoreConnector -> <初始的state类型, 最终输出到builder的类型>
              // converter -> 取用最近的Provider -> store -> 转化成我们需要的类型数据
              // builder -> 返回一个Widget -> 使用state -> viewModel
              CountView(viewModel),
              Add(viewModel),
              Expanded(
                child: ListsItemView(viewModel),
              ),
              ElevatedButton(
                onPressed: () {
                  viewModel.clearItems();
                },
                child: const Text('删除所有的Item项'),
              )
            ],
          ),
        ),
        // 1. 需要添加一个按钮 -> onPress -> new Action
        // 2. 需要添加action -> decrement
        // 3. 需要配置reducer -> dispatch -> new state
        floatingActionButton: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              child: const Icon(Icons.add),
              onPressed: () {
                // 触发dispatch方法 -> action -> Actions.increment
                viewModel.onIncrement();
              },
            ),
            const Padding(
              padding: EdgeInsets.only(top: 10.0),
            ),
            FloatingActionButton(
              child: const Icon(Icons.remove),
              onPressed: () {
                // 触发dispatch方法 -> action -> Actions.increment
                viewModel.onDecrement();
              },
            ),
          ],
        ),
      ),
      converter: (store) => ViewModel.create(store),
    );
  }
}

修改lists_item_view.dart:

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux_example/store/state.dart';
import 'package:redux_example/widgets/view_model.dart';

class ListsItemView extends StatelessWidget {
  final ViewModel viewModel;
  const ListsItemView(
    this.viewModel, {
    Key? key,
  }) : super(key: key);

  
  Widget build(BuildContext context) {
    return ListView(
      children: viewModel.lists
          .map((Item item) => ListTile(
                title: Text('${item.id}-${item.value}'),
            		// 添加一个按钮,添加对应的viewModel中的事件
                trailing: IconButton(
                  icon: const Icon(Icons.delete),
                  onPressed: () {
                    viewModel.removeItem(item.id);
                  },
                ),
              ))
          .toList(),
    );
  }
}