跳到主要內容

關於程式碼生成

程式碼生成是使用工具為我們生成程式碼的想法。 在 Dart 中,它的缺點是需要額外的步驟來“編譯”應用程式。 儘管這個問題可能在不久的將來得到解決, 但 Dart 團隊正在研究這個問題的潛在解決方案。

在 Riverpod 的上下文中,程式碼生成是稍微改變定義 "provider" 的語法。例如,代替:

final fetchUserProvider = FutureProvider.autoDispose.family<User, int>((ref, userId) async {
final json = await http.get('api/user/$userId');
return User.fromJson(json);
});

使用程式碼生成,我們可以編寫:


Future<User> fetchUser(Ref ref, {required int userId}) async {
final json = await http.get('api/user/$userId');
return User.fromJson(json);
}

使用 Riverpod 時,程式碼生成是完全可選的。 沒有的話完全可以使用 Riverpod。 同時,Riverpod 支援程式碼生成並推薦使用它。

有關如何安裝和使用 Riverpod 程式碼生成器的資訊, 請參閱入門指南頁面。 確保在文件的側欄中啟用程式碼生成。

我應該使用程式碼生成嗎?​

Riverpod 中的程式碼生成是可選的。 考慮到這一點,您可能會想是否應該使用它。

答案是:很可能是的
使用程式碼生成是使用 Riverpod 的推薦方式。 這是一種更面向未來的方法,可以讓您充分發揮 Riverpod 的潛力。
與此同時,許多應用程式已經使用 Freezedjson_serializable 等包來生成程式碼。在這種情況下,您的專案可能已經設定為程式碼生成, 並且使用 Riverpod 應該很簡單。

目前,程式碼生成是可選的,因為許多人不喜歡 build_runner。 但是,一旦 Dart 中提供了靜態超程式設計build_runner 將不再是問題。 屆時,程式碼生成語法將是 Riverpod 中唯一可用的語法。

如果使用 build_runner 對您來說是一個破壞性的事情, 那麼只有那時您才應該考慮不使用程式碼生成。 但請記住,您將錯過一些功能,並且將來您將不得不遷移到程式碼生成。
儘管如此,當這種情況發生時, Riverpod 將提供一個遷移工具,以使過渡儘可能順利。

使用程式碼生成有什麼好處?​

您可能想知道:“如果 Riverpod 中程式碼生成是可選的,為什麼要使用它?”

這和其他包的目的一樣:讓您的生活更輕鬆。這包括但不限於:

  • 更好的語法,更具可讀性/靈活性,並且學習曲線更短。
    • 無需擔心提供者程式的型別。寫下您的邏輯,Riverpod 將為您選擇最合適的提供者程式。
    • 語法看起來不再像我們定義了“骯髒的全域性變數”。相反,我們定義了一個自定義函式/類。
    • 向提供者程式傳遞引數現在不受限制。 您現在可以傳遞任何引數,而不是僅限於使用 .family 並傳遞單個位置引數。 這包括命名引數、可選引數,甚至預設值。
  • 用 Riverpod 編寫的程式碼的有狀態熱過載
  • 透過生成偵錯程式隨後拾取的額外元資料來更好地進行除錯。
  • 某些 Riverpod 功能僅在程式碼生成時可用。

語法​

定義提供者程式:​

使用程式碼生成定義提供者程式時,記住以下幾點會很有幫助:

  • 提供者程式可以定義為帶註釋的函式或 帶註釋的。它們幾乎相同, 但基於類的提供者程式的優點是包含公共方法,使 外部物件能夠修改提供者程式的狀態(副作用)。 函式提供者程式是用於編寫基於類的提供者程式的語法糖,只有 build 方法, 因此不能由 UI 修改。
  • 支援所有 Dart 非同步原語(Future、FutureOr 和 Stream)。
  • 當函式被標記為async時, 提供者程式會自動處理錯誤/載入狀態並公開 AsyncValue。
函式式的
(不能使用公共方法執行副作用)
基於類的
(可以使用公共方法執行副作用)
同步的

