针对flutter的项目暂时有Bloc、Mobx、scoped_model、fish_redux、Provider 五种框架,下面我们介绍一下:
提出疑问:
1.flutter是什么?
2.这些框架是解决什么问题的,为什么会需要这种框架?/响应式状态管理器/响应式编程是一种面向数据流和变化传播的编程范式
3.每个框架有什么优缺点?
4.我们应该使用什么框架比较好?
问题1:flutter是什么?
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作、跨平台运行。
问题2:这些框架是解决什么问题的?
这些框架都是来一种状态管理框架。响应式的编程框架中都会有一个永恒的主题——“状态(State)管理”,无论是在React/Vue(两者都是支持响应式编程的Web开发框架)还是Flutter中,他们讨论的问题和解决的思想都是一致的。一般的原则是:如果状态是组件私有的,则应该由组件自己管理;如果状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理。
问题3:每个框架都是怎么运行的?
框架一:scoped_model
一种最单纯的状态管理工具。
1.创建model
数据刷新需要调用 notifyListeners方法
import 'package:scoped_model/scoped_model.dart';
class CountModel extends Model{
int _count = 0;
get count => _count;
void increment(){
_count++;
notifyListeners();
}
CountModel of(context) =>
ScopedModel.*of*<CountModel>(context);//使用ScopedModel.of方式
}
第二步:放入顶层
import 'package:flutter/material.dart';
import 'package:scoped_demo/top_screen.dart';
import 'package:scoped_demo/model/count_model.dart';
import 'package:scoped_model/scoped_model.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
//创建顶层状态
CountModel countModel = CountModel();
@override
Widget build(BuildContext context) {
return ScopedModel<CountModel>(
model: countModel,
child: new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.*blue*,
),
home: TopScreen(),
),
);
}
}
第三步:页面使用
获取model分两种:
1. ScopedModelDescendant获取model
2.ScopedModel.of<T>(context)
import 'package:flutter/material.dart';
import 'package:scoped_demo/model/count_model.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_demo/under_screen.dart';
class TopScreen extends StatefulWidget {
@override
_TopScreenState createState() => _TopScreenState();
}
class _TopScreenState extends State<TopScreen> {
//静态获取model用法实例
Model getModel(BuildContext context){
//直接使用of
final countModel = ScopedModel.*of*<CountModel>(context);
//使用CountModel中重写的of
final countModel2 = CountModel().of(context);
countModel.increment();
countModel2.increment();
return countModel;
// return countMode2;
}
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<CountModel>(
builder: (context,child,model){
return Scaffold(
appBar: AppBar(
title: Text('Top Screen'),
),
body: Center(
child: Text(
model.count.toString(),
style: TextStyle(fontSize: 48.0),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.*of*(context).push(MaterialPageRoute(builder: (BuildContext context){
return UnderScreen(title: "Under Screen",);
}));
},
child: Icon(Icons.*forward*),
),
);
},
);
}
}
框架二:Provider
第一步:创建数据 Model
import 'package:flutter/material.dart';
class CounterModel with ChangeNotifier {
int _count = 0;
int get value => _count;
void increment() {
_count++;
notifyListeners();
}
}
第二步:创建顶层共享数据
void main() {
final counter = CounterModel();
final textSize = 48;
runApp(
Provider<int>.value(
value: textSize,
child: ChangeNotifierProvider.value(
value: counter,
child: MyApp(),
),
),
);
}
第三步:在子页面中获取状态(Provider.of)
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final _counter = Provider.of<CounterModel>(context);
final textSize = Provider.of<int>(context).toDouble();
return Scaffold(
appBar: AppBar(
title: Text('FirstPage'),
),
body: Center(
child: Text(
'Value: ${_counter.value}',
style: TextStyle(fontSize: textSize),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => SecondPage())),
child: Icon(Icons.navigate_next),
),
);
}
}
方法二:
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Consumer2<CounterModel,int>(
builder: (context, CounterModel counter, int textSize, _) => Center(
child: Text(
'Value: ${counter.value}',
style: TextStyle(
fontSize: textSize.toDouble(),
),
),
),
),
floatingActionButton: Consumer<CounterModel>(
builder: (context, CounterModel counter, child) => FloatingActionButton(
onPressed: counter.increment,
child: child,
),
child: Icon(Icons.add),
),
);
}
}
框架三:Bloc (https://pub.dev/packages/flutter_bloc)
|
|
使用:
第一步,创建一个bloc:内部包含sink、stream
Sink作用:进行事件触发,进行业务处理获取数据流
Stream作用:数据监听,用于widget监听数据变化的Observable。
如下:
import 'dart:async';
class CountBLoC {
int _count = 0;
var _countController = StreamController<int>.broadcast();
Stream<int> get stream => _countController.stream;
Sink<int> get sink => _countController.sink;
int get value => _count;
increment() {
sink.add(++_count);
}
dispose() {
_countController.close();
}
}
第二步:Scoped模式创建Bloc
创建bloc分三种:下面是Scoped方式
- 全局单例创建
- 局部创建
- scoped
import 'package:flutter/material.dart';
import 'package:bloc_demo/scoped/blocs/count_bloc.dart';
class BlocProvider extends InheritedWidget {
CountBLoC bLoC = CountBLoC();
BlocProvider({Key key, Widget child}) : super(key: key, child: child);
@override
bool updateShouldNotify(_) => true;
//这里真正项目中可以使用范性创建对象
static CountBLoC *of*(BuildContext context) =>
(context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider).bLoC;
}
第三步:页面中使用方式
import 'package:flutter/material.dart';
import 'blocs/bloc_provider.dart';
class UnderPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final bloc = BlocProvider.*of*(context);//创建被观察者对象
print('build');
return Scaffold(
appBar: AppBar(
title: Text('Under Page'),
),
body: Center(
child: StreamBuilder(
stream: bloc.stream, //赋值观察者
initialData: bloc.value,
builder: (context, snapshot) => Text(
"You hit me: ${snapshot.data} times",
style: Theme.*of*(context).textTheme.display1,
)),
),
floatingActionButton: FloatingActionButton(
onPressed: () => bloc.increment(),//触发调用逻辑,这里的触发会调用bloc中的sink
child: Icon(Icons.*add*),
),
);
}
}
StreamBuilder中stream参数代表了这个stream builder监听的流,我们这里监听的是countBloc的value(它是一个stream)。
initData代表初始的值,因为当这个控件首次渲染的时候,还未与用户产生交互,也就不会有事件从流中流出。所以需要给首次渲染一个初始值。
builder函数接收一个位置参数BuildContext 以及一个snapshot。snapshot就是这个流输出的数据的一个快照。我们可以通过snapshot.data访问快照中的数据。也可以通过snapshot.hasError判断是否有异常,并通过snapshot.error获取这个异常。
StreamBuilder中的builder是一个AsyncWidgetBuilder,它能够异步构建widget,当检测到有数据从流中流出时,将会重新构建。
总结:
1.页面触发事件,触发bloc中方法发起业务逻辑,并获取相关数据,
2.再由bloc中的sink进行数据返回,数据将发送到具体页面(page)下的stream的监听下
3.由AsyncSnapshot对象将数据状态下发到具体的控件,并触发UI的重绘
框架四:Mobx
第一步:创建可观察对象
import 'package:mobx/mobx.dart';
part 'counter.g.dart';
class Counter = CounterBase with _$Counter;
final Counter counter = Counter();
abstract class CounterBase implements Store {
@observable
int value = 0;
@action
void increment() {
value++;
}
@action
void decrement() {
value--;
}
@action
void set(int value) {
this.value = value;
}
}
mobx会根据
part 'counter.g.dart';
class Counter = CounterBase with _$Counter;
生成对应的.g.dart文件
第二步:页面使用:
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_mobx_project/mobx/counter.dart';
class SecondPage extends StatefulWidget {
@override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('second page'),
),
body: Center(
child: Observer(
builder: (_) => Text(
'count is ${counter.value}',
style: TextStyle(fontSize: 30),
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.*add*),
onPressed: counter.increment,
),
);
}
}
框架五:fish_redux(https://pub.dev/packages/fish_redux/install)(https://github.com/alibaba/fish-redux)
理解fish redux的组成
无法复制加载中的内容
|
|
|
- 用户点击勾选框,GestureDetector的onTap会被回调
- 通过buildView传入的dispatch函数对doneAction进行分发,发现todo_component的effect中无法处理此doneAction,所以将其交给pageStore的dispatch继续进行分发
- pageStore的dispatch会将action交给reducer进行处理,故doneAction对应的_markDone会被执行,对state进行clone,并修改clone后的state的状态,然后将这个全新的state返回
- 然后pageStore的dispatch会通知所有的listeners,其中负责界面重绘的_viewUpdater发现state发生变化,通知界面进行重绘更新
https://zhuanlan.zhihu.com/p/62588540
4.我们应该使用什么框架比较好?
之前没有接触过flutter可以先使用bloc,熟悉了flutter的一套语法及开发速度可以切换到fish-redux中