很多天沒發文了,今天翻翻原始碼,發現解決一個困擾我的問題: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群裡交流技術。手動搞笑。
結語
本文到此接近尾聲了,如果想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;如果想細細探究它,那就跟隨我的腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流群,歡迎小夥伴加入,共同探討Flutter的問題,本人微訊號:zdl1994328
,期待與你的交流與切磋。另外歡迎關注公眾號程式設計之王