Flutter 状态管理

在 React Native 要做状态管理通常都是使用 Redux,这种方式非常好。幸运的是,在 Flutter 也支持使用 Redux。

flutter_redux 是一个支持在 Flutter 里使用 Redux 的库,但并不是使用 JS 版本的 Redux,而是使用 dart_redux,这是一个在 dart 上的 Redux 实现。

Redux 的基础知识就不多说了,在这里主要介绍如何在 Flutter 里使用 Redux 管理应用的数据状态。

先安装依赖:

dependencies:
  redux: ^3.0.0
  flutter_redux: ^0.5.2

在 Flutter Redux 里有一个必要知道的概念:

  • StoreProvider – 一个组件,它会将给定的 Redux Store 传递给它的所有子组件。
  • StoreBuilder – 一个子 Widget 组件,StoreProvider 它从 Action 中获取 Store 并将其传递给 Widget builder 函数。
  • StoreConnector – 从最近的 StoreProvider 祖先获取 Store 的子 Widget,使用给定的函数将其转换 Store 为 Action,并将其传递给函数。只要 Store 发出更改事件,Widget 就会自动重建。无需管理订阅!

Flutter Redux 的概念就像是 react-redux 一样。

下面来看一个简单的示例。

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

void main() {
    runApp(new FlutterReduxApp());
}

// 定义个 Types 枚举
enum Types { Increment }

// Reducer 处理
int Reducer(int state, action) {
    if (action == Types.Increment) {
        return state + 1;
    }

    return state;
}

// 创建一个 Action 类
class Action {
    dynamic store;
    dynamic dispatch;
    Action(store) {
        this.store = store;
        this.dispatch = store.dispatch;
    }

    // 某个 Action
    increment() {
        dispatch(Types.Increment);
    }
}

class FlutterReduxApp extends StatelessWidget {
    // 创建一个 store
    Action action;
    final store = new Store(Reducer, initialState: 0);

    @override
    Widget build(BuildContext context) {
        // 初始化 action
        if (action == null) {
            action = new Action(store);
        }

        return new MaterialApp(
            // 创建一个 Provider
            home: new StoreProvider(
                store: this.store,
                child: new Scaffold(
                    body: new Center(
                        child: new Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                                new Text('You have pushed the button this many times:'),
                                // 连接器,连接之后可以获取数据,在 builder 里渲染内容
                                new StoreConnector<int, String>(
                                    converter: (store) => store.state.toString(),
                                    builder: (context, count) => new Text(count),
                                )
                            ],
                        ),
                    ),
                    floatingActionButton: new FloatingActionButton(
                        onPressed: action.increment, // action
                        child: new Icon(Icons.add),
                    ),
                ),
            ),
        );
    }
}

在需要改变状态的组件里使用 StoreConnector 连接数据(避免对整个内容做一次性的连接,导致整个渲染)。

在这里 Store 是一个泛型类,在初始化时请给它一个类型,否则就是 Store<int>。很多时候 State 是一个复杂的结构,并不是一个简单的 int 类型,那么如何适配?

// Reducer 处理
State reducer(State state, action) {
    if (action == Types.Increment) {
        state.count++;
        return state.newState(state);
    }

    return state;
}

class State {
    List<String> list;
    String name;
    int count;
    State(this.list, this.name, this.count);
    // 返回一个新的实例
    newState(State obj) {
        this.name = obj.name;
        this.list = obj.list;
        this.count = obj.count;
        return new State(obj.list, obj.name, obj.count);
    }
}

// 创建一个 store
final store = new Store<State>(reducer, initialState: new State([], 'abc', 0));

// 连接器,连接之后可以获取数据,在 builder 里渲染内容
new StoreConnector<State, String>(
    converter: (store) => store.state.count.toString(),
    builder: (context, count) => new Text(count),
),

StoreConnector 里的 converter 一定要是返回 String 吗?目前为止是的,因为大多数的在 Widget 上都是使用字符串渲染。

此外,可以使用 StoreConnector 对某个组件进行数据的连接,对于不变化的组件那就没必要连接了。

在 Action 初始了一个 store 和 dispatch,这样就可以在方法里直接引用 store 和 dispatch。

// 创建一个 Action 类
class Action {
    Store<State> store;
    dynamic dispatch;
    Action(store) {
        this.store = store;
        this.dispatch = store.dispatch;
    }

    // 某个 Action
    increment() {
        dispatch(Types.Increment);
    }
}

中间件

中间件是一个非常有用的东西,它可以在 Reducer 操作之前做一些拦截或者记录工作。

Store 的第三个参数是一个接受中间件的参数,我们可以通过下面的定义中间件方式,为 Store 添加中间件。

loggingMiddleware<T>(Store<T> store, action, NextDispatcher next) {
  print('${new DateTime.now()}: $action');
  next(action);
}

final store = new Store<int>(
    counterReducer,
    initialState: 0,
    middleware: [loggingMiddleware],
);

异步操作

在 action 里使用 async 即可,在等待异步操作完成后再调用 dispatch。

// 某个 Action
increment() async {
    // 模拟某个异步操作
    await new Future.delayed(new Duration(seconds: 1));
    // 完成后 dispatch
    dispatch(Types.Increment);
}

传递数据

在调用 dispatch 的时候 action 是一个 dynamic 类型,在上面是没有传递数据的,而数据的变化在 Reducer 里。

那么如何传递数据呢?在 action 里是一个 dynamic 类型,因此可以把 action 写成 Map 类型,在 Map 里获取 type 和 data。

// Reducer 处理
State reducer(State state, action) {
    if (action['type'] == Types.Increment) {
        state.count = action['data'];
        return state.newState(state);
    }

    return state;
}

// 某个 Action
increment() async {
    // 模拟某个异步操作
    await new Future.delayed(new Duration(seconds: 1));
    // 完成后 dispatch
    dispatch({
      'type': Types.Increment,
      'data': store.state.count + 1,
    });
}

这样就可以把数据从 Action 传递到 Reducer 了。

精简代码

在上面的代码里,在 Action 里得到最新的状态只,在 Reducer 里又需要进行一次赋值,这样会多出很多重复的代码。现在把 Action 变成一个 newState 函数,在函数里更新最新的状态,在 Reducer 里只创建新的实例即可,这样可以节省很多代码,Types 也不需要写了。

// Reducer 处理
State reducer(State state, action) {
    if (action is Function) {
        var s = state.newState(action(state));
        if (s != null) return s;
    }
    return state;
}

// 某个 Action
increment() async {
    // 模拟某个异步操作
    await new Future.delayed(new Duration(seconds: 1));
    // 完成后 dispatch

    dispatch((State state) {     // 要更新的状态
        state.count++;
        return state;
    });
}

不知看懂了没有,实际上就是利用传递函数与上下文的特点。

    原文作者:iwakevin
    原文地址: https://www.jianshu.com/p/54ba57df50c7
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