跳到主要内容

关于代码生成

代码生成是使用工具为我们生成代码的想法。 在 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(FetchUserRef 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(ExampleRef ref) {
return 'foo';
}

class Example extends _$Example {

String build() {
return 'foo';
}

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

Future<String> example(ExampleRef 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(ExampleRef 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(Example1Ref ref) => 'foo';

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

将参数传递给提供者程序(family):​

使用代码生成时,我们不再需要依赖 family 修饰符将参数传递给提供者程序。 相反,我们的提供者程序的主函数可以接受任意数量的参数,包括命名、可选或默认值。
但请注意,这些参数应该仍然具有 == 的一致性。 这意味着要么应该缓存值,要么应该覆盖 == 参数。

函数式的基于类的

String example(
ExampleRef 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(ExampleRef 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(ExampleRef ref) async {
return Future.value('foo');
}
StreamProvider
之前
final exampleProvider =
StreamProvider.autoDispose<String>((ref) async* {
yield 'foo';
});
之后

Stream<String> example(ExampleRef 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
}