# `main.dart`、`app.dart` 和 `const App()` 说明 ## `main.dart` 和 `app.dart` 应该放什么 以常见 Flutter App 项目结构来看,`main.dart` 和 `app.dart` 的职责应该分得很清楚: - `main.dart`:负责“启动前准备”和“启动 App” - `app.dart`:负责“描述整个 App 长什么样、用什么主题、从哪个页面开始、怎么路由” ## `main.dart` 应该放什么 `main.dart` 是程序入口,应该尽量薄。 最基础版本: ```dart import 'package:flutter/material.dart'; import 'app/app.dart'; void main() { runApp(const App()); } ``` 如果项目刚开始,这样就够了。 ## 稍微正式一点的 `main.dart` 如果后面需要初始化一些东西,可以这样: ```dart import 'package:flutter/material.dart'; import 'app/app.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); runApp(const App()); } ``` `WidgetsFlutterBinding.ensureInitialized()` 的作用是:在 `runApp` 之前确保 Flutter 框架初始化完成。 什么时候需要它: - 初始化 `SharedPreferences` - 初始化本地数据库 - 初始化 Firebase - 加载 `.env` - 初始化本地存储 - 设置屏幕方向 - 初始化依赖注入 例如以后可能会变成: ```dart Future main() async { WidgetsFlutterBinding.ensureInitialized(); await AppStorage.init(); await Firebase.initializeApp(); runApp(const App()); } ``` 但现在不需要提前写这些。 ## `main.dart` 不应该放什么 不建议在 `main.dart` 里放: - 页面 UI - `Scaffold` - `AppBar` - 首页布局 - 登录逻辑 - 网络请求 - 路由表 - 大量主题配置 - 业务状态管理 也就是说,不要让 `main.dart` 变成这样: ```dart void main() { runApp(MaterialApp( home: Scaffold( appBar: AppBar(...), body: ..., ), )); } ``` 这只适合临时 Demo,不适合项目长期维护。 ## `app.dart` 应该放什么 `app.dart` 放根组件,也就是整个应用壳子。 最基础版本: ```dart import 'package:flutter/material.dart'; import '../features/home/home_page.dart'; import 'theme.dart'; class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Patbond', debugShowCheckedModeBanner: false, theme: AppTheme.light, home: const HomePage(), ); } } ``` 这里负责: - App 标题 - 是否显示右上角 debug 标识 - 全局主题 - 首页入口 - 路由配置 - 本地化配置 - 全局导航配置 ## 如果暂时没有路由系统 项目刚开始,页面少,可以这样: ```dart return MaterialApp( title: 'Patbond', debugShowCheckedModeBanner: false, theme: AppTheme.light, home: const HomePage(), ); ``` 这种最简单。 适合: - 只有首页 - 页面数量很少 - 暂时不需要登录拦截 - 暂时不需要深链接 - 暂时不需要命名路由 ## 如果使用 Flutter 自带命名路由 可以这样: ```dart return MaterialApp( title: 'Patbond', debugShowCheckedModeBanner: false, theme: AppTheme.light, initialRoute: '/', routes: { '/': (_) => const HomePage(), '/login': (_) => const LoginPage(), '/profile': (_) => const ProfilePage(), }, ); ``` 适合小到中等项目。 优点: - 简单 - 不需要额外依赖 - 比 `Navigator.push` 字符串散落各处更统一 缺点: - 登录拦截不够优雅 - 嵌套路由不方便 - Web URL 管理不如 `go_router` ## 如果以后使用 `go_router` `app.dart` 会变成: ```dart import 'package:flutter/material.dart'; import 'router.dart'; import 'theme.dart'; class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Patbond', debugShowCheckedModeBanner: false, theme: AppTheme.light, routerConfig: appRouter, ); } } ``` 然后 `router.dart` 单独管理路由。 这种适合: - 页面多 - Web 地址栏需要正常变化 - 有登录态跳转 - 有底部 Tab - 有深链接 - 有权限拦截 ## `app.dart` 不应该放什么 不建议在 `app.dart` 里放: - 某个页面的具体 UI - 首页复杂布局 - 登录表单逻辑 - 网络请求实现 - 数据模型 - 业务判断细节 - 大量工具函数 `app.dart` 是应用壳,不是业务页面。 ## 推荐当前项目的版本 当前项目还很新,建议先用最简单的版本。 目录: ```text lib/ main.dart app/ app.dart theme.dart features/ home/ home_page.dart ``` `main.dart`: ```dart import 'package:flutter/material.dart'; import 'app/app.dart'; void main() { runApp(const App()); } ``` `app/app.dart`: ```dart import 'package:flutter/material.dart'; import '../features/home/home_page.dart'; import 'theme.dart'; class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Patbond', debugShowCheckedModeBanner: false, theme: AppTheme.light, home: const HomePage(), ); } } ``` `app/theme.dart`: ```dart import 'package:flutter/material.dart'; class AppTheme { const AppTheme._(); static ThemeData get light { return ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.deepPurple, ), useMaterial3: true, ); } } ``` `features/home/home_page.dart`: ```dart import 'package:flutter/material.dart'; class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return const Scaffold( body: Center( child: Text('Patbond'), ), ); } } ``` ## 职责总结 `main.dart`: ```text 负责启动 ``` 应该包含: ```text runApp 启动前初始化 全局异常捕获入口 ``` 不应该包含: ```text 页面 UI 业务逻辑 路由细节 主题细节 网络请求 ``` `app.dart`: ```text 负责 App 壳子 ``` 应该包含: ```text MaterialApp 主题配置入口 路由配置入口 首页入口 本地化配置入口 ``` 不应该包含: ```text 具体页面布局 业务逻辑 接口请求 复杂状态处理 ``` `home_page.dart`: ```text 负责首页 UI ``` 应该包含: ```text Scaffold AppBar body 首页交互 首页状态 ``` ## 关于 `const App()` 如果写: ```dart import 'package:flutter/material.dart'; import 'app/app.dart'; void main() { runApp(App()); } ``` IDE 提示要去掉或加上 `const`,通常是因为 `App` 构造函数是否支持 `const`。 在 Flutter 里,如果一个 Widget 可以在编译期确定,推荐写成: ```dart runApp(const App()); ``` 而不是: ```dart runApp(App()); ``` ## 正确写法 `main.dart`: ```dart import 'package:flutter/material.dart'; import 'app/app.dart'; void main() { runApp(const App()); } ``` `app/app.dart` 里面要这样写: ```dart import 'package:flutter/material.dart'; class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Patbond', debugShowCheckedModeBanner: false, home: const Scaffold( body: Center( child: Text('Patbond'), ), ), ); } } ``` 关键是这一行: ```dart const App({super.key}); ``` 如果 `App` 类里没有 `const` 构造函数,比如是这样: ```dart class App extends StatelessWidget { App({super.key}); @override Widget build(BuildContext context) { return MaterialApp(...); } } ``` 那 `runApp(const App());` 就不能用,IDE 可能会提示去掉 `const`。 ## 为什么 Flutter 推荐 `const` `const` 的好处是: - 编译期创建对象 - 减少运行时重复创建 Widget - Flutter rebuild 时更容易优化 - 代码语义更清楚:这个 Widget 本身没有可变状态 对于 `StatelessWidget`,只要构造参数也都是可 `const` 的,一般都应该写 `const` 构造函数。 ## 看到“让我去掉 const”的常见原因 ### 原因 1:`App` 构造函数不是 `const` 错误: ```dart class App extends StatelessWidget { App({super.key}); } ``` 正确: ```dart class App extends StatelessWidget { const App({super.key}); } ``` ### 原因 2:`App` 里面继承的不是 Widget,或者写法不完整 正确: ```dart class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(); } } ``` ### 原因 3:`App` 构造函数里有非 const 默认值或初始化逻辑 例如: ```dart class App extends StatelessWidget { App({super.key}) { print('init'); } } ``` 这种就不能是 `const` 构造函数。 ### 原因 4:`App` 构造函数接收了运行时对象 例如: ```dart class App extends StatelessWidget { final DateTime now; const App({super.key, required this.now}); } ``` 调用时不能这样: ```dart runApp(const App(now: DateTime.now())); ``` 因为 `DateTime.now()` 不是编译期常量。 ## 结论 优先写成: ```dart void main() { runApp(const App()); } ``` 并确保 `App` 里面是: ```dart class App extends StatelessWidget { const App({super.key}); } ``` 如果这样之后 IDE 还提示去掉 `const`,说明 `App` 构造函数或参数里有运行时内容。