
若你覺得 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:
setState、build
API 文檔:
- https://pub.flutter-io.cn/packages/consumer
- https://pub.flutter-io.cn/documentation/consumer/latest/
安裝 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.