Contents

Flutter 中 provider 的使用

Provider 的引入

Provider 解决的是组件之间传递数据的问题 在 Vue 中,父组件通过 props 传递数据给子组件,子组件通过 $emit 通知父组件,然后 Vue 重新渲染 DOM 在 Vue 中,也可以使用 provider 和 inject 传递数据,不过不能很好的处理响应性

与 Vue 相同的是,Flutter 的 Provider 的引入解决了数据传递中嵌套组件过多的问题,若在当前节点没有找到 Provider 组件, 则会通过上下文寻找 Provider ,直至找到

不过想使用 Provider,父组件得暴露出一个对象用以交互, 而这个对象得继承自 ChangeNotifier 以 ChangeNotifierProvider为例

ChangeNotifierProvider(
  builder: (context) => ExposedObject,
  child: ...
)

子组件想使用其中的值,得通过 Consumer 组件或者 context.read<ExposedType>

Consumer(
  create: (context, ExposedType expose, child) {
    // call expose
  }
)
var expose = context.read<ExposedType>();

注意

  1. 除了 context.read 外,还有 context.watch
  2. context.watch 会监听 Provider 的变化,调用后会更改页面的渲染,
  3. context.read 不会监听 Provider 的变化,调用后不会更改页面的渲染,所以不要用在 Widget 的 build 方法中

Provider 与 StatefulWidget

Provider 与 StatefulWidget 类似,都是把组件和状态分离开来, 其中 Statefulwidget 使用的是 State 类型来 build 组件,通过 setState 来通知变化 而 Provider 渲染的是其 child 组件,暴露出的组件通过调用 notifyListener 来通知变化,这个时候 Flutter 会丢弃 修改过的组件,直接换新的

Counter with Provider

首先是拥有 Provider 的父组件 App

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Counter with Provider',
      home: Scaffold(
	appBar: AppBar(title: Text('Counter with Provider'),),
	body: ChangeNotifierProvider(
	  create: (_) => CounterState(),
	  child: Counter(),
	),
      )
    );
  }
}

接着是暴露出的对象 CounterState

class CounterState extends ChangeNotifier {
  int count = 0;

  void increment() {
    count += 1;
    notifyListeners();
  }
}

再是使用 Provider 的子组件 Counter

class Counter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
	Text('You have pushed button many times: '),
	Text(context.watch<CounterState>().count.toString(), style: TextStyle(fontSize: 16)),
	FlatButton(
	  child: Text('click me'),
	  onPressed: () {
	    var state = context.read<CounterState>();
	    state.increment();
	  },
	),
      ]
    );
  }
}

不过我还是更推荐用 Consumer ,简洁明了

@override
Widget build(BuildContext context) {
  return Consumer(
    builder: (context, CounterState state, child) {
      return Column(
	children: [
	  Text('You have pushed button many times: '),
	  Text(state.count.toString(), style: TextStyle(fontSize: 16)),
	  FlatButton(
	    child: Text('Click me'),
	    onPressed: () {
	      state.increment();
	    }
	  )
	]
      )
    }
  )
}

总结

总的来说,使用 Provider 需要注意几点

  1. 提供暴露的接口
  2. 状态的更改在接口内部
  3. 状态的更改需要通知 Provider