深色模式
Flutter Hooks
介绍
把 React hooks 搬到 Flutter 中,也就是 Flutter Hooks。
使用 Hooks,可以减少很多重复的样板代码。
安装
执行:
sh
flutter pub add flutter_hooks或者,在pubspec.yaml中:
yaml
dependencies:
...
flutter_hooks: ^0.18.5+1使用 Hooks 的规则
hook底层是根据hook数组的index来维护的,所以使用hook时,要遵守一些规则:
继承
HookWidget使用hook时,组件要继承
HookWidget,而不是StatelessWidget或StatefulWidget。当使用
hooks_riverpod库时,则组件可以继承HookConsumerWidget或StatefulHookConsumerWidget,因为这2个类,都继承HookWidget。hook函数都以
use开头hook函数不可以在条件语句中使用
可以:
dartWidget build(BuildContext context) { useMyHook(); // .... }不可以:
dartWidget build(BuildContext context) { if (condition) { useMyHook(); } // .... }hook 在 hot-reload 时的行为
假设初始状态为:
dartuseA(); useB(0); useC();代码改为:
dartuseA(); useB(42); useC();HookA、HookC保持状态,HookB会热重载。
代码改为:
dartuseA(); useC();HookA保持状态,HookC重新加载。
可以自定义hook
有利于抽离逻辑,关注点分离。
比如:录音、播放,需要处理初始化、配置、状态判断、开始、暂停、停止等控制,可以抽取为单独的hook。
例,自定义一个hook名为
useAudioPlayer,然后使用它:dart@override Widget build(BuildContext context, WidgetRef ref) { final ( playStatus, playDuration, startPlay, pausePlay, resumePlay, stopPlay, ) = useAudioPlayer(filePath); }dartenum AudioStatus { idle, playing, paused, error, } /// 抽离播放逻辑 /// 返回播放状态、播放进度、播放控制方法 ( ValueNotifier<AudioStatus> status, ValueNotifier<int> duration, Future<void> Function() start, Future<void> Function() pause, Future<void> Function() resume, Future<void> Function() stop, ) useAudioPlayer(String recordPath) { final duration = useState(0); final status = useState(AudioStatus.idle); final player = useMemoized(() => AudioPlayer()); useEffect(() { // 监听播放完成 final completeSubscription = player.onPlayerComplete.listen((_) { status.value = AudioStatus.idle; duration.value = 0; }); // 监听播放进度 final positionSubscription = player.onPositionChanged.listen((position) { duration.value = position.inSeconds; }); return () { completeSubscription.cancel(); positionSubscription.cancel(); player.dispose(); }; }, []); Future<void> start() async { if (recordPath.isEmpty) { status.value = AudioStatus.error; return; } try { await player.play(DeviceFileSource(recordPath)); status.value = AudioStatus.playing; } catch (e) { status.value = AudioStatus.error; } } pause() async { await player.pause(); status.value = AudioStatus.paused; } resume() async { await player.resume(); status.value = AudioStatus.playing; } Future<void> stop() async { await player.stop(); status.value = AudioStatus.idle; duration.value = 0; } return ( status, duration, start, pause, resume, stop, ); }
Hooks API
useEffect
此函数是hook的核心,这里重点记录一下。
函数定义:
dart
void useEffect(Dispose? Function() effect, [List<Object?>? keys]) {
use(_EffectHook(effect, keys));
}
typedef Dispose = void Function();函数解释:
useEffect: hook函数名effect: 副作用函数Dispose?: 清理函数,它是effect的返回值,是一个函数类型,可空,它的作用是清除副作用。keys: 依赖数组
规则:
- 如果
keys为空,useEffect在每次build时,都会同步调用。 - 如果
keys不为空,useEffect只在首次build时,以及keys数组中任意值发生变化时调用。 effect被useEffect同步调用。effect可以返回一个清理函数,此函数在2种情况下被调用effect被再次调用时- 组件dispose
注意:
effect不可以是async函数dart// 错误用法 useEffect(() async { return null; }, []);清理函数调用时机
当组件dispose时,清理函数会被调用,这没有争议。
当
effect再次触发时,清理函数会被调用,它的作用是清除上一次effect的副作用,但是从时序上来看,清理函数会在下次effect之后才调用,而不是在它之前调用。所以,不能依赖时序来保证清理逻辑的正确。
清理函数是一个闭包,它会捕获外部的变量,在使用的时候,尽量不要捕获
effect函数外层的变量,否则,由于它的调用时序,容易逻辑出问题,比如:刚刚订阅一个流,然后马上就把它释放了。
useState
dart
ValueNotifier<T> useState<T>(T initialData) {
return use(_StateHook(initialData: initialData));
}initialData:状态的初始值- 返回一个
ValueNotifier对象,每当ValueNotifier.value发生变化,会将所属的Widget标记为需要重要重新build。 Widget每次build时,useState()返回的ValueNotifier对象,是不同的对象哦,但是ValueNotifier.value的值会保持。
useMemoized
dart
T useMemoized<T>(
T Function() valueBuilder, [
List<Object?> keys = const <Object>[],
]) {
return use(
_MemoizedHook(
valueBuilder,
keys: keys,
),
);
}- 立即执行
valueBuilder,返回一个值 - 在组件rebuild时,
valueBuilder不会重复执行,而是返回上次缓存的值 - 如果
keys发生变化,在rebuild时,valueBuilder才会重复执行
当组件销毁时,缓存值不再有效。
useRef
dart
ObjectRef<T> useRef<T>(T initialValue) {
return useMemoized(() => ObjectRef<T>(initialValue));
}
class ObjectRef<T> {
ObjectRef(this.value);
T value;
}返回一个值的包装引用,值改变时,不会触发rebuild。这一点与useState相对。
useCallback
dart
T useCallback<T extends Function>(
T callback, [
List<Object?> keys = const <Object>[],
]) {
return useMemoized(() => callback, keys);
}- 缓存一个函数,它是
useMemoized的语法糖
代码对比:
dart
final cachedFunction = useCallback(() {
print('doSomething');
}, [key]);dart
final cachedFunction = useMemoized(() => () {
print('doSomething');
}, [key]);useContext
dart
BuildContext useContext() {
assert(
HookElement._currentHookElement != null,
'`useContext` can only be called from the build method of HookWidget',
);
return HookElement._currentHookElement!;
}- 返回当前
HookWidget的BuildContext
useValueChanged
dart
R? useValueChanged<T, R>(
T value,
R? Function(T oldValue, R? oldResult) valueChange,
) {
return use(_ValueChangedHook(value, valueChange));
}- 观察一个值,每当值改变时,触发回调。
valueChange函数不会在定义时立即调用valueChange函数可以返回一个值useValueChanged勾子的返回值,是最近一次valueChange的返回值。
useReducer
(这个hook没什么卵用。)
dart
Store<State, Action> useReducer<State, Action>(
Reducer<State, Action> reducer, {
required State initialState,
required Action initialAction,
}) {
return use(
_ReducerHook(
reducer,
initialAction: initialAction,
initialState: initialState,
),
);
}示例:
dart
// 定义状态
class CounterState {
final int count;
CounterState(this.count);
}
// 定义动作
enum CounterAction { increment, decrement }dart
CounterState counterReducer(CounterState state, CounterAction action) {
switch (action) {
case CounterAction.increment:
return CounterState(state.count + 1);
case CounterAction.decrement:
return CounterState(state.count - 1);
default:
return state;
}
}dart
class CounterWidget extends HookWidget {
@override
Widget build(BuildContext context) {
final counter = useReducer(counterReducer, initialState: CounterState(0));
return Column(
children: <Widget>[
Text('Count: ${counter.state.count}'),
ElevatedButton(
onPressed: () => counter.dispatch(CounterAction.increment),
child: Text('Increment'),
),
ElevatedButton(
onPressed: () => counter.dispatch(CounterAction.decrement),
child: Text('Decrement'),
),
],
);
}
}