很多天没发文了,今天翻翻源码,发现解决一个困扰我的问题:redux中
的StoreConnector
还是StoreBuilder
似乎都可以定点刷新,控制粒度。那它们有什么区别呢?在官方样例中基本都用StoreConnector包裹一个组件的最顶层
,并且特别是在StoreBuilder源码中注释让我心里咯噔一下:我偏爱的StoreBuilder竟然是下下签
,推荐使用StoreConnector。喵了个咪,重构一下世界观。
/// Build a Widget by passing the [Store] directly to the build function. /// /// Generally, it's considered best practice to use the [StoreConnector] and to /// build a `ViewModel` specifically for your Widget rather than passing through /// the entire [Store], but this is provided for convenience when that isn't /// necessary.
既然不服那就来测:
1.StoreConnector 对战 StoreBuilder 第一回合
1.1:三件套先搞上
class CountState { final int counter; //计时器数字 CountState(this.counter); factory CountState.init([int counter]) => CountState(counter ?? 0); } //行为 class ActionCountAdd {} //处理器 var countReducer = TypedReducer<CountState, ActionCountAdd>((state, action) { var counter; if(action is ActionCountAdd) counter = state.counter + 1; return CountState(counter); });
1.2:把老爹先认着
void main() => runApp(Wrapper(child: MyApp(),)); class Wrapper extends StatelessWidget { final Widget child; Wrapper({this.child}); final store = Store<CountState>( //初始状态 countReducer, //总处理器 initialState: CountState.init());//初始状态 @override Widget build(BuildContext context) { return StoreProvider(store: store, child: child); } } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData(primarySwatch: Colors.blue,), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } }
1.3:视图模型
StoreConnector通常通过一个ViewHolder与仓库Store进行关联,然后将状态资源提供给视图
class CountViewModel { final int count;//数字 final VoidCallback onAdd;//点击回调 CountViewModel( {@required this.count, @required this.onAdd }); static CountViewModel fromStore(Store<CountState> store) { return CountViewModel( count: store.state.counter, onAdd: () => store.dispatch(ActionCountAdd()), ); } }
1.4 使用StoreConnector
可见每次都会使用只会走
StoreConnector
中的builder内部,并不会执行_MyHomePageState,如果将StoreConnector定点进行连接就可以缩小更新粒度
class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { print("MyHomePage--------builder"); return StoreConnector<CountState, CountViewModel>( converter: CountViewModel.fromStore, builder: (context, vm) { print("StoreConnector--------builder"); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[Text( 'You have pushed the button this many times:', style: TextStyle( fontSize: 18), ), Text('${vm.count}', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: vm.onAdd, tooltip: 'Increment', child: Icon(Icons.add), ), ); }); } }
1.5 使用StoreBuilder
StoreBuilder直接连接到Store,用起来比较简单,能打(触发事件)能抗(获取数据)。从表现上来看也是同样优秀。用起来似乎是StoreBuilder更加简单。
class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { print("MyHomePage--------builder"); return StoreBuilder<CountState>( builder: (context, store) { print("StoreBuilder--------builder"); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[Text( 'You have pushed the button this many times:', style: TextStyle( fontSize: 18), ), Text('${store.state.counter}', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: ()=> store.dispatch(ActionCountAdd()), tooltip: 'Increment', child: Icon(Icons.add), ), ); }); } }
2.StoreConnector 对战 StoreBuilder 第二回合
2.1 场景布置
添加一个ActionCountNone的行为,点击数字触发,数字状态保持原样。点三下自加事件,两次不加事件。查看结果
class CountState { final int counter; //计时器数字 CountState(this.counter); factory CountState.init([int counter]) => CountState(counter ?? 0); } //切换主题行为 class ActionCountAdd {} class ActionCountNone {} //切换主题理器 var countReducer = TypedReducer<CountState, ActionCountAdd>((state, action) { var counter; if(action is ActionCountAdd) counter = state.counter + 1; if(action is ActionCountNone) counter = state.counter ; return CountState(counter); });
2.2:StoreBuilder出战
可见状态量未改变,但界面刷新了。虽然定点的刷新可以控制粒度,但粒度小,StoreBuilder就会用得多,虽小,但状态量不变,刷新了也是事实。
class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { print("MyHomePage--------builder"); return StoreBuilder<CountState>( builder: (context, store) { print("StoreBuilder--------builder"); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[Text( 'You have pushed the button this many times:', style: TextStyle( fontSize: 18), ), InkWell( onTap: ()=> store.dispatch(ActionCountNone()),//<--不加 child: Text('${store.state.counter}', style: Theme.of(context).textTheme.display1, ), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: ()=> store.dispatch(ActionCountAdd()), tooltip: 'Increment', child: Icon(Icons.add), ), ); }); } }
2.2:StoreConnector出战
StoreConnector冷笑:
哥们,瞧我的
这里重写了CountViewModel的判等,可见当CountViewModel状态量不变时,界面不刷新
如果想让他刷新,可以控制distinct属性。所以StoreConnector似乎更胜一筹。
class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { print("MyHomePage--------builder"); return StoreConnector<CountState, CountViewModel>( distinct: true, converter: CountViewModel.fromStore, builder: (context, vm) { print("StoreConnector--------builder"); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[Text( 'You have pushed the button this many times:', style: TextStyle( fontSize: 18), ), InkWell( onTap: vm.onNone, child: Text('${vm.count}', style: Theme.of(context).textTheme.display1, ), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: vm.onAdd, tooltip: 'Increment', child: Icon(Icons.add), ), ); }); } } class CountViewModel { final int count;//数字 final VoidCallback onAdd;//点击回调 final VoidCallback onNone;//点击回调 CountViewModel( {@required this.count, @required this.onAdd,@required this.onNone, }); static CountViewModel fromStore(Store<CountState> store) { return CountViewModel( count: store.state.counter, onAdd: () => store.dispatch(ActionCountAdd()), onNone: () => store.dispatch(ActionCountNone()), ); } @override bool operator ==(Object other) => identical(this, other) || other is CountViewModel && runtimeType == other.runtimeType && count == other.count; @override int get hashCode => count.hashCode; }
到这你似乎又要说谁好谁坏了,那我只有呵呵了。没有好坏,只要适合和不适合,StoreConnector需要ViewModel对于一些较大块的组件可以使用。如果就一两个字段或是犄角旮旯里的小组件,StoreBuilder也是很精简的,刷一下就刷呗,犯不着为了一分钱去搬砖。知道它们在干什么最重要,而不是评论好坏。否则只会沦落键盘侠和喷嘴...还不如来我的Flutter群里交流技术。手动搞笑。