Flutter 中有无数的编码约定在流传。从流行的"Flutter Clean Architecture"到社区驱动的风格指南,开发者们多年来一直在遵循外部约定。我就是其中之一。
在过去的五年里,我遵循社区的最佳实践,强制执行严格的 Lint 规则,并根据流行的 Medium 文章和 YouTube 教程来构建我的 Flutter 应用。我的代码是"干净的",我的架构是"规范的"。
但当我开始研究 Google Flutter 团队实际是如何编写 Flutter 代码时,一切都改变了。
这个发现令人震惊:Google Flutter 团队遵循的模式与流行的社区约定完全矛盾。
在这篇文章中,我将分享我通过分析 50 多个 Flutter 框架文件、Google 自己的 Flutter 应用以及 Flutter 核心团队成员的贡献所发现的内部编码模式。
Widget 组合优于复杂继承
社区方式:
dart
scala
abstract class BaseWidget extends StatelessWidget {
final String title;
final EdgeInsets padding;
const BaseWidget({
required this.title,
this.padding = EdgeInsets.zero,
});
@override
Widget build(BuildContext context) {
return buildContent(context);
}
Widget buildContent(BuildContext context);
}
class MyWidget extends BaseWidget {
const MyWidget({required super.title});
@override
Widget buildContent(BuildContext context) {
return Container(
padding: padding,
child: Text(title),
);
}
}
Google方式:
dart
class MyWidget extends StatelessWidget {
const MyWidget({
super.key,
required this.title,
});
final String title;
@override
Widget build(BuildContext context) {
return _buildContent(context);
}
Widget _buildContent(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16.0),
child: Text(title),
);
}
}
Google 的 Flutter 团队避免复杂的继承层级结构,转而青睐组合。他们更喜欢小巧、专注、只做好一件事的 Widget,而不是创建试图解决多个问题的复杂基类。
真实世界示例:
dart
// 不创建 BaseCardWidget
class ProductCard extends StatelessWidget {
const ProductCard({
super.key,
required this.product,
});
final Product product;
@override
Widget build(BuildContext context) {
return Card(
child: Column(
children: [
_buildImage(),
_buildTitle(),
_buildPrice(),
],
),
);
}
Widget _buildImage() => Image.network(product.imageUrl);
Widget _buildTitle() => Text(product.name);
Widget _buildPrice() => Text('$${product.price}');
}
状态管理:保持简单,傻瓜式(KISS 原则)
社区痴迷:
dart
// 每个人都这么干
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterInitial()) {
on<CounterIncrement>(_onIncrement);
on<CounterDecrement>(_onDecrement);
}
void _onIncrement(CounterIncrement event, Emitter<CounterState> emit) {
emit(CounterValue(state.value + 1));
}
void _onDecrement(CounterDecrement event, Emitter<CounterState> emit) {
emit(CounterValue(state.value - 1));
}
}
Google 的现实:
dart
// Google 的 Flutter 团队这么做
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
void _increment() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('$_counter'),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
);
}
}
在分析了 Google 的 Flutter 代码库后,我发现 Flutter 团队使用 StatefulWidget
和 setState
的频率远高于任何复杂的状态管理解决方案。他们将高级状态管理方案保留给真正复杂的场景,而不是简单的 UI 状态。
原则: 使用最简单的解决方案。不要过度设计。
方法命名:面向动作,而非面向描述
社区模式:
dart
class UserService {
Future<User> getUserById(String id) async {}
Future<void> updateUserProfile(User user) async {}
Future<void> deleteUserAccount(String id) async {}
}
Google模式:
dart
class UserService {
Future<User> fetch(String id) async {}
Future<void> update(User user) async {}
Future<void> delete(String id) async {}
}
Google 的 Flutter 团队更喜欢简洁、面向动作 的方法命名,在上下文中意义清晰。上下文(UserService
,用户服务)已经告诉你这是关于用户的,所以方法名会专注于动作 :fetch
(获取)、update
(更新)、delete
(删除)。然而,他们不会为了简洁而牺牲清晰度------当可能出现歧义时,他们仍然会使用描述性名称,例如 fetchUserById()
或 getAuthToken()
。
真实世界示例:
dart
class CartService {
Future<void> add(Product product) async {}
Future<void> remove(String productId) async {}
Future<void> clear() async {}
Future<double> total() async {}
}
Widget 文件结构:一个 Widget,一个目的**
社区结构:
dart
// user_widgets.dart
class UserCard extends StatelessWidget {}
class UserList extends StatelessWidget {}
class UserProfile extends StatelessWidget {}
class UserSettings extends StatelessWidget {}
Google结构:
scala
// user_card.dart
class UserCard extends StatelessWidget {}
// user_list.dart
class UserList extends StatelessWidget {}
// user_profile.dart
class UserProfile extends StatelessWidget {}
// user_settings.dart
class UserSettings extends StatelessWidget {}
Google 的 Flutter 团队遵循严格的一个 Widget 一个文件 的规则。每个 Widget 都存在于自己的文件中。
这遵循了单一职责原则:一个文件,一个 Widget,一个目的。
它存在于自己的文件中,使得代码库更易于维护和导航。
常量:上下文驱动分组
社区方式:
dart
// constants.dart
class AppConstants {
static const double defaultPadding = 16.0;
static const double defaultRadius = 8.0;
static const Color primaryColor = Colors.blue;
static const String appName = 'MyApp';
static const int maxRetries = 3;
}
Google方式:
dart
// ui_constants.dart
class UiConstants {
static const double defaultPadding = 16.0;
static const double defaultRadius = 8.0;
}
// theme_constants.dart
class ThemeConstants {
static const Color primaryColor = Colors.blue;
static const Color secondaryColor = Colors.grey;
}
// network_constants.dart
class NetworkConstants {
static const int maxRetries = 3;
static const Duration timeout = Duration(seconds: 30);
}
Google 团队根据上下文对常量进行分组,而不是将所有常量都放在一个巨大的文件中。这使得查找和维护相关的常量变得更加容易。
错误处理:明确且即时
社区模式:
dart
class ApiService {
Future<Result<User>> getUser(String id) async {
try {
final user = await _fetchUser(id);
return Success(user);
} catch (e) {
return Failure(e.toString());
}
}
}
Google模式:
dart
class ApiService {
Future<User> getUser(String id) async {
final response = await _httpClient.get('/users/$id');
if (response.statusCode != 200) {
throw ApiException('Failed to fetch user: ${response.statusCode}');
}
return User.fromJson(response.data);
}
}
Google 的 Flutter 团队更倾向于使用异常(exceptions)进行明确的错误处理,而不是将所有内容都包装在 Result 类型中。他们让错误自然地冒泡,并在适当的级别进行处理。
Widget 测试:行为重于实现
社区关注点:
dart
testWidgets('Counter increments smoke test', (tester) async {
await tester.pumpWidget(const MyApp());
// Verify initial state
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap increment button
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify state changed
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
Google关注点
dart
testWidgets('User can increment counter', (tester) async {
await tester.pumpWidget(const MyApp());
// User sees initial counter
expect(find.text('0'), findsOneWidget);
// User taps increment
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// User sees updated counter
expect(find.text('1'), findsOneWidget);
});
Google 的应用级 Widget 测试侧重于用户行为而非实现细节。虽然框架内部仍然需要实现层面的测试(如渲染对象或布局行为),但他们来自 Flutter Gallery 等项目的应用测试更强调用户体验而非代码结构。
结论
在研究了 Google 的 Flutter 模式后,我意识到许多开发者(包括我自己)一直在将 Flutter 开发复杂化。这并不是说社区模式是错误的------它们对于大型团队、受监管行业或企业应用来说很有价值。但 Google 的方法令人耳目一新地简单,因为他们的团队优先考虑简洁性,并且知道何时进行优化。
关键模式:
- 保持 Widget 小巧且专注
- 使用最简单的状态管理方案
- 命名清晰简洁
- 一个 Widget 一个文件
- 根据上下文对常量进行分组
- 明确处理错误
- 在应用级测试中测试用户行为
这些模式改变了我编写 Flutter 代码的方式。我的应用程序更易于维护,我的团队效率更高,并且调试变得显著更容易。
洞察:
社区约定并非错误,它们只是对于许多应用程序而言,比实际所需的更"重"。Google 的内部团队之所以能够保持简单,是因为他们拥有深厚的框架知识,并且知道何时真正需要引入复杂性。
最后请关注我的公众号:OpenFlutter