Files
patbond-flutter/doc/main_app_const.md
T
2026-07-15 12:01:04 +08:00

583 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `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<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const App());
}
```
`WidgetsFlutterBinding.ensureInitialized()` 的作用是:在 `runApp` 之前确保 Flutter 框架初始化完成。
什么时候需要它:
- 初始化 `SharedPreferences`
- 初始化本地数据库
- 初始化 Firebase
- 加载 `.env`
- 初始化本地存储
- 设置屏幕方向
- 初始化依赖注入
例如以后可能会变成:
```dart
Future<void> 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` 构造函数或参数里有运行时内容。