在上篇文章中,已经对Provider的用法有了一定了解,下面在看下context.read
extension ReadContext on BuildContext {
T read() {
return Provider.of(this, listen: false);
}
}
extension WatchContext on BuildContext {
T watch() {
return Provider.of(this);
}
}
ReadContext暴露了一个read方法,可以从最近的父Provider中获取类型为T的值,这个正好跟WatchContext.watch方法相反的。如果用read方法获取的Provider,那么当value改变的时候,不会使页面重建,而且这个方法不能再StatelessWidget.build和State.build方法中调用,也就是说可在这些方法外面随意调用,如果这种方式不符合自己的风格,完全可用Provider.of(conext, listen:false)的方式,毕竟read方式只是它的封装方式,虽然是做同样的事,但是没有上面说的限制。不要在build方法里调用read方法,如果只是用于时间处理的话,比如下面的代码:
Widget build(BuildContext context) {
// counter is used only for the onPressed of RaisedButton
final counter = context.read();
return RaisedButton(
onPressed: () => counter.increment(),
);
虽然这么调用是本身是没bug的,这是个反例,它可能造成bug在未来重构widget的时候,在其他场景下使用counter,但是忘记把read方法改成watch获取provider,所以应该考虑在事件句柄里面调用read,就像下面这样:
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
// as performant as the previous solution, but resilient to refactoring
context.read().increment(),
},
);
}
上面这两种方式效果上是一样的,但是后面一种更加健壮。不要在构建widget时,使用read方式去获取一个从不改变的值,即使不会导致widget重构,虽然这个想法是好的,但也不该用read:
Widget build(BuildContext context) {
// using read because we only use a value that never changes.
final model = context.read();
return Text('${model.valueThatNeverChanges}');
}
read只要是用来解决优化比较脆弱的,依赖层度高的问题,在上面这种情况中,可以考虑使用select方法,如果不想重构的话:
Widget build(BuildContext context) {
// Using select to listen only to the value that used
final valueThatNeverChanges = context.select((Model model) => model.valueThatNeverChanges);
return Text('$valueThatNeverChanges');
}
虽然用select的方式更多代码,但是更安全,它不依赖Model的具体实现,不会造成UI不刷新的bug。使用read方法可以以一种更简单的方式去依赖其他对象,更随意的传递其他对象,而且可以不用依赖BuildContext:
class Model {
Model(this.context);
final BuildContext context;
void method() {
print(Provider.of(context));
}
}
// ...
Provider(
create: (context) => Model(context),
child: ...,
)
相比上面的方式,我们更倾向于下面方式:
class Model {
Model(this.read);
// `Locator` is a typedef that matches the type of `read`
final Locator read;
void method() {
print(read());
}
}
// ...
Provider(
create: (context) => Model(context.read),
child: ...,
)
上面两种方式,效果是一样的,但是呢,第二种方式中,Model可以不用依赖使用BuildContext的provider。WatchContext中的watch方法和ReadContext中的read方法是相似的,但是watch方法会导致widget重构,在获取到值变化的时候,在上面一段代码中,有个Locator类型的参数,它可以让其他对象使用read更加方便。使用watch方法,其实等效于Provider.of(context, listen: true),这个方法只能在StatelessWidget和State.build中使用,如果需要在其他地方使用可以使用Provider.of方法替换,listen需要设置为false,因为它没有这个限制,这个限制也是使用Provider刷新数据时唯一一个异常。