String example(Ref ref) {
return 'foo';
}

class Example extends _$Example {

String build() {
return 'foo';
}

// Add methods to mutate the state
}
非同步的 - Future

Future<String> example(Ref ref) async {
return Future.value('foo');
}

class Example extends _$Example {

Future<String> build() async {
return Future.value('foo');
}

// Add methods to mutate the state
}
非同步的 - Stream

Stream<String> example(Ref ref) async* {
yield 'foo';
}

class Example extends _$Example {

Stream<String> build() async* {
yield 'foo';
}

// Add methods to mutate the state
}

啟用/停用自動處置 autoDispose:​

使用程式碼生成時,提供者程式預設為 autoDispose。 這意味著當沒有監聽器附加到它們(ref.watch/ref.listen)時,它們會自動處理掉自己。
此預設設定更符合 Riverpod 的理念。 最初沒有使用程式碼生成變體時,預設情況下 autoDispose 處於關閉狀態, 以適應從 package:provider 遷移的使用者。

如果您想停用 autoDispose,可以透過將 keepAlive: true 傳遞給註釋來實現。

// AutoDispose provider (keepAlive is false by default)

String example1(Ref ref) => 'foo';

// Non autoDispose provider
(keepAlive: true)
String example2(Ref ref) => 'foo';

將引數傳遞給提供者程式(family):​

使用程式碼生成時,我們不再需要依賴 family 修飾符將引數傳遞給提供者程式。 相反,我們的提供者程式的主函式可以接受任意數量的引數,包括命名、可選或預設值。
但請注意,這些引數應該仍然具有 == 的一致性。 這意味著要麼應該快取值,要麼應該覆蓋 == 引數。

函式式的基於類的

String example(
Ref ref,
int param1, {
String param2 = 'foo',
}) {
return 'Hello $param1 & param2';
}

class Example extends _$Example {

String build(
int param1, {
String param2 = 'foo',
}) {
return 'Hello $param1 & param2';
}

// Add methods to mutate the state
}

從非程式碼生成變體遷移:​

使用非程式碼生成變體時,需要手動確定提供者程式的型別。 以下是轉換為程式碼生成變體的相應選項:

Provider
之前
final exampleProvider = Provider.autoDispose<String>(
(ref) {
return 'foo';
},
);
之後

String example(Ref ref) {
return 'foo';
}
NotifierProvider
之前
final exampleProvider = NotifierProvider.autoDispose<ExampleNotifier, String>(
ExampleNotifier.new,
);

class ExampleNotifier extends AutoDisposeNotifier<String> {

String build() {
return 'foo';
}

// Add methods to mutate the state
}
之後

class Example extends _$Example {

String build() {
return 'foo';
}

// Add methods to mutate the state
}
FutureProvider
之前
final exampleProvider =
FutureProvider.autoDispose<String>((ref) async {
return Future.value('foo');
});
之後

Future<String> example(Ref ref) async {
return Future.value('foo');
}
StreamProvider
之前
final exampleProvider =
StreamProvider.autoDispose<String>((ref) async* {
yield 'foo';
});
之後

Stream<String> example(Ref ref) async* {
yield 'foo';
}
AsyncNotifierProvider
之前
final exampleProvider =
AsyncNotifierProvider.autoDispose<ExampleNotifier, String>(
ExampleNotifier.new,
);

class ExampleNotifier extends AutoDisposeAsyncNotifier<String> {

Future<String> build() async {
return Future.value('foo');
}

// Add methods to mutate the state
}
之後

class Example extends _$Example {

Future<String> build() async {
return Future.value('foo');
}

// Add methods to mutate the state
}
StreamNotifierProvider
之前
final exampleProvider =
StreamNotifierProvider.autoDispose<ExampleNotifier, String>(() {
return ExampleNotifier();
});

class ExampleNotifier extends AutoDisposeStreamNotifier<String> {

Stream<String> build() async* {
yield 'foo';
}

// Add methods to mutate the state
}
之後

class Example extends _$Example {

Stream<String> build() async* {
yield 'foo';
}

// Add methods to mutate the state
}