Flutter 基础 - 给 React 开发者的入门指南

知识结构

Flutter 是什么

Flutter 是 Google 开发的开源 UI 框架,用于从单一代码库构建跨平台应用 (iOS、Android、Web、桌面)。

Flutter widgets are built using a modern framework that takes inspiration from React.

Flutter Introduction to Widgets

如果你熟悉 React,Flutter 会感觉非常亲切:

概念ReactFlutter
UI 范式声明式声明式
核心单元ComponentWidget
状态管理useState / ContextsetState / Provider
虚拟层Virtual DOMWidget Tree / Element Tree
语言JavaScript / TypeScriptDart

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 StatelessWidget
class 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 StatefulWidget
class 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 HooksFlutter StatefulWidget
constructor-createState
componentDidMountuseEffect(() => {}, [])initState
componentDidUpdateuseEffect(() => {}, [deps])didUpdateWidget
componentWillUnmountuseEffect returndispose
renderreturn JSXbuild
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.

React Reconciliation

Flutter 同理:

// React
{items.map(item => (
<ListItem key={item.id} data={item} />
))}
// Flutter
ListView.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,可以动态更新。由两部分组成:

  1. StatefulWidget 类:不可变的配置
  2. 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 FlexboxFlutter
display: flex; flex-direction: row;Row()
display: flex; flex-direction: column;Column()
justify-contentmainAxisAlignment
align-itemscrossAxisAlignment
flex: 1Expanded() / Flexible()
position: absoluteStack + 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.

Flutter Layout Constraints

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'),
)

导航

// 跳转到新页面 (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.

Flutter State Management

// 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.of
class 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适用场景
setStateuseState单个 Widget 内部状态
ProviderContext API跨组件共享,中小型应用
RiverpodReact Query + Jotai复杂状态,依赖注入
BlocRedux大型应用,事件驱动

主题与样式

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,因为:

  1. 性能:Dart 代码直接编译,避免 CSS 解析开销
  2. 类型安全:编译时检查样式错误
  3. 一致性:跨平台渲染完全一致

样式直接在 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'),
)

下一步学习

参考资料

Read Next

Web Security

Read Previous

Dart 语言基础