前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[- Flutter 数据&状态篇 -] redux

[- Flutter 数据&状态篇 -] redux

作者头像
张风捷特烈
发布2020-04-30 15:14:58
9390
发布2020-04-30 15:14:58
举报

本文比较渣,有待重构....

今天的任务是将昨的代码用redux整理一下。 在此之前先说统一几个名词在本文中的叫法。本文源码见github

代码语言:javascript
复制
store       : 仓库
dispatch    : 分发
action      : 动作
reducer     : 分解器
connector   : 连接器
provider    : 供应器
converter   : 转换器
builder     : 构造器

依赖: flutter_redux: ^0.5.3

1.初始项目的Redux化

大家应该都还记得初始项目吧,下面是它的梳理图,磨刀不误砍柴工。 我打算从它开始入手,向你简单介绍redux是什么?


1.1:分析行为及变化

很简单,行为是点击,变化是数字的自增长。 关于reducer,不想是什么纯不纯,在我看来它就是一个独立的逻辑单元, 不依靠外界存活,在逻辑上便可存在:给定一个输入就会返回一个预期的输出

代码语言:javascript
复制
enum Actions {
  increment//定义增加行为
}

//使用counterReducer将行为从类中抽离分解,成为独立逻辑单元
int counterReducer(int input, dynamic action) {
  var output;
  switch(action){
    case Actions.increment:
      output=input+1;
      break;
  }
  return output;
}

1.2:新建ReduxPage组件

redux核心之一便是Store,是一个仓库用来储存,供应,分发。 返回一个仓库提供器,它是一个Widget,需要store和child属性。

代码语言:javascript
复制
class ReduxPage extends StatelessWidget {
  final Store<int> store;
  ReduxPage({Key key, this.store,}) : super(key: key);
  
  return  StoreProvider<int>(
    store: store,
    child: child,
  );
}

1.3:现在的焦点在于孩子是如何构建的

这里为了看得清楚些,将countTextfab两个与状态有关的组件抽离一下

代码语言:javascript
复制
var child = MaterialApp(
  theme: ThemeData(
    primarySwatch: Colors.blue,
  ),
  home: Scaffold(
    appBar: AppBar(
      title: Text("Flutter Redux Demo"),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            'You have pushed the button this many time'
          ),
          countText//显示数字的Text
        ],
      ),
    ),
    floatingActionButton: fab,//点击的按钮
  ),
);
  • 显示数字的Text:countText

如果你想要到仓库拿东西,你需要什么?钥匙呗。StoreConnector仓库连接器就是这把钥匙 converter转换器中回调出store对象,你就可以通过store去取值了,通过构造器生成组件返回出去

代码语言:javascript
复制
var countText= StoreConnector<int, String>(
  converter: (store) => store.state.toString(),//转换器,获取仓库,从仓库拿值
  builder: (context, count) {//构造器,构建Widget
    return Text(
      count,
      style: Theme.of(context).textTheme.display1,
    );
  },
);
  • 处理动作的按钮

处理动作也是需要仓库,使用进行分发(dispatch)相应动作(action) 在构造器中,你就可以使用该动作逻辑了。

代码语言:javascript
复制
var fab= StoreConnector<int, VoidCallback>(
  converter: (store) {
    return () => store.dispatch(Actions.increment);//分发动作
  },
  builder: (context, callback) {//构造器,使用动作逻辑
    return FloatingActionButton(
      onPressed: callback,
      tooltip: 'Increment',
      child: Icon(Icons.add),
    );
  },
);

这里将动作方成为攻方,响应方成为受方,下面的图阐释了两方Widget如何构建


1.4:仓库对象的构建

可以说这核心便是仓库store了,看一下对象如何生成

代码语言:javascript
复制
void main() {
  final store =  Store<int>(counterReducer, initialState: 0);
  runApp(ReduxPage(
    store: store,
  ));
}
复制代码

2.redux优势

也许你会说:"感觉也不咋地啊,感觉好麻烦。"

2.1:增加一个功能时

比如我想要点一下加10该怎么办?使用redux你需要定义一个行为,及响应。 在行为分发时修改行为即可。也许你说我不用redux,改行就行了。如果逻辑非常多怎么办 之后又要改回来怎么办?抽象出一个行为来管理逻辑切换起来是非常方便的 而且想要修改直接在reducer中进行即可,就避免了污染封装的组件源码。

代码语言:javascript
复制
enum Actions{
  increment,
  increment10
}

int counterReducer(int input, dynamic action) {
  var output;
  switch(action){
    case Actions.increment:
      output=input+1;
      break;
    case Actions.increment10:
      output=input+10;
      break;
  }
  return output;
}

