flutter 怎么减少 Widget 的重新构建?

发布于 1周前 作者 sinazl 来自 Flutter

Flutter 怎么减少 Widget 的重新构建?

在 Flutter 中,减少 Widget 的重新构建可以提高应用程序的性能,特别是在处理复杂的 UI 或者大量数据时。以下是一些有效的方法和技巧来减少 Widget 的重新构建,并附上代码和完整案例。

1. 使用 const 构造函数

如果 Widget 的内容在运行时不发生变化,可以使用 const 关键字来创建它们。这将使得 Flutter 能够在构建过程中重用这些 Widget,而不是每次都重新创建。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    const header = Text('This is a constant header');
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: header,
        ),
        body: Center(
          child: Text('Hello, Flutter!'),
        ),
      ),
    );
  }
}

2. 使用 ValueNotifierValueListenableBuilder

当需要管理简单的状态时,ValueNotifierValueListenableBuilder 是一个很好的选择。ValueNotifier 允许你只更新使用它的 Widget,而不是整个 Widget 树。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final ValueNotifier<String> valueNotifier = ValueNotifier<String>('Initial Value');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('ValueNotifier Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ValueListenableBuilder<String>(
                valueListenable: valueNotifier,
                builder: (_, value, __) {
                  return Text(value);
                },
              ),
              ElevatedButton(
                onPressed: () {
                  valueNotifier.value = 'Updated Value';
                },
                child: Text('Update Value'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

3. 使用 Provider 或 Riverpod

使用状态管理库如 Provider 或 Riverpod 可以更细粒度地控制 Widget 的重建。当状态变化时,只有依赖于该状态的 Widget 会重新构建。

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(ProviderContainer(
    child: MyApp(),
  ));
}

final counterProvider = StateProvider<int>((ref) => 0);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Consumer(
        builder: (context, watch, child) {
          final counter = watch(counterProvider);
          return Scaffold(
            appBar: AppBar(
              title: Text('Provider Example'),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text('Counter: ${counter.state}'),
                  ElevatedButton(
                    onPressed: () => context.read(counterProvider).state++,
                    child: Text('Increment'),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

4. 拆分 Widget

将大的 Widget 拆分为多个小的 Widget。当小的 Widget 状态变化时,只会重新构建受影响的 Widget,而不会影响整个父 Widget。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Widget Splitting Example'),
        ),
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  int counter = 0;

  void incrementCounter() {
    setState(() {
      counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        CounterDisplay(counter: counter),
        ElevatedButton(
          onPressed: incrementCounter,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

class CounterDisplay extends StatelessWidget {
  final int counter;

  CounterDisplay({required this.counter});

  @override
  Widget build(BuildContext context) {
    return Text('Counter: $counter');
  }
}

5. 使用 shouldComponentUpdate 方法(在 Flutter 中对应 didUpdateWidget

对于 StatefulWidget,可以重写 didUpdateWidget 方法,并在需要时决定是否重建 Widget。通过条件判断,可以控制 Widget 的重建。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('didUpdateWidget Example'),
        ),
        body: Center(
          child: MyWidget(counter: 0),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  final int counter;

  MyWidget({required this.counter});

  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  bool shouldRebuild = true;

  @override
  void didUpdateWidget(covariant MyWidget oldDelegate) {
    super.didUpdateWidget(oldDelegate);
    // Only rebuild if counter has changed
    shouldRebuild = oldDelegate.counter != widget.counter;
    if (shouldRebuild) {
      setState(() {});
    }
  }

  @override
  Widget build(BuildContext context) {
    if (!shouldRebuild) {
      return Container(); // Return an empty widget to avoid rebuild
    }
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('Counter: ${widget.counter}'),
        ElevatedButton(
          onPressed: () {
            // Simulate a parent widget updating the counter
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => MyApp(
                  // Simulate new counter value
                  home: Scaffold(
                    appBar: AppBar(title: Text('New Page')),
                    body: Center(
                      child: MyWidget(counter: widget.counter + 1),
                    ),
                  ),
                ),
              ),
            );
          },
          child: Text('Go to New Page'),
        ),
      ],
    );
  }
}

6. 使用 ListView.builder 和其他构建器

对于长列表或动态内容,使用 ListView.builderGridView.builder 等构建器可以在需要时懒惰地构建 Widget,避免不必要的构建。

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final List<String> items = List.generate(100, (index) => 'Item $index');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('ListView.builder Example'),
        ),
        body: ListView.builder(
          itemCount: items.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text(items[index]),
            );
          },
        ),
      ),
    );
  }
}

