Flutter中的Bloc
Bloc和Widget是一種強(qiáng)綁定的關(guān)系以清,下面介紹一些核心的概念典格。下面提到的狀態(tài)并不是Flutter原生的State廓潜,而是Bloc中的 概念母怜。
常用Widget
BlocBuilder
是一個widget余耽,需要一個Bloc參數(shù)和builder參數(shù)。BlocBuilder會自動響應(yīng)狀態(tài)去 build widget苹熏。根據(jù)狀態(tài)去build widget碟贾,并不是BlocBuilder的特點(diǎn),像FutureBuilder轨域、StreamBuilder都可以做到這一點(diǎn)袱耽。但是BlocBuilder是為了結(jié)合 Bloc使用。參數(shù)中的builder可能會被調(diào)用多次干发,因?yàn)橹灰辛藸顟B(tài)改變就會 觸發(fā)一次builder調(diào)用(響應(yīng)式)朱巨。
BlocBuilder<BlocA, BlocAState>(
bloc: blocA,
builder: (context, state) {
//根據(jù)state 返回widget
}
)
其中bloc參數(shù)不是必填項(xiàng),如果不傳入bloc參數(shù)枉长,那么會根據(jù)自己的context自動向上尋找 合適 的Bloc冀续。那么是如何向上尋找呢?通過Provider搀暑。
//不帶bloc參數(shù)
BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
//根據(jù)state 返回widget
}
)
那么帶bloc參數(shù)和不帶bloc參數(shù)的區(qū)別是什么呢沥阳?
我們知道 bloc的作用是 將事件流 map為 狀態(tài)流。如果不帶參數(shù) 默認(rèn)是使用 父widget的 bloc自点,那就是說 子也需要這個狀態(tài)桐罕,這樣可以做到 將狀態(tài)層層下發(fā)下來,實(shí)現(xiàn)局部的widget刷新桂敛。比如將狀態(tài) 傳給了整個頁面級別的widget功炮,子widget也可以方便的獲取到這個 狀態(tài),不需要重新生成整個頁面术唬,也不需要通過構(gòu)造參數(shù)層層傳遞薪伏。
那么什么時候使用帶參數(shù)?什么時候使用不帶bloc參數(shù)呢粗仓?
第一:如果想要很好的自己控制自己嫁怀,那么就使用帶參數(shù)的设捐。
第二:如果通過context 向上尋找不到合適的bloc,那么就使用帶參數(shù)的塘淑。
我們知道Bloc是基于訂閱關(guān)系的萝招,那么就存在一種情況:每個輸入事件---都會產(chǎn)生一個狀態(tài)事件---都會調(diào)用build方法。我們知道flutter的widget是不可變的存捺,大多數(shù)情況下都會生成一個新的widget樹槐沼,這樣是十分消耗資源的。有些狀態(tài)不需要執(zhí)行build方法捌治,針對這種情況岗钩,BlocBuilder提供了一個condition方法參數(shù),他接受兩個兩個參數(shù):前一個狀態(tài)肖油、新生成的狀態(tài)兼吓,返回一個bool結(jié)果。如果true則執(zhí)行builder方法构韵,如果false則 不執(zhí)行周蹭。這樣就起到了過濾的作用。
BlocBuilder<BlocA, BlocAState>(
condition: (previousState, state) {
//根據(jù)返回的bool值去決定是否 指定builder方法
},
builder: (context, state) {
}
)
BlocProvider
上面我們提到了 通過provider向上尋找到合適的Bloc疲恢。這個provider就是BlocProvider凶朗。BlocProvider也是一個widget,它可以為他的子樹提供一個Bloc显拳。子樹可以通過BlocProvider.of<T>(context)去獲取到Bloc棚愤。是不是很像InheritedWidget。
既然BlocProvider可以提供一個Bloc杂数。那么根據(jù)構(gòu)造Bloc的方式就可以分為兩類了:
一:構(gòu)造一個新的Bloc宛畦。
BlocProvider(
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
二:使用一個已經(jīng)存在的Bloc。
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: ScreenA(),
);
那么使用這兩者有什么區(qū)別嗎揍移?誰負(fù)責(zé)創(chuàng)建了Bloc次和,那么誰就要去關(guān)閉Bloc。
MultiBlocProvider
MultiBlocProvider同樣是一個Widget那伐,它的作用是為了:將多個BlocProvider合并到一個之中踏施。因?yàn)檫@樣的可以減少多BlocProviders的嵌套層級,提高代碼的可閱讀性罕邀。如下所示使用與不使用的對比:
//層層嵌套 ----》串行
BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
child: BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
child: BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
child: ChildA(),
)
)
)
//多個合并----》并行
MultiBlocProvider(
providers: [
BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
),
BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
),
BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
),
],
child: ChildA(),
)
BlocListener
BlocListener也是一個Widget畅形,它需要一個BlocWidgetListener構(gòu)造參數(shù)。BlocWidgetListener的用處是響應(yīng)Bloc的狀態(tài)變化诉探。
我們知道BlocBuilder的builder也是響應(yīng)狀態(tài)變化日熬,那兩者的區(qū)別是什么呢?第一:BlocListener的listener函數(shù) 對于一種狀態(tài)只會被調(diào)用一次肾胯,而BlocBuilder的builder一種狀態(tài)可能會調(diào)用多次竖席。第二:BlocListener的listener函數(shù)是沒有返回值的耘纱,而BlocBuilder的builder需要一個Widget返回值。
和上述的其他widget相似怕敬,BlocListener也有可選的Bloc參數(shù)以及condition參數(shù)揣炕。
BlocListener<BlocA, BlocAState>(
condition: (previousState, state) {
//根據(jù)返回的bool值,判斷是否調(diào)用listener方法
},
listener: (context, state) {
}
child: Container(),
)
MultiBlocListener
MultiBlocListener會合并多個BlocListener到一個中东跪,用法同上述的MultiBlocProvider。
MultiBlocListener(
listeners: [
BlocListener<BlocA, BlocAState>(
listener: (context, state) {},
),
BlocListener<BlocB, BlocBState>(
listener: (context, state) {},
),
BlocListener<BlocC, BlocCState>(
listener: (context, state) {},
),
],
child: ChildA(),
)
BlocConsumer
上面我們將了BlocBuilder和BlocListener鹰溜。BlocBuilder提供了一種根據(jù)狀態(tài) 構(gòu)建 widget的能力虽填,BlocListener提供了一種 監(jiān)聽狀態(tài)的能力。 但是曹动,我們即像 構(gòu)建Ui 又像 執(zhí)行操作 怎么辦呢斋日?要么在BlocBuilder的builder代碼中 融合 操作代碼,要么在BlocListener中融合 rebuild操作墓陈,不管哪種操作恶守,都十分的怪異。
BlocConsumer就提供了解決這種怪異的能力贡必,它對外暴露了builder 和 listener兔港,兩者的方法同上述的builder和listener。除此之外仔拟,為了更加精細(xì)的控制函數(shù)的調(diào)用時機(jī)衫樊,它還提供了可選的listenWhen和buildWhen參數(shù),這兩個參數(shù)都返回bool值利花,用法同上述的condition科侈。
BlocConsumer<BlocA, BlocAState>(
listenWhen: (previous, current) {
//根據(jù)前一個狀態(tài)和當(dāng)前狀態(tài) 判斷 是否調(diào)用listener
},
listener: (context, state) {
},
buildWhen: (previous, current) {
//根據(jù)前一個狀態(tài)和當(dāng)前狀態(tài) 判斷是否調(diào)用builder
},
builder: (context, state) {
}
)
RepositoryProvider
我們在開發(fā)中經(jīng)常需要使用父Widget或祖Widget的某些數(shù)據(jù),RepositoryProvider就提供了這樣的能力炒事。它的子樹們可以很方便的通過RepositoryProvider.of<T>(context)獲取到它的某些信息臀栈。思路同BlocProvider,只不過BlocProvider對子樹們提供了Bloc挠乳,RepositoryProvider提供的是任意指定的信息权薯。
RepositoryProvider(
//指定了RepositoryA()信息
builder: (context) => RepositoryA(),
child: ChildA(),
);
//子樹們使用的方式
RepositoryProvider.of<RepositoryA>(context)
使用
上面我們介紹了幾個常用的widget,下面我們看一下具體的使用欲侮,仍以計(jì)數(shù)器為例崭闲。
事件
具體的事件就是用戶點(diǎn)擊的加一、減一威蕉。那么就是兩種事件刁俭。
enum CounterEvent { increment, decrement }
Bloc
Bloc就是將事件流轉(zhuǎn)為 狀態(tài)流。
//范型為:點(diǎn)擊的事件韧涨、數(shù)值狀態(tài)
class CounterBloc extends Bloc<CounterEvent, int> {
@override
int get initialState => 0;
//狀態(tài)的轉(zhuǎn)換
@override
Stream<int> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.decrement:
yield state - 1;
break;
case CounterEvent.increment:
yield state + 1;
break;
}
}
}
頁面
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//構(gòu)造bloc
CounterBloc counterBloc = CounterBloc();
return BlocProvider(
create: (BuildContext context) => counterBloc,
child: Scaffold(
appBar: AppBar(title: Text('Counter')),
body: BlocBuilder<CounterBloc, int>(
//指定builder需要的bloc
bloc: counterBloc,
//每次狀態(tài)變化 都會執(zhí)行builder
builder: (context, count) {
return Center(
child: Text(
'$count',
style: TextStyle(fontSize: 24.0),
),
);
},
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
//將事件 添加至 bloc
counterBloc.add(CounterEvent.increment);
},
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
child: Icon(Icons.remove),
onPressed: () {
//將事件添加至 bloc
counterBloc.add(CounterEvent.decrement);
},
),
),
],
),
),
);
}
}
我們發(fā)現(xiàn)Bloc就是 一個中轉(zhuǎn)站牍戚,將事件轉(zhuǎn)為狀態(tài)侮繁。那么為什么可以將狀態(tài)轉(zhuǎn)完更新UI呢?其實(shí)是流的訂閱如孝。
總結(jié)
UI組件將事件分派給Bloc宪哩,Bloc內(nèi)部做一個轉(zhuǎn)換,將狀態(tài)發(fā)送給UI第晰,UI再去更新局部或者全部自己锁孟。