为什么 setState 不重建我的子小部件?



在这个计数器示例中,可在飞镖板上重现,我希望当我点击增量按钮时child小部件重建,因为它CounterParentState的构建树在同一棵树中。

那个或有一些关于小部件树或我不明白的setState方法。

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class Controller {
late CounterParentState view;
void attach(CounterParentState v) {
this.view = v;
}
int counter = 0;
void incrementCounter() {
counter++;
this.view.applyState();
}
}
class CounterChild extends StatelessWidget {
final Controller controller;
const CounterChild({Key? key, required this.controller}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${controller.counter}',
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
final controller = Controller();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CounterParent(
title: 'Flutter Demo Home Page',
controller: controller,
child: CounterChild(controller: controller),
),
);
}
}
class CounterParent extends StatefulWidget {
final Controller controller;
final String title;
final Widget child;
CounterParent({
Key? key,
required this.title,
required this.controller,
required this.child,
}) : super(key: key);
@override
CounterParentState createState() => CounterParentState();
}
class CounterParentState extends State<CounterParent> {
void applyState() {
setState(() {});
}
@override
void initState() {
widget.controller.attach(this);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: widget.child,
floatingActionButton: FloatingActionButton(
onPressed: widget.controller.incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

为什么我选择这个设计?

我选择这种设计是因为我希望能够重复使用CounterParent并自由替换CounterParentchild

class CounterStylishChild extends StatelessWidget {
final Controller controller;
const CounterStylishChild({Key? key, required this.controller}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times: (And I am stylish too)',
),
Text(
'${controller.counter}',
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}
}
class MyMoreStylishApp extends StatefulWidget {
const MyMoreStylishApp({Key? key}) : super(key: key);
@override
MyMoreStylishAppState createState() => MyMoreStylishAppState();
}
class MyMoreStylishAppState extends State<MyMoreStylishApp> {
final controller = Controller();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CounterParent(
title: 'Counter Parent with a better looking child',
controller: controller,
child: CounterStylishChild(controller: controller),
),
);
}
}

setState正在按预期工作并重建CounterParent,但您传递了一个存储在CounterParent中的Widget实例("child"),并且在MyAppState中构造时保持不变,该实例永远不会在您的代码中重建。这相当于"存储"一个小部件,有些人这样做是为了防止不必要的重建。

例如,AnimatedBuilder构件执行此操作是为了防止不必要地重新生成较大的子项,同时重新生成具有创建动画的更改值的控件。

AnimatedBuilder示例代码可能会提供更多理解:

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
child: Container(
width: 200.0,
height: 200.0,
color: Colors.green,
child: const Center(
child: Text('Whee!'),
),
),
builder: (BuildContext context, Widget? child) {
return Transform.rotate(
angle: _controller.value * 2.0 * math.pi,
child: child,
);
},
);
}

"复杂"子项(Container)被传递给存储小部件的AnimatedBuilder。然后,它可以将此小部件作为快速调用的builder中的参数传递。Transformwidget 在每次调用builder时都会获得新的旋转值,但潜在的"复杂"子项从未真正重建。

这与你在这里的情况相同,只是你的情况是无意的。不建议让多个小部件如此相互依赖并以如此复杂的方式组织,就像你在这里一样。

虽然你没有在你的问题中要求这个,但我会提供一个修复。这可能不是你想要的,它可能会破坏这个精心设计的目的,但这是制作像这个函数这样的模式的简单方法。如果此解决方案不令人满意,详细说明为什么要这样做将很有帮助。

CounterChild应在CounterParent中实例化。这样,当为父级调用setState时,将创建子项的新实例并重新生成该实例。

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class Controller {
late CounterParentState view;
void attach(CounterParentState v) {
this.view = v;
}
int counter = 0;
void incrementCounter() {
counter++;
this.view.applyState();
}
}
class CounterChild extends StatelessWidget {
final Controller controller;
const CounterChild({Key? key, required this.controller}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${controller.counter}',
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
final controller = Controller();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CounterParent(
title: 'Flutter Demo Home Page',
controller: controller,
),
);
}
}
class CounterParent extends StatefulWidget {
final Controller controller;
final String title;
CounterParent({
Key? key,
required this.title,
required this.controller,
}) : super(key: key);
@override
CounterParentState createState() => CounterParentState();
}
class CounterParentState extends State<CounterParent> {
void applyState() {
setState(() {});
}
@override
void initState() {
widget.controller.attach(this);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: CounterChild(controller: widget.controller),
floatingActionButton: FloatingActionButton(
onPressed: widget.controller.incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

如果您想将CounterChild保留在原处,可以考虑使用InheritedNotifier

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class Controller extends ChangeNotifier {
int counter = 0;
void incrementCounter() {
counter++;
notifyListeners();
}
}
class Counter extends InheritedNotifier<Controller> { 
const Counter({
Key? key,
required Controller controller,
required Widget child,
}) : super(key: key, child: child, notifier: controller);

static Controller of(BuildContext context) {
final Controller? result = context.dependOnInheritedWidgetOfExactType<Counter>()?.notifier;
assert(result != null, 'No Controller found in context');
return result!;
}
@override
bool updateShouldNotify(Counter old) => notifier?.counter != old.notifier?.counter;
}
class CounterChild extends StatelessWidget {
const CounterChild({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${Counter.of(context).counter}',
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
final Controller controller = Controller();

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Counter(
controller: controller,
child: CounterParent(
title: 'Flutter Demo Home Page',
child: CounterChild(),
),
),
);
}
}
class CounterParent extends StatefulWidget {
final String title;
final Widget child;
CounterParent({
Key? key,
required this.title,
required this.child,
}) : super(key: key);
@override
CounterParentState createState() => CounterParentState();
}
class CounterParentState extends State<CounterParent> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: widget.child,
floatingActionButton: FloatingActionButton(
onPressed: Counter.of(context).incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

或者,如果你想保留更多的原始结构,你需要传递你的MyAppsetState而不是你的CounterParentsetState这样父项和子项都重建:

import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class Controller extends ChangeNotifier {
int counter = 0;
void incrementCounter() {
counter++;
notifyListeners();
}
}
class CounterChild extends StatelessWidget {
final Controller controller;

const CounterChild({Key? key, required this.controller}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${controller.counter}',
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
late final Controller controller;

@override
void initState() {
super.initState();

controller = Controller()
..addListener(()=>setState((){}));
}

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CounterParent(
controller: controller,
title: 'Flutter Demo Home Page',
child: CounterChild(controller: controller),
),
);
}
}
class CounterParent extends StatefulWidget {
final String title;
final Widget child;
final Controller controller;
CounterParent({
Key? key,
required this.title,
required this.controller,
required this.child,
}) : super(key: key);
@override
CounterParentState createState() => CounterParentState();
}
class CounterParentState extends State<CounterParent> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: widget.child,
floatingActionButton: FloatingActionButton(
onPressed: widget.controller.incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

最新更新