[ - Flutter 狀態篇 redux - ] StoreConnector仍是StoreBuilder,讓distinct把好關

不少天沒發文了,今天翻翻源碼,發現解決一個困擾個人問題: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.
複製代碼

既然不服那就來測:redux


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進行關聯,而後將狀態資源提供給視圖bash

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更加簡單。markdown

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的行爲,點擊數字觸發,數字狀態保持原樣。點三下自加事件,兩次不加事件。查看結果app

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就會用得多,雖小,但狀態量不變,刷新了也是事實。less

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彷佛更勝一籌。ide

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羣裏交流技術。手動搞笑。ui


結語

本文到此接近尾聲了,若是想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;若是想細細探究它,那就跟隨個人腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流羣,歡迎小夥伴加入,共同探討Flutter的問題,本人微信號:zdl1994328,期待與你的交流與切磋。另外歡迎關注公衆號編程之王this

相關文章
相關標籤/搜索