像Google那样编写Flutter代码

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 团队使用 StatefulWidgetsetState 的频率远高于任何复杂的状态管理解决方案。他们将高级状态管理方案保留给真正复杂的场景,而不是简单的 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

相关推荐
chao_7891 小时前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html
天蓝色的鱼鱼1 小时前
从零实现浏览器摄像头控制与视频录制:基于原生 JavaScript 的完整指南
前端·javascript
三原2 小时前
7000块帮朋友做了2个小程序加一个后台管理系统,值不值?
前端·vue.js·微信小程序
popoxf2 小时前
在新版本的微信开发者工具中使用npm包
前端·npm·node.js
爱编程的喵2 小时前
React Router Dom 初步:从传统路由到现代前端导航
前端·react.js
每天吃饭的羊2 小时前
react中为啥使用剪头函数
前端·javascript·react.js
Nicholas683 小时前
Flutter帧定义与60-120FPS机制
前端
多啦C梦a3 小时前
【适合小白篇】什么是 SPA?前端路由到底在路由个啥?我来给你聊透!
前端·javascript·架构
薛定谔的算法3 小时前
《长安的荔枝·事件流版》——一颗荔枝引发的“冒泡惨案”
前端·javascript·编程语言
中微子3 小时前
CSS 的 position 你真的理解了吗?
前端·css