Flutter的一個更精簡的狀態管理工具:consumer

consumer 是一個參考 react-consumer 方式的狀態管理, 使用 dart 的 Stream 作發佈訂閱.react

類 react 項目,當項目到必定程度,必不可少須要一個狀態管理器,flutter 有着很多狀態管理庫,BLOC、Provider、redux 等等;可是他們現有的問題是沒有給出很便捷的狀態管理優化方案。git

consumer 的特色是僅僅是發佈訂閱模式加 StateFulWidget,這比市面上基於 InheritedWidget 進行封裝的狀態管理器的優點是它不須要一個頂層的提供者模式的包裹。基於此,consumer 可讓項目更簡單建立子模塊的獨立的狀態管理,固然你也可使用 consumer 的單一模式管理整個項目的狀態。github

在這個前提下,咱們會發現若項目足夠大,咱們須要切分多個子狀態管理,或者一些局部的狀態管理,這樣能夠有效減小事件派發的影響範圍,從而提升性能;consumer 另外一個特色是強制使用者描述每一個訂閱所使用的對象,這樣 consumer 能夠幫助優化性能,攔截沒必要要的更新。redux

Feature

  • consumer 不須要一個頂層的 Provider 包裹對象;
  • consumer 能夠很輕鬆的給子模塊設置獨立的狀態管理;
  • consumer 使用 memo 攔截沒必要要的更新,從 react.Hooks 獲得的靈感;
  • consumer 很是易於使用, 僅有 3 個 API:
    • getState
    • setState
    • build

API 文檔:數組

安裝 consumer

修改 pubspec.yaml:app

dependencies:
 consumer: ^1.0.2
複製代碼

入門指南

這是一個 Flutter 默認的初始化項目,咱們使用 consumer 改造它,移除 StateFulWidget,替換成 StatelessWidget:less

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

// *** 定義一個類,描述狀態 ***
class ExampleState {
  int counter = 0;
}

// *** 建立一個 consumer ***
var consumer = Consumer(ExampleState());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Consumer Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  _incrementCounter() {
    // *** 使用 setState ,觸發訂閱的組件更新 ***
    consumer.setState((state) => state.counter++);
  }

  @override
  Widget build(BuildContext context) {
    print('整個對象僅更新一次,更新時僅更新訂閱的組件');

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            // *** 使用 consumer.build 訂閱一個組件 ***
            consumer.build(
              memo: (state) => [state.counter],
              builder: (ctx, state) {
                print('僅有 memo 返回值中的對象改變了,builder 對象纔會更新');
                return Text(
                  '$state.counter',
                  style: Theme.of(context).textTheme.display1,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

複製代碼

FAQ

參數 memo 的做用是什麼?

若是你項目有着很是多的狀態訂閱,使用 memo 能夠大幅度提升性能;因此 memo 設計爲必須定義的參數。ide

memo 的概念是來自於 react.Hooks, 它用來描述監聽變化的對象,僅有監聽對象變化時,纔會派發更新。函數

一個原則是,咱們在 builder 對象中須要使用什麼屬性,memo 返回的數組就定義什麼屬性, 咱們這裏有一些例子:性能

若是咱們由 consumer.build 建立的兩個 widget:

// *** definition a state ***
class ExampleState {
  List<String> animates = [];
  int age = 0;
  String name = 'dog';
}

// *** create a consumer ***
var consumer = Consumer(ExampleState());

Column(
  children: <Widget>[
    consumer.build(
      memo: (state) => [state.age, state.animates],
      builder: (ctx, state) {
        print('Update when state.age change');
        return Text(
          '$state.age',
          style: Theme.of(context).textTheme.display1,
        );
      },
    ),
    consumer.build(
      memo: (state) => [state.name],
      builder: (ctx, state) {
        print('Update when state.name change');
        return Text(
          state.name,
          style: Theme.of(context).textTheme.display1,
        );
      },
    ),
  ],
);
複製代碼

而後咱們更新 state.name:

consumer.setState((state){
  state.name = 'cat';
});
複製代碼

此時,當咱們更新 state.name,只有訂閱了 memo: (state) => [state.name] 的 widget 會更新,其餘 Widget 的更新都會被 consumer 攔截。

爲何個人使用了 consumer.setState 以後 Widget 並無更新?

或許你在 builder 中使用了 state.name, 不過 memo 返回的數組未包含 state.name:

Center(
  child: consumer.build(
    memo: (state) => [state.age],
    builder: (ctx, state) {
      return Text(
        state.name,
        style: Theme.of(context).textTheme.display1,
      );
    },
  ),
);
複製代碼

或許你的 memo 未監放任何對象:

Center(
  child: consumer.build(
    memo: (state) => [],
    builder: (ctx, state) {
      return Text(
        state.name,
        style: Theme.of(context).textTheme.display1,
      );
    },
  ),
);
複製代碼

或許你僅僅是改變了 List 或 Map 內的對象,可是沒有從新設定一個新的 List 或 Map:

class ExampleState {
  List<String> names = ['dog', 'cat'];
}

var consumer = Consumer(ExampleState());

Center(
  child: consumer.build(
    memo: (state) => [state.names],
    builder: (ctx, state) {
      return Text(
        state.names[0],
        style: Theme.of(context).textTheme.display1,
      );
    },
  ),
);

// 錯誤的更新:
Consumer.setState((state){
  state.names[0] = 'fish'
});

// 正確的更新:
Consumer.setState((state){
  List<String> names = [...state.names];
  names[0] = 'fish'
  state.names = names;
});
複製代碼

State 小技巧

若是你須要在更新以前作一些計算, 或者更方便處理數組之類的更新,你能夠建立一些函數屬性給 State:

這裏有一個修改 List 數據的例子:

class ExampleState {
  int lastChangeNamesIndex;
  List<String> names = ['dog', 'cat'];

  changeNameAt(int index, String name) {
    lastChangeNamesIndex = index;
    List<String> nextNames = [...names];
    nextNames[index] = name;
    names = nextNames;
  }
}

var consumer = Consumer(ExampleState());

Center(
  child: consumer.build(
    memo: (state) => [state.names, state.lastChangeNamesIndex],
    builder: (ctx, state) {
      return Text(
        state.names[state.lastChangeNamesIndex],
        style: Theme.of(context).textTheme.display1,
      );
    },
  ),
);

// 輕鬆更新 names 和 lastChangeNamesIndex
consumer.setState((state){
  state.changeNameAt(0, 'monkey');
})
複製代碼

That's all

感謝你閱讀本文檔和使用 consumer.

相關文章
相關標籤/搜索