Flutter状态管理Provider详解

provider

可以进行依赖注入和状态管理,使用widget创建,适用于widget。

它是故意设计成使用widget来进行依赖注入和状态管理的,而不是纯使用dart类,像是stream这些。因为widget简单且健壮可伸缩。

使用weidget来进行状态管理可以保证。

  1. 维护性。
  2. 可测试和兼容性。
  3. 健壮性。

使用

暴露一个值

暴露一个对象实例(object instance)

除了暴露一个值的可访问性,provider还包括这个值的创建,监听,销毁。

为了暴露一个新创建的对象,可以使用provider的默认构造函数。不要使用.value命名构造函数类创建一个值对象,不然可能会造成其他不期望影响。

  • 可做 create中创建一个对象。
Provider(
  create:(_)=>new MyModel(),
  child:...
)
  • 不可做 使用Provider.value命名构造函数创建一个对象。
ChangeNotifierProvider.value(
  value:new MyModel(),
  child:...
)

如果创建了一个对象,它使用了为了可能变更的变量做参数,那么考虑使用ProxyProvider:

int count;

ProxyProvider0(
  update:L(_,__)=> new MyModel(count),
  child:...
)
复用一个已经存在的对象实例

如果有个对象实例,你想把其暴露在其他地方使用,那么你应该使用Provider的.value命名构造函数。

不这么做可能会造成在该对象还在使用时被dispose

  • 可做 使用ChangeNotifierProvider.value来提供一个已经存在的ChangeNotifier
MyChangeNotifier varibale;

ChangeNotifierProvider.value(
  value:variable,
  child:...
)
  • 不可做 在默认构造函数中重用CahangeNotifier
MyChangeNotifier variable;

ChangeNotifierProvider(
  create:(_)=>variable,
  child:...
)

读取一个值(获取暴露的值)

获取一个值最简单的方法是使用静态方法Provider.of<T>(BuildContext context)

这个方法会从当前的context在widget树中向根widget方向查找符合类型T的最近的值。(如果没有找到就throw)。

除了Provider.of方法我们也可以使用ConsumerSelector两个widget。
这对于高效的组织代码以及难以获取BuildContext的情况比较有帮助。

多个Provider的情况(嵌套)/MultiProvider

当在一个较大的应用中注入较多的数据时,Provider会飞快地嵌套多层。

Provider<Something>(
  create:(_)=>Something(),
  child: Provider<SomethingElse>(
    create:(_)=>SomethingElse(),
    child:Provider<AnotherThing>(
      create:(_)=>AnotherTing(),
      child:someWidget,
    )
  )
)

可以这样写

MultiProvider(
  Providers:[
    Provider<Somthing>(create:(_)=>Something()),
    Provider<SomthingElse>(create:(_)=>SomethingElse()),
    Provider<AnotherThing>(create:(_)=>AnotherTthing()),
  ],
  child:someWidget
)

上面代码的结果是严格的一样的。MultiProvider仅仅是改变了代码的形式。

ProxyProvider

从版本3.0.0开始增加了一个新的Provider:ProxyProvider。

ProxyProvider本身是一个Provider,它把其他多个provider的数据结合成一个新的对象,并且把这个结果发送一个一个Provider。

被结合的的这些provider中的任何一个数据更新了,这个新的对象都会更新。

下面这个例子使用了ProxyProvider,他把其他provider中的counter做了个中转。

Widget build(BuildContext context){
  return MultiProvider(
    providers:[
      ChangeNotifierProvider(create:(_)=>Counter()),
      ProxyProvider<Counter,Translations>(
        create:(_,counter,__)=>Translations(clunter.value),
      ),
      child:Foo()
    ]
  )
}

class Translations{
  const Translations(this._value);

  final init _value;

  String get title=>'You clicked: $_value times';
}

ProxyProvider有很多种变体,例如:

  • ProxyProvider vs ProxyProvider2 vs ProxyProvider3...

类名后的数字是指ProxyProvider依赖其他Provider的数量。

  • ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider,...他们的工作方式是类似的,相对于发送结果给一个Provider,一个ChangeNotifierProxyProvider会发送给一个ChangeNotifierProxyProvider

问答

在InitState中获取Provider发生了错误,怎么办?

这个错误是因为你想监听一个在其生命周期中不会被再次调用的provider。

这说明你不会再使用其他的生命周期(didChangeDependencies/build),或者你不在乎数据更新。
不要这么做

initState(){
  super.initState();
  print(Provider.of<Foo>(context).value);
}

你可以这么做

Value value;

didChangeDependencies(){
  super.didChangeDependencies();
  final value = Provider.of<Foo>(context).value;
  if(value != this.value){
    this.value = value;
    print(value);
  }
}

