状态管理

setState

setStateflutter 内置的方法

通常有 3 种管理状态的方式

  • 管理自身
  • 父组件管理
  • 混搭管理(将上面两种结合起来即可)

管理自身

class CounterWidget extends StatefulWidget {
  const CounterWidget({super.key});

  
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _counter = 0;

  
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('$_counter'),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _counter++;
            });
          },
          child: const Text('Increment'),
        )
      ],
    );
  }
}

父组件管理

class TapWidget extends StatefulWidget {
  const TapWidget({super.key});

  
  State<TapWidget> createState() => _TapWidgetState();
}

class _TapWidgetState extends State<TapWidget> {
  bool _active = false;
  void _handleTapboxChanged(bool newValue) {
    setState(() {
      _active = newValue;
    });
  }

  
  Widget build(BuildContext context) {
    return SizedBox(
      child: TapboxWidget(
        active: _active,
        onChanged: _handleTapboxChanged,
      ),
    );
  }
}

class TapboxWidget extends StatelessWidget {
  const TapboxWidget({super.key, this.active = false, required this.onChanged});

  final bool active;
  final ValueChanged<bool> onChanged;

  void _handleTap() {
    onChanged(!active);
  }

  
  Widget build(BuildContext context) {
    return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
      Text(
        active ? 'Active' : 'Inactive',
      ),
      ElevatedButton(
        onPressed: _handleTap,
        child: const Text('Tap me'),
      )
    ]);
  }
}

flutter_redux

各种包太多了,为了方便选择了 flutter_redux

安装

flutter pub add flutter_redux

配置 pubspec.yaml 文件

dependencies:
    flutter_redux: ^0.10.0
    redux: ^5.0.0

创建 state、action、reducer

此处将其放在一个文件内

// 创建 state
class UserState {
    String nickname;
    String age;
    String birthday;

    UserState({
        this.nickname,
        this.age,
        this.birthday,
    });

    // 初始化
    factory UserState.initial() => UserState(nickname: '', age: '', birthday: '');

    
    String toString() {
        return 'nickname: $nickname, age: $age, birthday: $birthday}';
    }
}

// 创建 action
enum UserAction{
    getProfile,
    setProfile
}

// 创建 reducer
UserState userReducer(UserState prevState, action) {
    UserState state
    switch (action['type']) {
        case UserAction.getProfile:
            state = prevState;
            break;
        case UserAction.setProfile:
            final data = action['payload'];
            state = UserState(
                nickname: data['nickname'],
                age: data['age'],
                birthday: data['birthday']
            );
            break;
        default:
            state = prevState;
            break;
    }
    return state;
}

创建 store

如无特殊需求,通常和 stateactionreducer 在同一个文件内创建

import 'package:redux/redux.dart';

void main() {
    Store<UserState> store = Store<UserState>(
            userReducer,
            initialState: UserState.initial()
        );
    runApp(MyApp(store: store));
}

store 与根组件关联

import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

void main() {
    Store<UserState> store = Store<UserState>(
            userReducer,
            initialState: UserState.initial()
        );
    runApp(MyApp(store: store));
}

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

    const MyApp({Key? key, required this.store}) : super(key: key);

    
    Widget build(BuildContext context) {
        return StoreProvider<TokenState>(
            store: store,
            child: MaterialApp(
                title: 'Flutter Demo',
                // ...
            )
        )
    }
}

store 与其他组件关联

import 'package:flutter_redux/flutter_redux.dart';

