为什么当其父 StatelessWidget 重新运行 build() 时 Provider 不重新运行其 create() ?

问题描述 投票:0回答:1

我知道处理此类事情的更常见方法涉及使用

ChangeNotifierProvider
(CNP)等,但我想了解
Provider
实际上是如何工作的。

如果我有一个

StatelessWidget
Provider
在它的小部件树后代中的某个地方,并且
StatelessWidget
被重新创建(即,它的
build()
方法再次运行),我希望创建一个新的
Provider
。 因此,如果
Provider
是根据已传递给该
StatelessWidget
的值创建的,我希望使用该新值创建一个新的
Provider
。 然后,任何使用
Provider.of<T>(context, listen:true)
的下游小部件都会更新......我想。

...但这并没有发生。 当使用新的输入值重建父级

StatelessWidget
时,
Provider
不会重建自身。 为什么不呢?

再说一次,我并不是说这将是一个很好的方法,我只是想了解为什么会发生这样的情况。

这里有一些基本代码来说明我的意思: 在 DartPad 中

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: CounterUpper(),
        ),
      ),
    );
  }
}

class _CounterUpperState extends State<CounterUpper>
    with TickerProviderStateMixin {
  Ticker? ticker;
  double animationValue = 0;

  @override
  void initState() {
    print('Running _CenterUpperState.initState()');
    super.initState();
    ticker = Ticker((Duration elapsed) {
      if (elapsed.inSeconds - animationValue > 1) {
        setState(() {
          print(
              'Running _CenterUpperState.setState() with animationValue: $animationValue and elapsed.inSeconds: ${elapsed.inSeconds}');
          animationValue = elapsed.inSeconds.toDouble();
        });
      }
      if (elapsed.inSeconds > 5) {
        ticker?.stop();
      }
    });
    ticker!.start();
  }

  @override
  Widget build(BuildContext context) {
    print(
        'Running _CenterUpperState.build() with animationValue: $animationValue');
    return ProviderHolder(animationValue: animationValue);
  }
}

class ProviderHolder extends StatelessWidget {
  const ProviderHolder({super.key, required this.animationValue});

  final double animationValue;

  @override
  Widget build(BuildContext context) {
    print(
        'Running ProviderHolder.build() with animationValue: $animationValue');
    return Provider<ValueWrapper>(
      create: (_) {
        print('Running Provider.create() with animationValue: $animationValue');
        return ValueWrapper(animationValue);
      },
      child: ContentHolder(),
    );
  }
}

class ContentHolder extends StatelessWidget {
  const ContentHolder({super.key});

  @override
  Widget build(BuildContext context) {
    final double providerValue =
        Provider.of<ValueWrapper>(context, listen: true).value;
    print('Running ContentHolder.build() with providerValue: $providerValue');
    return Text(providerValue.toString());
  }
}

class ValueWrapper {
  const ValueWrapper(this.value);

  final double value;
}

class CounterUpper extends StatefulWidget {
  const CounterUpper({super.key});

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

我尝试过以多种不同的方式重新排列(父小部件的不同版本,包括

StatefulWidget
)。 我知道最常见的方法是使用一些本身可以操纵的对象的
Provider
。 类似于包含整数以及一些更改该整数值的方法的基本类。 然后,您只需使用这些方法来更改值,而不是重新创建父窗口小部件。

但是……为什么这不起作用? 它的行为就像

Provider
被定义为 const,即使它传递的值不是 const。

flutter provider
1个回答
0
投票

提供程序不会在重建时再次调用创建,有趣的是,您在自己的日志记录中包含此信息,创建日志仅被调用一次,这就是提供程序内部处理创建函数的方式(即使重建运行,提供程序选择忽略您的创建脚本,以免重新创建已存在的对象)。

就像留下一些选项的编程一样,

  1. 处理 ProviderHolder 的重建,以便在每次重建时完全处置 Provider,这将导致子级重建,并且子级从值提供者获取新的副本。这是低效的,因为在内存中您正在销毁整个对象并重新创建它。加上它可能在较大的类中调用的任何初始化。
  2. 不要使用提供程序,如果您正在做一些小事情,例如将应用程序的动画值更新为 2 个级别,只需手动传递值即可,直接传递值可能看起来很混乱,但它比直接传递值更易于维护整个状态对象。特别是对于本地化的动画项目,除非您正在做一些非常复杂的事情。
  3. 将您的逻辑移至您的提供程序中,以便您可以拥有 1 个状态对象,该对象创建一次,更新其自己的参数,然后根据谁正在侦听更新将构建指令发送到需要它的小部件。

选项 3 就是我要做的,类似这样的(快速完成,以便可以优化,但它可以作为一个基本示例):

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: CounterUpper(),
        ),
      ),
    );
  }
}

class CounterUpper extends StatefulWidget {
  const CounterUpper({super.key});

  @override
  State<CounterUpper> createState() => _CounterUpperState();
}

class _CounterUpperState extends State<CounterUpper> with TickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<ValueWrapper>(create: (_) => ValueWrapper(), child: const ContentHolder());
  }
}

class ContentHolder extends StatelessWidget {
  const ContentHolder({super.key});

  @override
  Widget build(BuildContext context) {
    return Text(Provider.of<ValueWrapper>(context, listen: true).animationValue.toString());
  }
}

class ValueWrapper with ChangeNotifier {
  ValueWrapper() : animationValue = 0 {
    ticker = Ticker((Duration elapsed) {
      if (elapsed.inSeconds - animationValue > 1) {
        updateAnimationValue(elapsed.inSeconds.toDouble());
        animationValue = elapsed.inSeconds.toDouble();
      }
      if (elapsed.inSeconds > 5) {
        ticker.stop();
      }
    });
    ticker.start();
  }

  double animationValue;
  late Ticker ticker;

  void updateAnimationValue(double value) {
    animationValue = value;
    notifyListeners();
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.