極簡Flutter狀態(tài)管理庫:consumer

若你覺得 Provider 等狀態(tài)管理太繁瑣, consumer 就是為你準(zhǔn)備的一款高性能、極簡的狀態(tài)管理庫。

consumer 是一個參考 react-consumer 方式的狀態(tài)管理, 使用 dart 的 Stream 做發(fā)布訂閱.

類 react 項目,當(dāng)項目到一定程度,必不可少需要一個狀態(tài)管理器,flutter 有著不少狀態(tài)管理庫,BLOC、Provider、redux 等等;但是他們現(xiàn)有的問題是沒有給出很便捷的狀態(tài)管理優(yōu)化方案。

consumer 的特點是僅僅是發(fā)布訂閱模式加 StateFulWidget,這比市面上基于 InheritedWidget 進(jìn)行封裝的狀態(tài)管理器的優(yōu)勢是它不需要一個頂層的提供者模式的包裹?;诖耍琧onsumer 可以讓項目更簡單創(chuàng)建子模塊的獨立的狀態(tài)管理,當(dāng)然你也可以使用 consumer 的單一模式管理整個項目的狀態(tài)。

在這個前提下,我們會發(fā)現(xiàn)若項目足夠大,我們需要切分多個子狀態(tài)管理,或者一些局部的狀態(tài)管理,這樣可以有效減少事件派發(fā)的影響范圍,從而提高性能;consumer 另一個特點是強制使用者描述每個訂閱所使用的對象,這樣 consumer 可以幫助優(yōu)化性能,攔截不必要的更新。

Feature

  • 僅更新數(shù)據(jù)變化的局部
  • 不需要一個頂層的 Provider 包裹對象
  • 可以很輕松的給子模塊設(shè)置獨立的狀態(tài)管理
  • 非常易于使用, 僅有 2 個 API: setStatebuild

API 文檔:

安裝 consumer

修改 pubspec.yaml:

dependencies:
  consumer: ^2.2.0

入門指南

這是一個 Flutter 默認(rèn)的初始化項目,我們使用 consumer 改造它,移除 StateFulWidget,替換成 StatelessWidget:

import 'package:flutter/material.dart';

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

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

// *** 創(chuàng)建一個 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 ,觸發(fā)訂閱的組件更新 ***
    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((ctx, state) {
              return Text(
                state.counter.toString(),
                style: Theme.of(context).textTheme.display1,
              );
            }, memo:()=>[state.counter]),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

參數(shù) memo 的作用是什么?

從 v2.2.0 版本開始 memo 參數(shù)是必傳的,這是因為作者認(rèn)為與其等性能出現(xiàn)問題再去優(yōu)化,不如從編寫的時候就強制開發(fā)者編寫性能已優(yōu)化的代碼。

如果你項目有著非常多的狀態(tài)訂閱,使用 memo 可以大幅度提高性能.

memo 的概念是來自于 react.Hooks, 它用來描述監(jiān)聽變化的對象,僅有監(jiān)聽對象變化時,才會派發(fā)更新。

一個原則是,我們在 builder 對象中需要使用什么屬性,memo 返回的數(shù)組就定義什么屬性, 我們這里有一些例子:

如果我們由 consumer.build 創(chuàng)建的兩個 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((ctx, state) {
        print('Update when state.age change');
        return Text(
          '$state.age',
          style: Theme.of(context).textTheme.display1,
        );
      },
      memo: (state) => [state.age, state.animates],
    ),
    consumer.build((ctx, state) {
        print('Update when state.name change');
        return Text(
          state.name,
          style: Theme.of(context).textTheme.display1,
        );
      },
      memo: (state) => [state.name],
    ),
  ],
);

然后我們更新 state.name:

consumer.setState((state){
  state.name = 'cat';
});

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

完整的使用 consumer 配合 memo 攔截更新的例子

一般來說使用狀態(tài)管理都會涉及到跨組件更新,consumer 建議您把相關(guān)組件使用的狀態(tài)放到一個文件中,在不同的組件中進(jìn)行引用:

lib/consumer.dart: 聲明狀態(tài)和狀態(tài)消費者

import 'package:consumer/consumer.dart';

class ExampleState {
  int counter = 0;
  String time = DateTime.now().toString();
}

var consumer = Consumer(ExampleState());

lib/main.dart: 使用狀態(tài)消費者,繪制需要被狀態(tài)接管的組件

import 'package:flutter/material.dart';
import './consumer.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Material App',
      theme: ThemeData(primaryColor: Colors.blue),
      home: Scaffold(
        appBar: AppBar(
          title: Text("hello"),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('counter:'),
              consumer.build(
                (ctx, state) {
                  print("update state.counter");
                  return Text(
                    state.counter.toString(),
                    style: Theme.of(ctx).textTheme.headline4,
                  );
                },
                memo: (state) => [state.counter],
              ),
              Container(
                child: TextButton(
                  onPressed: () {
                    consumer.setState((state) {
                      state.counter += 1;
                    });
                  },
                  child: Text("Only Change counter",
                      style: TextStyle(fontSize: 24)),
                ),
                margin: EdgeInsets.only(top: 20, bottom: 40),
              ),
              Text('time:'),
              consumer.build(
                (ctx, state) {
                  print("update state.time");
                  return Text(
                    state.time.toString(),
                    style: Theme.of(ctx).textTheme.headline4,
                  );
                },
                memo: (state) => [state.time],
              ),
              Container(
                child: TextButton(
                  onPressed: () {
                    consumer.setState((state) {
                      state.time = DateTime.now().toString();
                    });
                  },
                  child:
                      Text("Only Change time", style: TextStyle(fontSize: 24)),
                ),
                margin: EdgeInsets.only(top: 20),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

為什么我的使用了 consumer.setState 之后 Widget 并沒有更新?

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

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

或許你的 memo 未監(jiān)聽任何對象:

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

或許你僅僅是改變了 List 或 Map 內(nèi)的對象,但是沒有重新設(shè)定一個新的 List 或 Map:

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

var consumer = Consumer(ExampleState());

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

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

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

State 小技巧

如果你需要在更新之前做一些計算, 或者更方便處理數(shù)組之類的更新,你可以創(chuàng)建一些函數(shù)屬性給 State:

這里有一個修改 List 數(shù)據(jù)的例子:

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((ctx, state) {
      return Text(
        state.names[state.lastChangeNamesIndex],
        style: Theme.of(context).textTheme.display1,
      );
    },
    memo: (state) => [state.names, state.lastChangeNamesIndex],
  ),
);

// 輕松更新 names 和 lastChangeNamesIndex
consumer.setState((state){
  state.changeNameAt(0, 'monkey');
})

That's all

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容