var fab= StoreConnector<int, VoidCallback>(
      converter: (store) {
        return () => store.dispatch(Actions.increment10);
      },

2.2:全局的状态共享

另一个界面如何轻松享有上个界面的数据,这是个很大的问题。 当然可以通过构造传参,但这显然十分麻烦,不仅乱,而且还要接收个参数。

代码语言:javascript
复制
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';

class SecondPage extends StatelessWidget {
  SecondPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    var text = StoreConnector<int, String>(//直接从状态取值
      converter: (store) => store.state.toString(),
      builder: (context, count) {
        return Text(
          count.toString(),
          style: Theme.of(context).textTheme.display1,
        );
      },
    );

    return Scaffold(
      appBar: AppBar(
        title: Text("SecondPage"),
      ),
      body: Align(
        alignment: Alignment.topCenter,
        child: text,
      ),
    );
  }
}

  • ReduxPage中为文字添加点击跳转到SecondPage
代码语言:javascript
复制
Builder _skipToSecondPage(StoreConnector<int, String> countText) {
  return Builder(
    builder: (context) =>
        InkWell(child: countText, onTap: () {
          Navigator.of(context)
              .push(MaterialPageRoute(builder: (BuildContext context) {
            return SecondPage();
          }));
        },),
  );
}

3.对昨天TodoList的改造

还是一样的界面效果。


3.1:定义Todo描述类
代码语言:javascript
复制
class Todo {
  String sth; //待做事项
  bool done;//是否已完成
  Todo({this.sth, this.done}); //是否已做完
}

3.2:定义状态类和动作及变化

昨天分析了有个有三个状态和四个动作

代码语言:javascript
复制
class TodoState {
  List<Todo> todos; //列表数据
  String text; //当前输入文字
  ShowType showType;//显示类型
  TodoState({this.todos, this.text, this.showType}); //显示类型
}

enum Acts {
  add, //添加到todo
  selectAll, //筛选所有
  selectTodo, //筛选待完成
  selectDone, //筛选已完成
}

TodoState todoReducer(TodoState input, dynamic action) {
  switch (action) {
    case Acts.add:
      if (input.text != null && input.text != "") {
        input.todos.add(Todo(sth: input.text, done: false));
        input.text = "";
      }
      break;
    case Acts.selectAll:
      input.showType=ShowType.all;
      break;
    case Acts.selectTodo:
      input.showType=ShowType.todo;
      break;
    case Acts.selectDone:
      input.showType=ShowType.done;
      break;
  }
  return input;
}


final todoListStore = Store<TodoState>(todoReducer,
      initialState://初始状态
      TodoState(todos: <Todo>[], text: "", showType: ShowType.all));

3.3:组件的封装

和上面一样,使用StoreConnector来从仓库拿资源,只不过这里资源是TodoState对象 动作的话,作为攻方,依旧是那回调来执行相应动作。

代码语言:javascript
复制
class TodoList extends StatefulWidget {
  final Store<TodoState> store;

  TodoList({
    Key key,
    this.store,
  }) : super(key: key);

  @override
  _TodoListState createState() => _TodoListState();
}

class _TodoListState extends State<TodoList> {

