Flutter 基础 - 给 React 开发者的入门指南
知识结构
Flutter 是什么
Flutter 是 Google 开发的开源 UI 框架,用于从单一代码库构建跨平台应用 (iOS、Android、Web、桌面)。
Flutter widgets are built using a modern framework that takes inspiration from React.
如果你熟悉 React,Flutter 会感觉非常亲切:
| 概念 | React | Flutter |
|---|---|---|
| UI 范式 | 声明式 | 声明式 |
| 核心单元 | Component | Widget |
| 状态管理 | useState / Context | setState / Provider |
| 虚拟层 | Virtual DOM | Widget Tree / Element Tree |
| 语言 | JavaScript / TypeScript | Dart |
React vs Flutter 核心对比
组件 vs Widget
在 React 中,UI 由组件构成。在 Flutter 中,一切皆 Widget。
flowchart LR
subgraph React
FC["函数组件<br/>Function Component"]
CC["类组件<br/>Class Component"]
end
subgraph Flutter
SL["StatelessWidget"]
SF["StatefulWidget"]
end
FC --> SL
CC --> SF
React 函数组件 vs Flutter StatelessWidget
// React 函数组件function Greeting({ name }) { return <h1>Hello, {name}!</h1>;}// Flutter StatelessWidgetclass Greeting extends StatelessWidget { final String name; const Greeting({super.key, required this.name});
@override Widget build(BuildContext context) { return Text('Hello, $name!'); }}React 类组件 vs Flutter StatefulWidget
// React 类组件 (现在更常用 Hooks)class Counter extends React.Component { state = { count: 0 };
increment = () => { this.setState({ count: this.state.count + 1 }); };
render() { return ( <div> <p>Count: {this.state.count}</p> <button onClick={this.increment}>Add</button> </div> ); }}// Flutter StatefulWidgetclass Counter extends StatefulWidget { const Counter({super.key});
@override State<Counter> createState() => _CounterState();}
class _CounterState extends State<Counter> { int _count = 0;
void _increment() { setState(() { _count++; }); }
@override Widget build(BuildContext context) { return Column( children: [ Text('Count: $_count'), ElevatedButton( onPressed: _increment, child: const Text('Add'), ), ], ); }}Props vs 构造函数参数
React 通过 props 传递数据,Flutter 通过构造函数参数:
// React<CustomCard index={1} onPress={() => console.log('pressed')} />// Flutter - 使用命名参数 (推荐)CustomCard( index: 1, onPress: () => print('pressed'),)Flutter 使用 required 关键字标记必填参数:
class CustomCard extends StatelessWidget { final int index; final VoidCallback onPress;
const CustomCard({ super.key, required this.index, // 必填 required this.onPress, });
// ...}生命周期对比
Flutter 的 StatefulWidget 生命周期与 React 类组件相似:
| React 类组件 | React Hooks | Flutter StatefulWidget |
|---|---|---|
constructor | - | createState |
componentDidMount | useEffect(() => {}, []) | initState |
componentDidUpdate | useEffect(() => {}, [deps]) | didUpdateWidget |
componentWillUnmount | useEffect return | dispose |
render | return JSX | build |
class MyWidget extends StatefulWidget { const MyWidget({super.key});
@override State<MyWidget> createState() => _MyWidgetState();}
class _MyWidgetState extends State<MyWidget> { @override void initState() { super.initState(); // 类似 componentDidMount // 初始化控制器、订阅等 }
@override void didUpdateWidget(MyWidget oldWidget) { super.didUpdateWidget(oldWidget); // 类似 componentDidUpdate // 当 widget 配置改变时调用 }
@override void dispose() { // 类似 componentWillUnmount // 清理资源、取消订阅 super.dispose(); }
@override Widget build(BuildContext context) { // 类似 render return const Text('Hello'); }}Keys 的作用
React 和 Flutter 都使用 Keys 来优化列表渲染:
When children have keys, React uses the key to match children in the original tree with children in the subsequent tree.
Flutter 同理:
// React{items.map(item => ( <ListItem key={item.id} data={item} />))}// FlutterListView.builder( itemCount: items.length, itemBuilder: (context, index) { final item = items[index]; return ListItem( key: ValueKey(item.id), // 使用 ValueKey data: item, ); },)Flutter 提供多种 Key 类型:
| Key 类型 | 用途 |
|---|---|
ValueKey | 基于值的标识 (推荐用于列表) |
UniqueKey | 每次都生成新标识 |
GlobalKey | 跨 Widget 树访问状态 |
Widget 基础
StatelessWidget
无状态 Widget,一旦创建就不会改变 (除非父 Widget 重建)。
适用场景:
- 纯展示组件
- 配置不变的 UI 元素
- 类似 React 的纯函数组件
class WelcomeMessage extends StatelessWidget { final String username;
const WelcomeMessage({super.key, required this.username});
@override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue.shade100, borderRadius: BorderRadius.circular(8), ), child: Text( 'Welcome, $username!', style: const TextStyle(fontSize: 20), ), ); }}StatefulWidget
有状态 Widget,可以动态更新。由两部分组成:
- StatefulWidget 类:不可变的配置
- State 类:持有可变状态
class TodoItem extends StatefulWidget { final String title;
const TodoItem({super.key, required this.title});
@override State<TodoItem> createState() => _TodoItemState();}
class _TodoItemState extends State<TodoItem> { bool _isCompleted = false;
void _toggleComplete() { setState(() { _isCompleted = !_isCompleted; }); }
@override Widget build(BuildContext context) { return ListTile( leading: Checkbox( value: _isCompleted, onChanged: (_) => _toggleComplete(), ), title: Text( widget.title, // 通过 widget 访问父类属性 style: TextStyle( decoration: _isCompleted ? TextDecoration.lineThrough : TextDecoration.none, ), ), ); }}第一个 Flutter 应用
试试下面的交互式示例:
布局系统
Flutter 布局 vs CSS Flexbox
Flutter 的布局系统与 CSS Flexbox 非常相似:
| CSS Flexbox | Flutter |
|---|---|
display: flex; flex-direction: row; | Row() |
display: flex; flex-direction: column; | Column() |
justify-content | mainAxisAlignment |
align-items | crossAxisAlignment |
flex: 1 | Expanded() / Flexible() |
position: absolute | Stack + Positioned |
Row 和 Column
// 水平排列 (类似 flex-direction: row)Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, // justify-content crossAxisAlignment: CrossAxisAlignment.center, // align-items children: [ Icon(Icons.star), Icon(Icons.star), Icon(Icons.star), ],)
// 垂直排列 (类似 flex-direction: column)Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text('Title'), Text('Subtitle'), ElevatedButton( onPressed: () {}, child: const Text('Action'), ), ],)约束模型
Flutter 布局的核心原则:
Constraints flow down. Sizes flow up. Parents set positions.
flowchart TB
subgraph Constraint["约束传递 (向下)"]
P1["Parent"] -->|"max/min width/height"| C1["Child"]
end
subgraph Size["尺寸反馈 (向上)"]
C2["Child"] -->|"actual size"| P2["Parent"]
end
subgraph Position["位置设定"]
P3["Parent 决定 Child 的位置"]
end
Container
类似 HTML 的 div,是最常用的容器 Widget:
Container( width: 200, height: 100, padding: const EdgeInsets.all(16), margin: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: const Text('Hello Flutter'),)Stack 和 Positioned
层叠布局,类似 CSS 的 position: absolute:
Stack( children: [ // 底层:背景图 Container( width: 300, height: 200, color: Colors.grey.shade300, ), // 中层:居中文字 const Center( child: Text('Centered'), ), // 顶层:角标 Positioned( top: 8, right: 8, child: Container( padding: const EdgeInsets.all(4), decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), child: const Text('3', style: TextStyle(color: Colors.white)), ), ), ],)ListView
可滚动列表,类似 React Native 的 FlatList:
// 静态列表ListView( children: [ ListTile(title: Text('Item 1')), ListTile(title: Text('Item 2')), ListTile(title: Text('Item 3')), ],)
// 动态列表 (推荐,按需构建)ListView.builder( itemCount: 100, itemBuilder: (context, index) { return ListTile( leading: CircleAvatar(child: Text('${index + 1}')), title: Text('Item $index'), subtitle: Text('Description for item $index'), trailing: const Icon(Icons.chevron_right), ); },)常用组件
按钮
// 填充按钮 (主要操作)ElevatedButton( onPressed: () {}, child: const Text('Submit'),)
// 轮廓按钮 (次要操作)OutlinedButton( onPressed: () {}, child: const Text('Cancel'),)
// 文字按钮 (低优先级)TextButton( onPressed: () {}, child: const Text('Learn more'),)
// 图标按钮IconButton( onPressed: () {}, icon: const Icon(Icons.favorite),)
// 浮动操作按钮FloatingActionButton( onPressed: () {}, child: const Icon(Icons.add),)输入框
// 基础输入框TextField( decoration: const InputDecoration( labelText: 'Email', hintText: 'Enter your email', prefixIcon: Icon(Icons.email), border: OutlineInputBorder(), ), keyboardType: TextInputType.emailAddress, onChanged: (value) { print('Input: $value'); },)
// 受控输入框 (类似 React 受控组件)class ControlledInput extends StatefulWidget { @override State<ControlledInput> createState() => _ControlledInputState();}
class _ControlledInputState extends State<ControlledInput> { final _controller = TextEditingController();
@override void dispose() { _controller.dispose(); // 记得清理! super.dispose(); }
@override Widget build(BuildContext context) { return TextField( controller: _controller, onSubmitted: (value) { print('Submitted: $value'); _controller.clear(); }, ); }}图片
// 网络图片Image.network( 'https://example.com/image.png', width: 200, height: 200, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return const CircularProgressIndicator(); }, errorBuilder: (context, error, stackTrace) { return const Icon(Icons.error); },)
// 本地资源 (需在 pubspec.yaml 配置)Image.asset('assets/images/logo.png')
// 圆形头像CircleAvatar( radius: 40, backgroundImage: NetworkImage('https://example.com/avatar.png'),)导航
Navigator 基本用法
// 跳转到新页面 (push)Navigator.push( context, MaterialPageRoute(builder: (context) => const DetailPage()),);
// 返回上一页 (pop)Navigator.pop(context);
// 返回并传递数据Navigator.pop(context, 'result data');
// 等待返回结果final result = await Navigator.push( context, MaterialPageRoute(builder: (context) => const SelectionPage()),);print('Selected: $result');命名路由
// 在 MaterialApp 中定义路由MaterialApp( initialRoute: '/', routes: { '/': (context) => const HomePage(), '/detail': (context) => const DetailPage(), '/settings': (context) => const SettingsPage(), },)
// 使用命名路由跳转Navigator.pushNamed(context, '/detail');
// 带参数跳转Navigator.pushNamed( context, '/detail', arguments: {'id': 123, 'title': 'Hello'},);
// 在目标页面获取参数class DetailPage extends StatelessWidget { @override Widget build(BuildContext context) { final args = ModalRoute.of(context)!.settings.arguments as Map; return Text('ID: ${args['id']}'); }}状态管理
setState (局部状态)
类似 React 的 useState,适用于单个 Widget 内部状态:
class ToggleButton extends StatefulWidget { @override State<ToggleButton> createState() => _ToggleButtonState();}
class _ToggleButtonState extends State<ToggleButton> { bool _isOn = false;
@override Widget build(BuildContext context) { return Switch( value: _isOn, onChanged: (value) { setState(() { _isOn = value; }); }, ); }}Provider (跨组件状态)
类似 React 的 Context API,是 Flutter 官方推荐的状态管理方案:
Provider is the recommended approach for managing state that needs to be shared between widgets.
// 1. 创建状态类class CartModel extends ChangeNotifier { final List<String> _items = [];
List<String> get items => List.unmodifiable(_items); int get itemCount => _items.length;
void add(String item) { _items.add(item); notifyListeners(); // 通知监听者更新 }
void remove(String item) { _items.remove(item); notifyListeners(); }}
// 2. 在顶层提供状态 (类似 React Context.Provider)void main() { runApp( ChangeNotifierProvider( create: (_) => CartModel(), child: const MyApp(), ), );}
// 3. 消费状态 (类似 useContext)class CartIcon extends StatelessWidget { @override Widget build(BuildContext context) { // 方式一:Consumer return Consumer<CartModel>( builder: (context, cart, child) { return Badge( label: Text('${cart.itemCount}'), child: const Icon(Icons.shopping_cart), ); }, ); }}
// 方式二:Provider.ofclass CartButton extends StatelessWidget { @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () { // 不监听变化,只调用方法 Provider.of<CartModel>(context, listen: false).add('New Item'); }, child: const Text('Add to Cart'), ); }}状态管理方案对比
| 方案 | 类比 React | 适用场景 |
|---|---|---|
setState | useState | 单个 Widget 内部状态 |
Provider | Context API | 跨组件共享,中小型应用 |
Riverpod | React Query + Jotai | 复杂状态,依赖注入 |
Bloc | Redux | 大型应用,事件驱动 |
主题与样式
ThemeData
类似 CSS 变量或 styled-components 的主题:
MaterialApp( theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.light, ), textTheme: const TextTheme( headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold), bodyMedium: TextStyle(fontSize: 16), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), ), ), darkTheme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.dark, ), ), themeMode: ThemeMode.system, // 跟随系统 home: const MyHomePage(),)
// 使用主题class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); return Text( 'Hello', style: theme.textTheme.headlineLarge, ); }}TextStyle
const Text( 'Styled Text', style: TextStyle( fontSize: 24, fontWeight: FontWeight.w600, color: Colors.blue, letterSpacing: 1.5, height: 1.4, // 行高 decoration: TextDecoration.underline, ),)项目结构
my_flutter_app/├── lib/│ ├── main.dart # 入口文件│ ├── app.dart # MaterialApp 配置│ ├── models/ # 数据模型│ ├── screens/ # 页面 (类似 pages)│ ├── widgets/ # 可复用组件│ ├── services/ # 业务逻辑、API│ └── utils/ # 工具函数├── assets/ # 静态资源│ ├── images/│ └── fonts/├── test/ # 测试文件├── android/ # Android 原生代码├── ios/ # iOS 原生代码└── pubspec.yaml # 依赖配置 (类似 package.json)常见问题
何时用 StatelessWidget vs StatefulWidget
flowchart TD
A["组件需要管理自己的状态吗?"] -->|Yes| B["StatefulWidget"]
A -->|No| C["数据只来自父组件?"]
C -->|Yes| D["StatelessWidget"]
C -->|No| E["使用 Provider/Riverpod 等"]
为什么 Flutter 没有 CSS
Flutter 不使用 CSS,因为:
- 性能:Dart 代码直接编译,避免 CSS 解析开销
- 类型安全:编译时检查样式错误
- 一致性:跨平台渲染完全一致
样式直接在 Widget 属性中定义:
// CSS: padding: 16px; margin: 8px; background-color: blue;Container( padding: const EdgeInsets.all(16), margin: const EdgeInsets.all(8), color: Colors.blue, child: const Text('Hello'),)下一步学习
- Dart 语言基础 - 掌握 Dart 语法
- Flutter 底层原理 - 理解三棵树、渲染机制
- Flutter Impeller 渲染引擎 - 新一代渲染引擎
参考资料
- Flutter for React Native Developers - 官方对比指南
- Flutter for Web Developers - Web 开发者指南
- Introduction to Widgets - Widget 入门
- Flutter Layout Constraints - 约束布局模型
- State Management Options - 状态管理方案