class AppStart extends StatelessWidget {
  const AppStart({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            StoreConnector<TokenState, TokenState>(
              // 这里有回调几个事件,具体查看文档
              onWillChange: onReduxChange, // 状态改变时的回调
              onInitialBuild: afterBuild, // 初始化时的回调
              builder: (context, tokenState) {
                return Text('token: ${tokenState.token}');
              },
              converter: (store) => store.state,
            ),
            StoreConnector<TokenState, VoidCallback>(
              // 需要执行 dispatch 时的类型不同
              builder: (context, callback) {
                return ElevatedButton(
                  onPressed: callback,
                  child: const Text('change token'),
                );
              },
              // 这里要注意一下语法,需要返回一个函数,否则 onPressed 无法正常执行
              converter: (store) {
                return () => store.dispatch(
                      {
                        'type': TokenAction.setToken,
                        'payload': Random().nextInt(100).toString()
                      },
                    );
              },
            )// 当执行 callback 需要传参时,将 StoreConnector 的第二个参数类型改为自定义参数即可,此时需要注意调用 callback 时的写法
            StoreConnector<TokenState, dynamic>(
              builder: (context, callback) {
                return ElevatedButton(
                  onPressed: () {
                    callback('123')
                  },
                  child: const Text('change token'),
                );
              },
              // 这里要注意一下语法,需要返回一个函数,否则 onPressed 无法正常执行
              converter: (store) {
                return (data) => store.dispatch(
                      {
                        'type': TokenAction.setToken,
                        'payload': Random().nextInt(100).toString()
                      },
                    );
              },
            )
          ],
        ),
      ),
    );
  }

  void onReduxChange(TokenState? prevState, TokenState state) {
    print('prevState: $prevState');
    print('state: $state');
  }

  void afterBuild(TokenState state) {
    print('afterBuild: $state');
  }
}

StoreConnector 主要是起一个数据转化的作用,可以在组件赋值之前做一些数据转化操作。有两个泛型参数

  • 原本的 store 类型,
  • 需要转换的参数类型,它可以是类,方法,基本数据类型等都可以

需要声明两个方法:

  • builder 接收数据,构建组件交互,第二个参数就是我们声明的转化参数的返回值,返回值类型视传入的泛型类型而定。
  • converter 起数据转化作用,返回值会直接传递到 builder 方法的第二个参数中去。

多 store 使用

当使用多 store 时,需要多次调用 StoreProvider 方法,然后使用时根据传入的泛型类型区分是哪个 store

根组件多 store

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

void main() {
  final store1 = new Store<int>(counterReducer, initialState: 0);
  final store2 = new Store<String>(textReducer, initialState: "");

  runApp(new MyApp(store1, store2));
}

class MyApp extends StatelessWidget {
  final Store<int> store1;
  final Store<String> store2;

  MyApp(this.store1, this.store2);

  
  Widget build(BuildContext context) {
    // 注意第一个泛型变量
    return new StoreProvider<int>(
      store: store1,
      child: new StoreProvider<String>(
        store: store2,
        child: new MaterialApp(
          title: 'Flutter Redux Demo',
          home: new MyHomePage(),
        ),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Flutter Redux Demo"),
      ),
      body: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          // 注意第一个泛型变量
          new StoreConnector<int, String>(
            converter: (store) => store.state.toString(),
            builder: (context, count) {
              return new Text(
                "Counter: $count",
                style: new TextStyle(fontSize: 20.0),
              );
            },
          ),
          // 注意第一个泛型变量
          new StoreConnector<String, String>(
            converter: (store) => store.state,
            builder: (context, text) {
              return new Text(
                "Text: $text",
                style: new TextStyle(fontSize: 20.0),
              );
            },
          ),
        ],
      ),
      floatingActionButton: new Column(
        mainAxisAlignment: MainAxisAlignment.end,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: <Widget>[
          new FloatingActionButton(
            onPressed: () {
              // 注意第一个泛型变量
              StoreProvider.of<int>(context).dispatch(new IncrementAction());
            },
            child: new Icon(Icons.add),
          ),
          new Padding(
            padding: new EdgeInsets.only(top: 16.0),
            child: new FloatingActionButton(
              onPressed: () {
                // 注意第一个泛型变量
                StoreProvider.of<String>(context).dispatch(new ChangeTextAction());
              },
              child: new Icon(Icons.text_fields),
            ),
          ),
        ],
      ),
    )
  }
}

子组件多 store

class MyChildWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return new StoreProvider<int>(
      // 仅在此处不同,直接在此处创建即可
      store: new Store<int>(counterReducer, initialState: 0),
      child: new StoreProvider<String>(
        store: new Store<String>(textReducer, initialState: ""),
        child: new Column(
          children: <Widget>[
            new StoreConnector<int, String>(
              converter: (store) => store.state.toString(),
              builder: (context, count) {
                return new Text(
                  "Counter: $count",
                  style: new TextStyle(fontSize: 20.0),
                );
              },
            ),
            new StoreConnector<String, String>(
              converter: (store) => store.state,
              builder: (context, text) {
                return new Text(
                  "Text: $text",
                  style: new TextStyle(fontSize: 20.0),
                );
              },
            ),
            new RaisedButton(
              onPressed: () {
                StoreProvider.of<int>(context).dispatch(new IncrementAction());
              },
              child: new Text("Increment"),
            ),
            new RaisedButton(
              onPressed: () {
                StoreProvider.of<String>(context).dispatch(new ChangeTextAction());
              },
              child: new Text("Change Text"),
            ),
          ],
        ),
      ),
    );
  }
}

StoreBuilder

有时候需要在一个组件内既获取 store 又执行 dispatch,这时候推荐使用 StoreBuilder 组件

class MyWidget extends StatefulWidget {
  
  Widget build(BuildContext context) {
    return StoreBuilder<VideoState>(
      builder: (context, store) {
        print('这是store:$store');
        return ElevatedButton(
            onPressed: () {
                // 这里可以执行 dispatch
                store.dispatch(
                {
                    'type': VideoAction.setLoading,
                    'payload': false,
                },
                );
            },
            child: Text('123'),
        ),
        
      },
    );
  }
}

中间件

用于拦截 action -> reducer 的过程,将其变为 action -> middleware -> reducer

该操作可以实现如异步 action、日志输出、异常报告等功能

// 创建
Middleware<AppState> appMiddleware = (Store<AppState> store, action, NextDispatcher next) {
    // 执行需要的操作
     print('dispatching ${action.runtimeType}');

     // 将操作传递给下一个中间件或store
     next(action);
};

// 使用
final store = Store<AppState>(
    appReducer,
    initialState: AppState.initial(),
    middleware: [appMiddleware]
)
  • 当使用多个中间件时,它们将按顺序执行
  • 在中间件中调用 dispatch 发送其他 action 时,会递归先处理新发出的 action
  • 位于 next 后面的代码,会在所有中间件执行完毕后,以中间件组的倒序的方式来执行

异步操作

需要用到 redux_thunk

import 'package:redux_thunk/redux_thunk.dart';

// 注册中间件
Store<AppState> store = Store<AppState>(
    tokenReducer,
    initialState: AppState.init(),
    middleware: [thunkMiddleware], // thunkMiddleware 这个中间件是 redux_thunk 包自带的
);

// 创建异步 action
// params 是为了接收调用时候的传参,如果无需传参则直接返回一个异步函数即可
ThunkAcion<AppState> action(Map<String, String> params) => (Store<AppState> store) async {
    var res = await login(params);
    store.dispatch({'type': AppAction, 'payload': res.data});
};

// 调用
StoreConnector<AppState, VoidCallback>(
    builder: (context, callback) {
        return ElevatedButton(
            onPressed: callback,
            child: const Text('Login'),
        );
    },
    converter: (store) {
        return () => store.dispatch(
            tokenAction({'username': 'admin', 'password': '123456'}));
    },
)

distinct 属性

store 有个 distinct 属性,用于设置 store 改变的时候是否重新渲染,返回 true 表示不渲染。

对于 store 中有多个属性,但只改变其中某个属性值时不需要重新渲染的情况下尤其有用

该操作通常需要搭配自定义 == 操作符和 hashCode 一起使用,否则无效

class VideoState {
  bool isLoading;
  Video? video;

  VideoState({
    this.isLoading = false,
    this.video,
  });

  factory VideoState.initial() => VideoState(isLoading: false, video: null);

  // 这里的 other 根据需要判断返回 true 或者 false
  bool operator ==(Object? other) {
    if (other == null || !(other is VideoState)) {
      return false;
    }
    return other.video == this.video;
  }

  // hashCode 也要重写
  
  get hashCode => video.hashCode;
}

Last Updated:
Contributors: af