7

本文详细讲述怎样在flutter中集成和使用redux,关于redux的概念、原理和实现,读者可自行百度,本文不做累述。

redux库

https://pub.flutter-io.cn/pac...

集成redux

修改项目根目录下的pubsepc.yaml,添加依赖

flutter_redux: ^0.5.2

clipboard.png

基本集成

创建项目

先使用flutter命令

flutter create flutter_use_redux

创建一个flutter默认的Helloworld项目

目标:改造flutter的Helloworld项目使用redux实现

原来的代码如下,去掉了注释,以便显得更短...

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}
这一步的目标是将原来的程序改成用redux来实现。

我们知道redux是整个应用程序使用唯一一个store来管理整个应用程序的状态的。那么这里我们先定义一个store,上述的程序里面的“状态”为一个计数器,所以我们先定义一个int类型的状态,初始值为0:

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

int mainReducer(int state, dynamic action){
  return state;
}

void main() {
  Store<int> store = new Store<int>(mainReducer,initialState: 0);
  runApp(new MyApp());
}

这里的mainReducer为应用程序的主reducer,我们知道,reducer是一个函数,它接受一个状态(State),并且“必须”返回一个状态(State),如果状态没有变化,那么应该返回原来的状态。

传递State

按照上述程序的逻辑,当点击下面的按钮的时候,计数器+1。

那么我们首先我们需要将这个计数器的状态传递给中间的Text,当点击按钮的时候,需要修改状态。

在Redux中修改状态,实际上是使用Store.dispatch这个方法,分发一个“Action",由reducer这个函数对”Action“进行解析,并返回新的State。

传递状态,使用StoreConnector这个Widget

继续修改我们的程序:



enum Actions{
  Increase,
}

int mainReducer(int state, dynamic action){

  if(Actions.Increase==action){
    return state + 1;
  }

  return state;
}

void main() {
  Store<int> store = new Store<int>(mainReducer,initialState: 0);
  runApp(new MyApp(store: store,));
}

class MyApp extends StatelessWidget {

  final Store<int> store;

  MyApp({this.store});

  @override
  Widget build(BuildContext context) {
    return new StoreProvider(store: store, child: new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home:  new StoreConnector(builder: (BuildContext context,int counter){
        return new MyHomePage(title: 'Flutter Demo Home Page',counter:counter);
      }, converter: (Store<int> store){
        return store.state;
      }) ,
    ));
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title,this.counter}) : super(key: key);
  final String title;
  final int counter;

  @override
  Widget build(BuildContext context) {

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$counter',
              style: Theme
                  .of(context)
                  .textTheme
                  .display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: () {

        },
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}


这步跨度有点大,解释一下:

  • 在mainReducer中增加对action的处理:
  if(Actions.Increase==action){
    return state + 1;
  }
  • 原来的MaterialApp外面套一层StoreProvider
    这一步是官方要求的,如果不做会报错。
  • 将MyHomePage改成 StatelessWidget
    由于将状态放在了Store中,所以无需使用StatefulWidget
  • 传递状态
new StoreConnector(builder: (BuildContext context,int counter){
        return new MyHomePage(title: 'Flutter Demo Home Page',counter:counter);
      }, converter: (Store<int> store){
        return store.state;
      }) 

这里实现了真正的将Store中的状态传递到组件MyHomePage中,一定需要使用converter将Store中的状态转变成组件需要的状态,这里的builder函数的第二个参数就是converter函数的返回值。

绑定Action
floatingActionButton: new StoreConnector<int,VoidCallback>(builder: ( BuildContext context,VoidCallback callback ){
        return new FloatingActionButton(
          onPressed:callback,
          tooltip: 'Increment',
          child: new Icon(Icons.add),
        );

      }, converter: (Store<int> store){
        return ()=>store.dispatch(Actions.Increase);
      }),

这里使用StoreConnector<int,VoidCallback>这个Widget绑定Action,注意泛型必须写对,不然会报错,第一个泛型为Store存储的类型,第二个泛型为converter的返回值得类型。

到这里,就改造完毕了,和官方的helloworld功能一样。

clipboard.png

敢不敢复杂一点?例子:登录

上面这个例子中,状态为一个int,实际项目当然没有那么简单,我们需要规划整个app的状态,这里我们使用AppState这个类来管理


/// 这个类用来管理登录状态
class AuthState{
  bool isLogin;     //是否登录
  String account;   //用户名
  AuthState({this.isLogin:false,this.account});
}

/// 管理主页状态
class MainPageState{
  int counter;
  MainPageState({this.counter:0});
}

/// 应用程序状态
class AppState{
  AuthState auth;     //登录
  MainPageState main; //主页

  AppState({this.main,this.auth});
}

那么下面的程序要跟着将所有的int替换成AppState,并修改reducer的代码,这里就只贴重要的代码了:


AppState mainReducer(AppState state, dynamic action){

  if(Actions.Increase==action){
    state.main.counter += 1;
  }

  return state;
}

void main() {
  Store<AppState> store = new Store<AppState>(mainReducer,initialState: new AppState(
    main: new MainPageState(),
    auth: new AuthState(),
  ));
  runApp(new MyApp(store: store,));
}
增加登录逻辑

改造一下MyHomePage,新怎一些状态

 MyHomePage({Key key, this.title,this.counter,this.isLogin,this.account}) : super(key: key);
  final String title;
  final int counter;
  final bool isLogin;
  final String account;

新增登录ui的判断

    /// 有登录,展示你好:xxx,没登录,展示登录按钮
            isLogin ?  new RaisedButton(onPressed: (){

            },child: new Text("您好:$account,点击退出"),) :new RaisedButton(onPressed: (){

            },child: new Text("登录"),)

为了快速看到效果,这里将登录输入用户名省略掉。。

改造reducer


AppState mainReducer(AppState state, dynamic action){


  print("state charge :$action ");
  if(Actions.Increase==action){
    state.main.counter+=1;
  }

  if(Actions.LogoutSuccess == action){

    state.auth.isLogin = false;
    state.auth.account = null;
  }

  if(action is LoginSuccessAction){
    state.auth.isLogin = true;
    state.auth.account = action.account;
  }

  print("state changed:$state");

  return state;
}

改造build:

Widget loginPane;
    if(isLogin){
      loginPane = new StoreConnector(
          key:new ValueKey("login"),
          builder: (BuildContext context,VoidCallback logout){
        return new RaisedButton(onPressed: logout,child: new Text("您好:$account,点击退出"),);
      }, converter: (Store<AppState> store){
        return ()=>
            store.dispatch(
                Actions.LogoutSuccess
            );
      });
    }else{
      loginPane =  new StoreConnector<AppState,VoidCallback>(
          key:new ValueKey("logout"),
          builder: (BuildContext context,VoidCallback login){
        return new RaisedButton(onPressed:login,child: new Text("登录"),);
      }, converter: (Store<AppState> store){
        return ()=>
            store.dispatch(
                new LoginSuccessAction(account: "xxx account!")
            );
      });
    }

附件:

代码:
https://github.com/jzoom/flut...

如有疑问,请加qq群854192563讨论


jzoom
1.2k 声望334 粉丝

A simple way to solving problems is using tools like docker/Spring boot/React Native/React/Vue… Technology should not become a bottleneck in thinking.