学Google那样编写Flutter

62 阅读3分钟

此文章为个人学习Flutter的笔记

1. Widget 组合优于复杂继承

谷歌示例

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,而不是创建试图解决多个问题的复杂基类。

现实编码示例

// 不创建 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}');  
}

2. 状态管理:保持简单,傻瓜式(KISS 原则)

谷歌示例

// 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'),  
        ),  
      ],  
    );  
  }  
}

谷歌Flutter 团队使用 StatefulWidget 和 setState 的频率远高于任何复杂的状态管理解决方案。他们将高级状态管理方案保留给真正复杂的场景,而不是简单的 UI 状态。 原则:  使用最简单的解决方案。不要过度设计。

3. 方法命名:面向动作,而非面向描述

谷歌示例

class UserService {  
  Future<User> fetch(String idasync {}  
  Future<void> update(User userasync {}  
  Future<void> delete(String idasync {}  
}

Google 的 Flutter 团队更喜欢简洁、面向动作的方法命名,在上下文中意义清晰。上下文(UserService,用户服务)已经告诉你这是关于用户的,所以方法名会专注于动作fetch(获取)、update(更新)、delete(删除)。然而,他们不会为了简洁而牺牲清晰度——当可能出现歧义时,他们仍然会使用描述性名称,例如 fetchUserById() 或 getAuthToken()

现实编码示例

class CartService {  
  Future<void> add(Product productasync {}  
  Future<void> remove(String productIdasync {}  
  Future<void> clear() async {}  
  Future<double> total() async {}  
}

4. Widget 文件结构:一个 Widget,一个目的**

谷歌示例

// 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,一个目的。它存在于自己的文件中,使得代码库更易于维护和导航。

5. 常量:上下文驱动分组

谷歌示例

// 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 团队根据上下文对常量进行分组,而不是将所有常量都放在一个巨大的文件中。这使得查找和维护相关的常量变得更加容易。

6. 错误处理:明确且及时

谷歌示例

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 类型中。他们让错误自然地冒泡,并在适当的级别进行处理。

7. Widget 测试:行为重于实现

谷歌示例

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 等项目的应用测试更强调用户体验而非代码结构。

总结:

  • 保持 Widget 小巧且专注
  • 使用最简单的状态管理方案
  • 命名清晰简洁
  • 一个 Widget 一个文件
  • 根据上下文对常量进行分组
  • 明确处理错误
  • 在应用级测试中测试用户行为