Riverpod之Provider(一),使用Provider讲解了WidgetRef和Ref的watch要点
Riverpod之StateProvider(二),讲解了StateProvider的内部流程,主要涉及的是其内部的名叫state的Provider。
Riverpod之Provider&StateProvider(三),讲解了Provider和StateProvider的组合使用。
Riverpod之StateNotifierProvider(四),介绍了StateNotifierProvider的使用。
Riverpod之FutureProvider(五),介绍了FutureProvider的使用。
Riverpod之select(六),介绍了Provider的select方法使用和原理。
Riverpod之family(七),介绍了family方法得使用和内部流程
Riverpod之autoDispose(八),介绍了autoDispose方法的使用和内部流程
Riverpod之override(九),介绍了override属性的使用和内部流程
StateProvider主要用于处理状态为基本类型数据的场景,像字符串、数字、枚举之类的,官方甚至说如果复杂系数超过count++这样的,就得用StateNotifierProvider(ChangeNotifierProvider是为了方便从Provider库向Riverpod过渡,状态值是可变的,并不推荐使用)。
其实它们内部流程差不多,区别在于:StateProvider帮你实现了StateController把简单场景处理了,而StateNotifierProvider需要自己实现类似的StateController应对复杂场景。
Demo
我们用User模拟一个复杂对象
class User {
final int age;
final String displayName;
User({
required this.displayName,
required this.age,
});
User copyWith({
String? displayName,
int? age,
}) {
return User(
displayName: displayName ?? this.displayName, age: age ?? this.age);
}
}
使用StateNotifierProvider,其泛型参数比其它的Provider要复杂一点
final userProvider = StateNotifierProvider<UserController, User>(
(ref) => UserController(User(displayName: 'unknown', age: 0)));
class UserController extends StateNotifier<User> {
UserController(super.state);
void updateName(String name) {
state = state.copyWith(displayName: name);
}
void updateAge(int age) {
state = state.copyWith(age: age);
}
}
如下图,第一个泛型类型要求是StateNotifier子类,这样数据变动会通知监听者;第二个泛型类型是状态值类型,这里我们指定类型为User
在控组件中的使用和之前的StateProvider是一样的,效果是点击后页面文字更新为“Tom”
void main() {
runApp(const ProviderScope(child: NotifierApp()));
}
class NotifierApp extends StatelessWidget {
const NotifierApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(home: _Home());
}
}
class _Home extends ConsumerWidget {
const _Home();
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(title: const Text('example')),
body: Center(
child: Consumer(builder: (context, ref, _) {
debugPrint("build Consumer");
final user = ref.watch(userProvider);
return Text(user.displayName);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(userProvider.notifier).updateName("Tom"),
child: const Icon(Icons.add),
),
);
}
}
注意update方法,使用的copy的方式去覆盖,这是dart中常用的更新方式
void updateName(String name) {
state = state.copyWith(displayName: name);
}
像下面这种方式是错误的,即使User是可变的,能这样赋值,也没有效果
void updateName(String name) {
state.displayName = name;
}
因为这样只是改了属性,没有触发state的更新,就算触发了更新,StateNotifier在通知监听者之前会比较更新前后的对象,如果是同一个也不会通知
set state(T value) {
final previousState = _state;
_state = value;
/// 看看两个对象的地址是不是同一个
if (!updateShouldNotify(previousState, value)) {
return;
}
for (final listenerEntry in _listeners) {
try {
listenerEntry.listener(value);
} catch (error, stackTrace) {
...
}
}
}
当点击按钮后,页面文字从“unkown”更新为“Tom”,但此后每次点击,页面文字不变,但仍然会打印“build Consumer”,说明Widget更新了,这是无效更新,因为内容都没变。
那内容一样为什么Widget会更新,因为updateShouldNotify仅仅比较的是对象的地址,我们每次都new个对象,地址肯定是不一样的
bool updateShouldNotify(
T old,
T current,
) =>
!identical(old, current);
如果要根据对象内容比较,就需要改造一下User,主要是复写hashCode和复写==方法:除了地址相同以外,如果两个对象的runtimeType、displayName、age一样,我们也认为对象是相同的。
这里的比较条件自由定制,但需要注意的是hashCode和==方法中的字段应该一致,要么都有age字段,要么都没有。如果有很多对象需要这样手写模版代码,考虑使用equatable,地址:pub-web.flutter-io.cn/packages/eq…
class User {
final int age;
final String displayName;
User({
required this.displayName,
required this.age,
});
User copyWith({
String? displayName,
int? age,
}) {
return User(
displayName: displayName ?? this.displayName, age: age ?? this.age);
}
@override
bool operator ==(Object other) {
// 包含了地址比较
return identical(this, other) ||
(other is User &&
runtimeType == other.runtimeType &&
(identical(displayName, other.displayName) ||
displayName == other.displayName) &&
(identical(age, other.age) || age == other.age));
}
@override
int get hashCode => Object.hash(runtimeType, displayName, age);
}
改造之后,再复写updateShouldNotify方法,使用我们自定义的比较替代
class UserController extends StateNotifier<User> {
UserController(super.state);
void updateName(String name) {
state = state.copyWith(displayName: name);
}
void updateAge(int age) {
state = state.copyWith(age: age);
}
@override
bool updateShouldNotify(User old, User current) {
// 这里返回的不是相等,而是不相等
return !(old == current);
}
}
这样再次点击后,就不会无效更新了