Flutter go_router用pushNamed导航时前页面重渲染是否正常?
问题描述
在Flutter项目中使用go_router时,调用pushNamed导航到新页面时,之前的页面会被重渲染,想确认这是否是预期行为。
路由结构
HomePage: route path: `/` -- ScreenWithUniqueKey: route path/name: `screen_with_unique_key` -- ScreenWithoutUniqueKey: route path/name: `screen_without_unique_key` -- -- DetailPage: route path/name: `detail_page`
测试结果
在除HomePage外的每个页面的build方法中添加了打印语句,测试场景及输出如下:
路径:HomePage -> goNamed -> ScreenWithUniqueKey -> pushNamed -> DetailPage
build ScreenWithUniqueKey build DetailPage build ScreenWithUniqueKey路径:HomePage -> goNamed -> ScreenWithUniqueKey -> pushReplacementNamed -> DetailPage
build ScreenWithoutUniqueKey build DetailPage路径:HomePage -> goNamed -> ScreenWithoutUniqueKey -> pushNamed -> DetailPage
build ScreenWithoutUniqueKey build DetailPage build ScreenWithoutUniqueKey路径:HomePage -> goNamed -> ScreenWithoutUniqueKey -> pushReplacementNamed -> DetailPage
build ScreenWithoutUniqueKey build DetailPage
示例代码
运行环境:Dart 3.4.1、Flutter 3.22.1、go_router 14.1.4
import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; void main() { runApp(const MyApp()); } final GoRouter _router = GoRouter( routes: <RouteBase>[ GoRoute( path: '/', builder: (BuildContext context, GoRouterState state) { return const HomeScreen(); }, routes: <RouteBase>[ GoRoute( path: 'screen_with_unique_key', name: 'screen_with_unique_key', builder: (_1, _2) => ScreenWithUniqueKey(key: UniqueKey()), ), GoRoute( path: 'screen_without_unique_key', name: 'screen_without_unique_key', builder: (_1, _2) => ScreenWithoutUniqueKey(), ), GoRoute( path: 'detail_page', name: 'detail_page', builder: (_1, _2) => DetailPage(), ), ], ), ], ); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: _router, ); } } class HomeScreen extends StatelessWidget { /// Constructs a [HomeScreen] const HomeScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Home Screen')), body: Center( child: Column( children: [ ElevatedButton( onPressed: () => context.go('/screen_with_unique_key'), child: const Text('Go to the Screen With Unique Key'), ), ElevatedButton( onPressed: () => context.go('/screen_without_unique_key'), child: const Text('Go to the Screen Without Unique Key'), ), ], ), ), ); } } class ScreenWithUniqueKey extends StatelessWidget { const ScreenWithUniqueKey({super.key}); @override Widget build(BuildContext context) { print('build ScreenWithUniqueKey'); return Scaffold( appBar: AppBar(title: const Text('Screen With Unique Key')), body: Center( child: Column( children: [ ElevatedButton( onPressed: () => context.pushNamed('detail_page'), child: const Text('Push Named to Detail Page'), ), ElevatedButton( onPressed: () => context.pushReplacementNamed('detail_page'), child: const Text('Push Reaplcement Named to Detail Page'), ), ], ), ), ); } } class ScreenWithoutUniqueKey extends StatelessWidget { const ScreenWithoutUniqueKey({super.key}); @override Widget build(BuildContext context) { print('build ScreenWithoutUniqueKey'); return Scaffold( appBar: AppBar(title: const Text('Screen Without Unique Key')), body: Center( child: Column( children: [ ElevatedButton( onPressed: () => context.pushNamed('detail_page'), child: const Text('Push Named to Detail Page'), ), ElevatedButton( onPressed: () => context.pushReplacementNamed('detail_page'), child: const Text('Push Reaplcement Named to Detail Page'), ), ], ), ), ); } } class DetailPage extends StatelessWidget { const DetailPage({super.key}); @override Widget build(BuildContext context) { print('build DetailPage'); return Scaffold( appBar: AppBar(title: const Text('Detail Page')), body: Center( child: ElevatedButton( onPressed: () => context.pop(), child: const Text('Go back'), ), ), ); } }
解答
这属于go_router的预期行为,核心原因有两点:
路由状态更新触发重建:调用
pushNamed时,go_router会更新内部路由栈状态,而MaterialApp.router会监听该状态变化并触发全局重建。父页面作为路由树的一部分,会随着状态更新被重新build。UniqueKey的额外影响:
ScreenWithUniqueKey使用了UniqueKey(),每次父组件重建时这个Key都会生成新值,Flutter会判定这是一个全新的Widget,进而强制重建该页面,这也是带UniqueKey的页面同样出现重复build的原因。
优化建议
- 若要避免不必要的重建,可将页面改为
StatefulWidget并混入AutomaticKeepAliveClientMixin,保持页面状态不被销毁; - 不要随意给页面设置
UniqueKey,仅在需要强制Flutter重建组件时使用; - 对性能敏感的页面,可使用
const构造器(若允许),或通过Memoized缓存组件,减少无意义的build。
内容的提问来源于stack exchange,提问作者Ben

