- 原文地址:Effective BLoC pattern
- 原文做者:Sagar Suri
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:LucaslEliane
- 校對者:portandbridge
朋友們,我有好長一段時間沒有寫過 flutter 相關的文章了。在完成了兩篇關於 BLoC 模式的文章以後,我花了一些時間,分析了社區對於這種模式的使用狀況,在回答了一些關於 BLoC 模式實現的一些問題以後,我發現你們對於 BLoC 模式存在不少疑惑。因此,我構思了一套方法,你們按照這一套方法來作,就能夠正確地實現 BLoC 模式了,這會幫助開發人員在實現的時候避免犯下一些常見的錯誤。因此,我今天向你們介紹一下在使用 BLoC 模式時必需要遵循的 8 個黃金點。html
我心目中的讀者,應該知道 BLoC 模式是什麼,或者使用模式建立了一個應用(至少作過 CTRL + C
和 CTRL + V
)。若是你是第一次聽到 BLoC 這個詞,那麼下面三篇文章能夠很好地幫助你理解這個模式。前端
我知道,BLoC 模式是一個很難去理解和實現的模式。我看過了不少開發人員的帖子,詢問 哪裏是學習 BLoC 模式的最佳資源呢?讀完了不一樣的帖子和評論以後,我以爲你們在理解這個問題的阻礙有如下幾點。git
響應式地思考。github
努力瞭解須要建立多少 BLoC 文件。編程
懼怕這個模式會形成代碼複雜度的提高。後端
不知道 stream 在何時會被處理掉。安全
什麼是 BLoC 模式的完整形式?(這是一個業務邏輯組件)網絡
更多其餘的緣由……
可是今天我要列出一些最爲重要的點,這些點能夠幫助你更加自信及有效地實現 BLoC 模式。如今,就讓咱們趕快看看有哪些很棒的點。
這是須要記住的最重要的一個點。每當你建立了一個新的頁面,例如登陸頁,註冊頁,我的資料頁等涉及到數據處理的頁面的時候,你必需要爲其 建立一個新的 BLoC。不要將全局 BLoC 用於處理應用中的全部頁面。你可能會認爲,若是咱們有一個全局的 BLoC,就能夠輕鬆地處理跨頁面的數據了。這很很差,由於你的庫應當將這些公共數據提供給 BLoC。BLoC 僅僅是獲取數據而且將其注入到頁面中,來向用戶展現。
這一點比較直接。你建立的每一個 BLoC 都應該有一個 dispose()
方法。這個方法是你清理或者關閉你建立的全部 stream 的位置。下面是一個 dispose()
的簡單的例子。
class MoviesBloc {
final _repository = Repository();
final _moviesFetcher = PublishSubject<ItemModel>();
Observable<ItemModel> get allMovies => _moviesFetcher.stream;
fetchAllMovies() async {
ItemModel itemModel = await _repository.fetchAllMovies();
_moviesFetcher.sink.add(itemModel);
}
dispose() {
_moviesFetcher.close();
}
}
複製代碼
每當你想要建立一個傳遞數據到 BLoC 或者從 BLoC 中獲取數據的頁面的時候,請使用 StatefulWidget
。使用 StatefulWidget
相比於使用 StatelessWidget
的最大優勢在於 StatefulWidget
中的生命週期方法。在文章的後面,咱們會討論在使用 BLoC 模式時須要覆蓋的兩個最重要的方法。StatelessWidget
很適合製做頁面的小的靜態部分,例如顯示圖像或者是硬編碼的文本。若是你想要看看怎麼用 StatelessWidget
來實現 BLoC 模式,請看上面推薦的文章的 第一部分,而在第二部分中,我講述了本身爲何要從 StatelessWidget
遷移到 StatefulWidget
。
若是你須要在初始化的時候須要一個 context
來初始化 BLoC 對象,那麼這個方法就是在 StatefulWidget
中須要重寫的最重要的方法。你能夠將其視爲初始化方法(最好僅用於 BLoC 的初始化)。你或許會說,咱們有 initState()
方法,那麼爲何咱們要使用 didChangeDependencies()
方法。文檔裏面清楚地提到,從 didChangeDependencies()
調用 BuildContext.inheritFromWidgetOfExactType 是安全的。下面是使用這個方法的一個簡單的例子:
@override
void didChangeDependencies() {
bloc = MovieDetailBlocProvider.of(context);
bloc.fetchTrailersById(movieId);
super.didChangeDependencies();
}
複製代碼
就和有一個初始化方法同樣,咱們還有一個方法,來處理掉咱們在 BLoC 中建立的鏈接。dispose()
方法是調用與該頁面相連的對應的 BLoC 的 dispose()
方法的最佳位置。每當你離開頁面的時候,須要調用這個方法(實際上就是StatefulWidget
被處理掉的時候)。如下是該方法的一個小例子:
@override
void dispose() {
bloc.dispose();
super.dispose();
}
複製代碼
若是你以前使用過 BLoC 模式的話,那麼你必定據說過 [RxDart](https://github.com/ReactiveX/rxdart)
庫。這個庫是 Google Dart 的響應式函數式編程庫,它只是一個包裝器,用來包裝 Dart 提供的 Stream
API。我建議你僅在須要處理,相似於連接多個網絡請求這樣的複雜邏輯時,才使用這個庫。對於一些簡單的實現,使用 Dart 語言提供的 Stream
API 就足夠了,由於這個 API 已經很是成熟了。下面我添加了一個 BLoC,它使用了 Stream
API 而不是 RxDart
庫,這樣會讓操做變得很是簡單,咱們不須要額外的庫來實現一樣的事情:
import 'dart:async';
class Bloc {
//Our pizza house
final order = StreamController<String>();
//Our order office
Stream<String> get orderOffice => order.stream.transform(validateOrder);
//Pizza house menu and quantity
static final _pizzaList = {
"Sushi": 2,
"Neapolitan": 3,
"California-style": 4,
"Marinara": 2
};
//Different pizza images
static final _pizzaImages = {
"Sushi": "http://pngimg.com/uploads/pizza/pizza_PNG44077.png",
"Neapolitan": "http://pngimg.com/uploads/pizza/pizza_PNG44078.png",
"California-style": "http://pngimg.com/uploads/pizza/pizza_PNG44081.png",
"Marinara": "http://pngimg.com/uploads/pizza/pizza_PNG44084.png"
};
//Validate if pizza can be baked or not. This is John
final validateOrder =
StreamTransformer<String, String>.fromHandlers(handleData: (order, sink) {
if (_pizzaList[order] != null) {
//pizza is available
if (_pizzaList[order] != 0) {
//pizza can be delivered
sink.add(_pizzaImages[order]);
final quantity = _pizzaList[order];
_pizzaList[order] = quantity-1;
} else {
//out of stock
sink.addError("Out of stock");
}
} else {
//pizza is not in the menu
sink.addError("Pizza not found");
}
});
//This is Mia
void orderItem(String pizza) {
order.sink.add(pizza);
}
}
複製代碼
對於那些在 Flutter 項目中使用 RxDart
庫的人來講,這一點會更加地明確。BehaviorSubject
是一個特殊的 StreamController
,它會捕獲到已經添加到 controller 的最新項,而且將其做爲新的 listener 的第一個事件觸發。即便你在 BehaviorSubject
上調用 close()
或者 drain()
,它仍然會保留最後一項,而且在這個 listener 被訂閱的時候觸發。若是開發人員不瞭解這個功能,這有可能會變成一場噩夢。而 PublishSubject
不會存儲最後一項,更加適合於大多數狀況。在這個項目中,能夠查看 BehaviorSubject
的功能。運行應用程序,而且跳轉到 'Add Goal' 頁面,在表單中輸入詳細信息,而且跳轉回來。如今,再次訪問 'Add Goal' 頁面,你就會發現表單裏已經預先填寫了你以前輸入的數據。若是你和我同樣懶,那麼能夠看我下面附上的視頻:
在我說這一點以前,請看下面的代碼片(第 9 行和第 10 行)。
import 'package:flutter/material.dart';
import 'ui/login.dart';
import 'blocs/goals_bloc_provider.dart';
import 'blocs/login_bloc_provider.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LoginBlocProvider(
child: GoalsBlocProvider(
child: MaterialApp(
theme: ThemeData(
accentColor: Colors.black,
primaryColor: Colors.amber,
),
home: Scaffold(
appBar: AppBar(
title: Text(
"Goals",
style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.amber,
elevation: 0.0,
),
body: LoginScreen(),
),
),
),
);
}
}
複製代碼
你能夠清楚地看到,多個 BLoC Provider 是嵌套的。這時候,那麼你必定會擔憂,若是繼續在同一個鏈中添加更多的 BLoC,會致使一場噩夢,你可能會得出 BLoC 模式沒法擴展的結論。可是,讓我告訴你,當你須要在 Widget 樹中訪問多個 BLoC 的時候,可能會有一種特殊的狀況(BLoC 只保存應用程序所須要的 UI 配置),所以,對於這種狀況,上述的嵌套是徹底沒問題的。可是我建議你在大多數的狀況下,仍是要避免這種嵌套的,而且只在實際須要的地方提供 BLoC。所以,好比當你須要導航到新的頁面的時候,能夠像這樣使用 BLoC Provider:
openDetailPage(ItemModel data, int index) {
final page = MovieDetailBlocProvider(
child: MovieDetail(
title: data.results[index].title,
posterUrl: data.results[index].backdrop_path,
description: data.results[index].overview,
releaseDate: data.results[index].release_date,
voteAverage: data.results[index].vote_average.toString(),
movieId: data.results[index].id,
),
);
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return page;
}),
);
}
複製代碼
這樣,MovieDetailBlocProvider
就不會爲整個組件樹,而是會爲 MovieDetail
頁面提供 BLoC。你能夠看到,我將 MovieDetailScreen
存儲在一個新的 final variable
中,來避免每次在 MovieDetailScreen
中打開或者關閉鍵盤的時候,都會從新建立 MovieDetailScreen
的問題。
雖然這裏是本文的結尾了,但並非這個主題的結尾。我也會在這個有關優化 BLoC 模式的文集中不斷添加新的想法,從而繼續豐富它的內容。我但願這些想法能夠幫助你更好地實現 BLoC 模式。Keep learning and keep coding :)。若是你喜歡這篇文章,能夠經過點贊來表達你的愛。
有任何疑問,請在 LinkedIn 與我聯繫,或者在 Twitter 上關注我。我會盡我所能解決你的問題。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。