每当值发生了变化,都会被打印。
也可以这么做

initState(){
  super.initState();
  print(Provider.of<Foo>(context,listen:false).value);
}

这样只会打印value一次,不再更新。

我使用了ChangeNotifier,当我更新数据时发生了错误,发生了什么?

这个经常发生在widget树正在构建时,你对ChangeNotifier进行了更改操作。

一个典型的情景是,发起了一个http请求,然后该future被保存在了notifer中。

initState(){
  super.initState();
  Provider.of<Foo>(context).fetchSomething();
}

这样是禁止的,因为更改必须是立即的。

这意味着有些widget可能在变动之前build,然而其他的在变动之后build。这可能会造成你的ui发生冲突,所以是禁止的。

相比,你可以在整个widget树都同步之后(渲染前/选然后?)进行变动。

  • 直接在你的模型之内进行创建:
class Mymodel width ChangeNotifier{
  MyModel(){
    _fetchSomething();
  }
  
  Future<void> _fetchSomething()async {}
}

这个适用于没有额外参数的情况。

  • 一部发生在最后一帧:
initState(){
  super.initState();
  Future.microtash(()=>{
    Provider.of<Foo>(context).fetchSomething(someValue);
  })
}

这个多少是不太理想的,但是允许传入参数进行变更。

对于复杂的状态我是否必须使用ChangeNotifier?

不是。

你可以使用任何对象来呈现状态。例如其他可用的方式是Provider.value()结合一个StatefulWidget使用。

这里有个计数的例子,使用了这个方法:

class Example extends StatefulWidget{
  const Example({Key key, this.child}):super(key key);

  final Widget child;

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

class ExampleState extends State<Example>{
  int _count;

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

  @override
  Widget build(BuildContext context){
    return Provider.value(
      value:_count,
      child:Provider.value(
        value:this,
        child:widget.child
      )
    )
  }
}

可以如此读取数据:

return Text(Provider.of<int>(context).toString());

如此更改数据:

return FloatingActionButton(
  onPress:Provider.of<Examp0leState>(context).increment,
  child:Icon(Icons.plus_one),
);

此外,你也可以创建自己的provider。

我制作一个自己的Provider吗?

当然,provider暴露了所有的小的组件,这些制作了一个简陋的provider。

包括:

  • SingleChildCloneableWidget,可用来创建任何配个MultiProvider工作的widget。
  • InheritedProvider,通用的InheritedWidget,使用Provider.of来获取。
  • DelegateWidget/BuilderDelegate/ValueDelegate帮助处理"MyProvider() 创建一个对象" vs ”Myprovider.value() 随时间更新"的逻辑。

我的widget build太频繁,怎么办?

相较于Provider.of,你可以使用Consumer/Selector。

他们可选的child参数只允许重建widget中非常小的具体部分。

Foo(
  child:Consumer<A>(
    builder:(_,a,child){
      return Bar(a:a,child:child);
    }
    child: Baz(),
  ),
)

这个例子中只有Bar会在A更新时被重建,Foo和Baz非必要下不会更新。

更深一步,使用selector来忽略widget树中一些没有影响的更新也是可能的。

Selector<List,int>(
  selector:(_,list)=>list.length,
  builder:(_,length,__){
    return Text('$length');
  }
);

这个代码片段中,只有list的length变化时,才会被重构。即使一个item发生了变化也不会更新。

我能够使用同样的类型获取两个不同的provider吗?

不能。
你可以使用多个Provider共享同样的类型,一个widget只能获取到他们中的一个:最近的那个。

不然,你必须给与不同的provider不同的数据类型。

相较:

Provider<String>(
  create:(_)=>'england',
  child:Provider<String>(
    create:(_)=>'London',
    child:...
  )
)

推荐:

Provider<Country>(
  create:(_)=>'england',
  child:Provider<City>(
    create:(_)=>'London',
    child:...
  )
)

现有的providers

provider包提供了一些不同类新的'provider'应对不同类型的对象。
如下:

名字 说明
Provider provider的最基本形式,可以添加和暴露任何形式的值
ListenableProvider 使用Listenable对象的特殊provider,ListenableProvider会监听对象,并且在监听器在任何时候调用时要求widget重构。
ChangeNotifierProvider ChangeNotifier规格的ListenableProvider,他会在必要时自动调用ChangeNotifier.dispose.
ValueListenableProvider 监听一个ValueListenable并且只暴露ValueListenable.value。
StreamProvider 监听Stream并且暴露最新的emitted的值。
FutureProvider 添加一个Future,并且在future完成时更新附从。
阅读 3.9k

推荐阅读

近几年来前端的变化记录

38 人关注
38 篇文章
专栏主页