  @override
  Widget build(BuildContext context) {

    var textField= StoreConnector<TodoState, TodoState>(
      converter: (store) =>store.state,//转换器,获取仓库,从仓库拿值
      builder: (context, state) {//构造器,构建Widget
        return TextField(
          controller: TextEditingController(text: state.text),
          keyboardType: TextInputType.text,
          textAlign: TextAlign.start,
          maxLines: 1,
          cursorColor: Colors.black,
          cursorWidth: 3,
          style: TextStyle(
              fontSize: 16, color: Colors.lightBlue, backgroundColor: Colors.white),
          decoration: InputDecoration(
            filled: true,
            fillColor: Colors.white,
            hintText: '添加一个待办项',
            hintStyle: TextStyle(color: Colors.black26, fontSize: 14),
            contentPadding: EdgeInsets.only(left: 14.0, bottom: 8.0, top: 8.0),
            focusedBorder: OutlineInputBorder(
              borderSide: BorderSide(color: Colors.white),
              borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
            ),
            enabledBorder: UnderlineInputBorder(
              borderSide: BorderSide(color: Colors.white),
              borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
            ),
          ),
         onChanged: (str){
           state.text=str;
         },
        );
      },
    );

    var btn = StoreConnector<TodoState, VoidCallback>(
      converter:(store) {
      return () => store.dispatch(Acts.add);//分发动作
    },
      builder: (context, callback) {
        return RaisedButton(
          child: Icon(Icons.add),
          padding: EdgeInsets.zero,
          onPressed: (){
            callback();
            FocusScope.of(context).requestFocus(FocusNode());
          });
      },
    );

    var inputBtn = Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Container(
          child: textField,
          width: 200,
        ),
        ClipRRect(
          borderRadius: BorderRadius.only(
              topRight: Radius.circular(10), bottomRight: Radius.circular(10)),
          child: Container(
            child: btn,
            width: 36,
            height: 36,
          ),
        ),
      ],
    );

    var listInfo = [
      ["全部", Acts.selectAll],
      ["已完成", Acts.selectDone],
      ["未完成", Acts.selectTodo],
    ];

    var op = Row(//操作按钮
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: listInfo.map((e) {
        return StoreConnector<TodoState, VoidCallback>(
          converter: (store) {
            return () => store.dispatch(e[1]);
          },
          builder: (context, callback) {
            return RaisedButton(
              onPressed: callback,
              child: Text(e[0]),
              color: Colors.blue,
            );
          },
        );
      }).toList(),
    );

    var listView = StoreConnector<TodoState, TodoState>(
        converter: (store) => store.state, //转换器,获取仓库,从仓库拿值
        builder: (context, state) {
          var result;
          //构造器,构建Widget
          switch(state.showType){
            case ShowType.all:
              result= formList(state.todos);
              break;
            case ShowType.todo:
              result= formList(List.of( state.todos.where((e)=>!e.done)));
              break;
            case ShowType.done:
              result= formList(List.of( state.todos.where((e)=>e.done)));
              break;
          }
         return result;
        });

    return StoreProvider<TodoState>(
      store: widget.store,
      child: Column(
        children: <Widget>[inputBtn, op, Expanded(child: listView)],
      ),
    );
  }

  Widget formList(List<Todo> todos) {
    return ListView.builder(
      itemCount: todos.length,
      padding: EdgeInsets.all(8.0),
      itemExtent: 50.0,
      itemBuilder: (BuildContext context, int index) {
        var key = todos[index].sth;
        var value = todos[index].done;
        var text = Align(
          child: Text(
            key,
            style: TextStyle(
                decorationThickness: 3,
                decoration:
                    value ? TextDecoration.lineThrough : TextDecoration.none,
                decorationColor: Colors.blue),
          ),
          alignment: Alignment.centerLeft,
        );

        return Card(
          child: Row(
            children: <Widget>[
              Checkbox(
                onChanged: (b) {
                  todos[index].done = b;
                  setState(() {});
                },
                value: todos[index].done,
              ),
              text
            ],
          ),
        );
      },
    );
  }
}

3.3:ViewModel的使用

可以看到StoreConnector中有两个泛型,其中第二个命名为ViewModel 现在实现一下,在已完成和未完成按钮点击后将CheckBox隐藏,这时就非常方便了。

代码语言:javascript
复制
--->[1.加入一个状态]----
class TodoState {
  List<Todo> todos; //列表数据
  String text; //当前输入文字
  ShowType showType;//显示类型
  bool showBox=true;//是否显示checkBox
  TodoState({this.todos, this.text, this.showType,this.showBox}); //显示类型
}

--->[2.在todoReducer中直接修改showBox状态]----
case Acts.selectAll:
  input.showBox=true;
  input.showType=ShowType.all;
  break;
case Acts.selectTodo:
  input.showType=ShowType.todo;
  input.showBox=false;
  break;
case Acts.selectDone:
  input.showType=ShowType.done;
  input.showBox=false;
  break;
  

---->[提取出checkBox组件代码]----
var checkBox=StoreConnector<TodoState, CheckBoxViewModel>(
  converter: (store) {
    return CheckBoxViewModel(store,index);
  },
  builder: (context, model) {
    return Offstage(child:Checkbox(
        value: model.done,
        onChanged: model.onClick) ,
      offstage: !model.store.state.showBox,) ;
  },
);

---->[定义CheckBox模型]----
class CheckBoxViewModel {
  final Store store;
  final int index;

  void Function(bool check) onClick;

  CheckBoxViewModel(this.store,this.index) {
    onClick = (check) {
      store.dispatch(Acts.check);
      store.state.todos[index].done = check;
    };
  }

  get done{
    return store.state.todos[index].done;
  }
}

这样就可以很容易对状态进行更改,否则,要在TodoList中直接改会很费劲。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年07月27日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.初始项目的Redux化
    • 1.1:分析行为及变化
      • 1.2:新建ReduxPage组件
        • 1.3:现在的焦点在于孩子是如何构建的
          • 1.4:仓库对象的构建
          • 2.redux优势
            • 2.1:增加一个功能时
              • 2.2:全局的状态共享
              • 3.对昨天TodoList的改造
                • 3.1:定义Todo描述类
                  • 3.2:定义状态类和动作及变化
                    • 3.3:组件的封装
                      • 3.3:ViewModel的使用
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档