7. 使用 RepaintBoundary

在复杂的 UI 中,可以使用 RepaintBoundary 来创建一个单独的绘制区域,以便只重绘该区域,而不是整个 Widget 树。


更多关于flutter 怎么减少 Widget 的重新构建?的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于flutter 怎么减少 Widget 的重新构建?的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在 Flutter 中,减少 Widget 的重新构建(Rebuild)是提高应用性能的关键。Flutter 的性能优化通常涉及减少不必要的 Widget 构建和渲染。以下是一些常见的技术和代码示例,帮助你减少 Widget 的重新构建:

1. 使用 constfinal

确保不需要改变的变量使用 constfinal 关键字,这可以让 Dart 编译器知道这些变量是不可变的,从而可能优化构建过程。

const String title = 'My App';
final int initialCount = 0;

2. 使用 StatefulWidgetStatelessWidget

将不变的逻辑放在 StatelessWidget 中,将可变的状态放在 StatefulWidgetState 中。StatelessWidget 在构建过程中是不可变的,因此不会被不必要的重建。

class MyStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('I am stateless');
  }
}

class MyStatefulWidget extends StatefulWidget {
  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int count = 0;

  void increment() {
    setState(() {
      count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text('Count: $count'),
        Button(onPressed: increment, child: Text('Increment')),
      ],
    );
  }
}

3. 使用 keys 属性

为动态列表中的每个子 Widget 设置唯一的 key,可以帮助 Flutter 框架识别哪些子 Widget 需要更新,哪些可以保持不变。

List<Widget> generateList(List<String> items) {
  return items.map((item) => ListTile(
    key: ValueKey(item), // 使用 ValueKey 为每个 ListTile 设置唯一 key
    title: Text(item),
  )).toList();
}

4. 使用 memoization 技术

对于复杂的 Widget,可以通过自定义的 memoization 技术来缓存其构建结果。例如,使用 MemoizedWidgetprovider 包中的 selector

class MemoizedWidget extends StatelessWidget {
  final Widget Function() builder;
  final Object key;
  MemoizedWidget({required this.builder, required this.key});

  @override
  Widget build(BuildContext context) {
    final _MemoizedWidgetScope scope = _MemoizedWidgetScope.of(context);
    return scope.memoize(key, builder);
  }
}

class _MemoizedWidgetScope extends InheritedWidget {
  final Map<Object, Widget> _cache = {};

  _MemoizedWidgetScope({required Widget child}) : super(child: child);

  static _MemoizedWidgetScope? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<_MemoizedWidgetScope>()!;
  }

  Widget memoize(Object key, Widget Function() builder) {
    return _cache.putIfAbsent(key, builder) ?? _cache[key]!;
  }
}

5. 使用 Provider 包管理状态

使用 provider 包中的 select 函数,可以在 Widget 树中高效地选择性地重建部分 Widget。

final counterProvider = StateProvider<int>((ref) => 0);

// 在 Widget 树中使用
Consumer<int>(
  builder: (context, count, child) {
    return Text('Count: $count');
  },
  selector: (context, value) => value, // 选择要监听的状态
);

6. 避免在 build 方法中进行复杂计算

将复杂的计算移到 initState 或其他生命周期方法中,或者使用 computed 属性(类似于 MobX 或 Vuex)。

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  late final int computedValue;

  @override
  void initState() {
    super.initState();
    computedValue = complexComputation();
  }

  int complexComputation() {
    // 复杂的计算逻辑
    return 42;
  }

  @override
  Widget build(BuildContext context) {
    return Text('Computed Value: $computedValue');
  }
}

通过上述技术和代码示例,你可以有效地减少 Flutter 应用中 Widget 的重新构建,从而提升应用的性能和用户体验。

回到顶部