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属性的使用和内部流程
如果你的Provider里面的状态值是一个复杂对象,里面一个字段的变动都可能引起页面重建,而你关心的那个属性并没有变化,这样页面就无效重建了,这个时候就得select方法上场了
select方法是Provider基类的一个方法,之前说过的各种Provider都能用上,还是以StateNotifierProvider为例演示一下用法
Demo
User对象还是用之前的,如果字段也有对象,同样需要复写hashCode和==方法
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);
@override
String toString() {
return 'User{age: $age, displayName: $displayName}';
}
}
关于provider的使用,改动不大,主要是select方法的使用,它提供一个函数将对象的某个属性提取出来,这个函数和map的作用差不多
ref.watch(userProvider.select((value) => value.displayName))
final userProvider = StateNotifierProvider<UserController, User>(
(ref) => UserController(User(displayName: 'unknown', age: 0)));
class UserController extends StateNotifier<User> {
UserController(super.state);
void update({String? name, int? age}) {
state = state.copyWith(displayName: name, age: age);
}
@override
bool updateShouldNotify(User old, User current) {
// 这里返回的不是相等,而是不相等
debugPrint("old=$old current=$current");
return !(old == current);
}
}
class SelectApp extends StatelessWidget {
const SelectApp({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 name =
ref.watch(userProvider.select((value) => value.displayName));
return Text("name is $name");
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref
.read(userProvider.notifier)
.update(name: "Tom", age: random.nextInt(50)), 名字没有变,年龄使用随机数
child: const Icon(Icons.add),
),
);
}
}
Random random = Random();
当不停的点击按钮,updateShouldNotify方法打印User前后的信息,名字没变,年龄一直在变动,说明这个对象是一直变动的,按之前的写法,Consumer会重建,但使用select后,我们关心的名字除了第一次以后都没有变动,Consumer就不会重建。那select方法是如何实现的呢?
select内部流程
select方法就是将Provider和selector函数包装成了_ProviderSelector对象,这样在watch、listen的时候走的就是另一个分支了
AlwaysAliveProviderListenable<Selected> select<Selected>(
Selected Function(State value) selector,
) {
return _AlwaysAliveProviderSelector<State, Selected>(
provider: this,
selector: selector,
);
}
}
class _AlwaysAliveProviderSelector<Input, Output> = _ProviderSelector<Input,
Output> with AlwaysAliveProviderListenable<Output>;
在之前的流程分析中,为了简便,一直都是直接忽略了这个分支,下面是WidgetRef类型的,watch和listen走的都是这个。Ref类型的有所不同,最后走的是_elementListen,但殊途同归。
ProviderSubscription<State> listen<State>(
ProviderListenable<State> provider,
void Function(State? previous, State next) listener, {
bool fireImmediately = false,
void Function(Object error, StackTrace stackTrace)? onError,
}) {
如果provider是selector类型,走这个分支
if (provider is _ProviderSelector<Object?, State>) {
return provider.listen(
this,
listener,
fireImmediately: fireImmediately,
onError: onError,
);
}
...
}
在_ProviderSelector的listen方法中,有两个重点,一是_select方法
var lastSelectedValue = _select(selectedElement.getState()!);
它将Provider中的状态值按我们提供的selector函数进行转换,比如案例中将User转换成String
Result<Output> _select(Result<Input> value) {
try {
return value.map(
data: (data) => Result.data(selector(data.state)),
error: (error) => Result.error(error.error, error.stackTrace),
);
} catch (err, stack) {
// TODO test
return Result.error(err, stack);
} finally {
...
}
}
还有一个就是_selectOnChange函数,用来比较_select方法处理后的前后值,判断是否需要更新,比如name前后没变化就不会往下走,注意这里使用的是!=,比较的对象需要复写hashCode和==,否则默认比较的是地址,很可能不相等(看const修饰符)。
为啥是很可能,比如我们的User,如果没有复写hashCode和==,那么const User(tom,10)和另一个const User(tom,10)是相等的,但const User(tom,10)和另一个User(tom,10)就实会相等。
void _selectOnChange({
required Input newState,
required Result<Output> lastSelectedValue,
required void Function(Object error, StackTrace stackTrace) onError,
required void Function(Output? prev, Output next) listener,
required void Function(Result<Output> newState) onChange,
}) {
final newSelectedValue = _select(Result.data(newState));
判断是否需要通知更新
if (!lastSelectedValue.hasState ||
!newSelectedValue.hasState ||
lastSelectedValue.requireState != newSelectedValue.requireState) {
onChange(newSelectedValue);
newSelectedValue.map(
data: (data) {
listener(
lastSelectedValue.stateOrNull,
data.state,
);
},
error: (error) => onError(error.error, error.stackTrace),
);
}
}
其他流程和之前还是类似的,不再赘述。