Flutter ValueNotifier 组件原理剖析

在flutter的状态管理中,ValueNotifier会被经常使用,一般会和ValueListenableBuilder一起使用.ValueNotifier负责状态,ValueListenableBuilder负责嵌套组件来刷新

java 复制代码
ValueNotifier<int> type = ValueNotifier(0);

ValueListenableBuilder(
  valueListenable: type,
  builder: (context, type, child) {
    return Text(type.toString());
  },
);

这篇文章就来深度剖析一下这个组件内部的具体实现原理

首先我们从ValueNotifier入手,它的继承关系

java 复制代码
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T>

mixin class ChangeNotifier implements Listenable

abstract class ValueListenable<T> extends Listenable

Listenable

最终根源是节点是Listenable,它提供了addListener,removeListener,用来添加监听和移除监听

ChangeNotifier

再来看ChangeNotifier,这个类在我们使用Provider的时候也是会经常用到,并且很多的controller会是它的子类,比如常用的ScrollController,TabController.ChangeNotifier要实现Listenable的方法

java 复制代码
void addListener(VoidCallback listener) {
   assert(ChangeNotifier.debugAssertNotDisposed(this));
   if (kFlutterMemoryAllocationsEnabled) {
     maybeDispatchObjectCreation(this);
   }
   if (_count == _listeners.length) {
     if (_count == 0) {
       _listeners = List<VoidCallback?>.filled(1, null);
     } else {
       final List<VoidCallback?> newListeners =
           List<VoidCallback?>.filled(_listeners.length * 2, null);
       for (int i = 0; i < _count; i++) {
         newListeners[i] = _listeners[i];
       }
       _listeners = newListeners;
     }
   }
   _listeners[_count++] = listener;
}

@override
  void removeListener(VoidCallback listener) {
    for (int i = 0; i < _count; i++) {
      final VoidCallback? listenerAtIndex = _listeners[i];
      if (listenerAtIndex == listener) {
        if (_notificationCallStackDepth > 0) {
          _listeners[i] = null;
          _reentrantlyRemovedListeners++;
        } else {
          _removeAt(i);
        }
        break;
      }
    }
  }

ChangeNotifier内部提供_listeners,用来存放监听的回调,来实现上面两个函数,主要作用是来操作_listeners添加删除回调函数
ChangeNotifier特色函数notifyListeners

java 复制代码
void notifyListeners() {
    assert(ChangeNotifier.debugAssertNotDisposed(this));
    if (_count == 0) {
      return;
    }
    _notificationCallStackDepth++;

    final int end = _count;
    for (int i = 0; i < end; i++) {
      try {
        _listeners[i]?.call();
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'foundation library',
          context: ErrorDescription('while dispatching notifications for $runtimeType'),
          informationCollector: () => <DiagnosticsNode>[
            DiagnosticsProperty<ChangeNotifier>(
              'The $runtimeType sending notification was',
              this,
              style: DiagnosticsTreeStyle.errorProperty,
            ),
          ],
        ));
      }
    }

    _notificationCallStackDepth--;

    if (_notificationCallStackDepth == 0 && _reentrantlyRemovedListeners > 0) {
      final int newLength = _count - _reentrantlyRemovedListeners;
      if (newLength * 2 <= _listeners.length) {
        final List<VoidCallback?> newListeners = List<VoidCallback?>.filled(newLength, null);

        int newIndex = 0;
        for (int i = 0; i < _count; i++) {
          final VoidCallback? listener = _listeners[i];
          if (listener != null) {
            newListeners[newIndex++] = listener;
          }
        }

        _listeners = newListeners;
      } else {
        // Otherwise we put all the null references at the end.
        for (int i = 0; i < newLength; i += 1) {
          if (_listeners[i] == null) {
            // We swap this item with the next not null item.
            int swapIndex = i + 1;
            while (_listeners[swapIndex] == null) {
              swapIndex += 1;
            }
            _listeners[i] = _listeners[swapIndex];
            _listeners[swapIndex] = null;
          }
        }
      }

      _reentrantlyRemovedListeners = 0;
      _count = newLength;
    }
  }

大段代码不想看,其实可以只关注

ini 复制代码
final int end = _count;
for (int i = 0; i < end; i++) {
  _listeners[i]?.call();
}

主要是调用监听的回调函数,剩下的部分是对_listeners的优化,在执行完所有的回调函数后,才对_listeners进行长度变更,对应removeListener,在_notificationCallStackDepth > 0的时候,并没有对数组长度进行优化,而是在回调结束后,才进行的优化

比如只需要监听一次结果,然后在回调中移除了监听,这里如果正好在_listeners长度变化的节点上,需要重新开辟内存,此时可能是出于性能的优化,并不会在回调执行过程中做数组的内存变更,统一在所有回调结束后去操作,还有一种情况是我的回调函数很多,移除的也很多,可能会出现多次开辟内存的情况,而最后统一处理,最多只需要开辟一次内存,这个就属于很细节的操作了

到这里,我们结束了对ChangeNotifier剖析

ValueListenable

scala 复制代码
abstract class ValueListenable<T> extends Listenable {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const ValueListenable();

  /// The current value of the object. When the value changes, the callbacks
  /// registered with [addListener] will be invoked.
  T get value;
}

ValueListenable就是在Listenable基础上添加了get value也是一个基类

ValueNotifier

在讲解完上面的部分后,再来说ValueNotifier就相对容易些了

scala 复制代码
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
  ValueNotifier(this._value);

  @override
  T get value => _value;

  T _value;

  set value(T newValue) {
    if (_value == newValue) {
      return;
    }
    _value = newValue;
    notifyListeners();
  }

  @override
  String toString() => '${describeIdentity(this)}($value)';
}

接口类ValueListenable中的添加删除监听的实现,被ValueNotifier的父类ChangeNotifier实现了,ValueListenable增加了成员变量_value,并且实现了ValueListenableget value,重点是在set value

调用了父类notifyListeners(),用来调用监听回调函数,所以我们在最开始的例子中,直接执行type.value++,就可以直接通知到被监听对象了

ValueListenableBuilder

这个组件是Widget组件,用来包裹需要刷新的组件,是一个StatefulWidget

scala 复制代码
typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value, Widget? child);
class ValueListenableBuilder<T> extends StatefulWidget {
  final ValueListenable<T> valueListenable;
  final ValueWidgetBuilder<T> builder;
  final Widget? child;
}

valueListenable抽象成了ValueListenable,所以所有实现了ValueListenable方法的类都可以使用这个组件来进行刷新

具体看下它的 State _ValueListenableBuilderState

scss 复制代码
class _ValueListenableBuilderState<T> extends State<ValueListenableBuilder<T>> {
  late T value;

  @override
  void initState() {
    super.initState();
    value = widget.valueListenable.value;
    widget.valueListenable.addListener(_valueChanged);
  }

  @override
  void didUpdateWidget(ValueListenableBuilder<T> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.valueListenable != widget.valueListenable) {
      oldWidget.valueListenable.removeListener(_valueChanged);
      value = widget.valueListenable.value;
      widget.valueListenable.addListener(_valueChanged);
    }
  }

  @override
  void dispose() {
    widget.valueListenable.removeListener(_valueChanged);
    super.dispose();
  }

  void _valueChanged() {
    setState(() { value = widget.valueListenable.value; });
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(context, value, widget.child);
  }
}
  • initState中添加监听addListener -> _valueChanged
  • dispose中移除监听removeListener -> _valueChanged
  • _valueChanged,就是简单的setState刷新组件
  • didUpdateWidget是在widget重新构建后调用,如果监听对象变了,需要移除之前的监听,并且添加新的监听
    到这里,关于ValueNotifier的内容就结束了,但是我们试想一下,如果一个widget组件需要监听多个valueListenable,如何做到,一种方式是ValueListenableBuilder嵌套,能解决问题,但是不是很优雅
    完全可以根据上边的原理,我们实现一套多监听的组件
swift 复制代码
final ValueListenable<T> valueListenable 变为 final List<ValueListenable> valueListenables;

将之前的单个valueListenable添加回调变成数组遍历添加回调,移除亦然,关于didUpdateWidget中对数组中的每个元素作比较这个方法很多,可以选择

scss 复制代码
void initState() {
    super.initState();
    value = widget.valueListenables;
    for (var element in widget.valueListenables) {
      element.addListener(_valueChanged);
    }
}

@override
  void dispose() {
    _removeListener(widget);
    super.dispose();
  }

  _removeListener(ValueListenableListBuilder widget) {
    for (var element in widget.valueListenables) {
      element.removeListener(_valueChanged);
    }
  }

 @override
  void didUpdateWidget(ValueListenableListBuilder<T> oldWidget) {
    if (!const DeepCollectionEquality()
        .equals(oldWidget.valueListenables, widget.valueListenables)) {
      _removeListener(oldWidget);
      value = widget.valueListenables;
      _removeListener(widget);
    }
    super.didUpdateWidget(oldWidget);
  }

  void _valueChanged() {
    setState(() {
      value = List.of(widget.valueListenables);
    });
  }

  @override
  Widget build(BuildContext context) {
    final result = value.map((e) => e.value).toList();
    return widget.builder(context, result, widget.child);
  }

再进一步优化,上边返回的result是List,元素没有具体的类型,在使用的时候会有一些不便利,所以我们可以再上层,添加一层嵌套,一两个为例,创建一个类来接收这两个ValueListenable

csharp 复制代码
class Tuple<T1, T2> {
  T1 value1;
  T2 value2;

  Tuple(this.value1, this.value2);

  List<R> toList<R>() => List.from([value1, value2]);

  factory Tuple.fromList(List list) {
    return Tuple(list[0], list[1]);
  }
}
scss 复制代码
typedef ValueTupleWidgetBuilder<T1, T2> = Widget Function(
  BuildContext context,
  Tuple<T1, T2> value,
  Widget? child,
);
scala 复制代码
class ValueListenableList2Builder<T1, T2> extends ValueListenableListBuilder {
  ValueListenableList2Builder({
    super.key,
    required Tuple<ValueListenable<T1>, ValueListenable<T2>> valueListenables,
    required ValueTupleWidgetBuilder<T1, T2> builder,
    super.child,
  }) : super(
          valueListenables: valueListenables.toList(),
          builder: (context, value, child) => builder(
            context,
            Tuple.fromList(value),
            child,
          ),
        );
}

这样一来,我们在使用Tuple的时候就不用再考虑类型问题了

相关推荐
玲珑Felone25 分钟前
从flutter源码看其渲染机制
android·flutter
ALLIN21 小时前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei21 小时前
Flutter 国际化
flutter
Dabei21 小时前
Flutter MQTT 通信文档
flutter
Dabei1 天前
Flutter 中实现 TCP 通信
flutter
孤鸿玉1 天前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter
前端 贾公子1 天前
《Vuejs设计与实现》第 16 章(解析器) 上
vue.js·flutter·ios
tangweiguo030519871 天前
Flutter 数据存储的四种核心方式 · 从 SharedPreferences 到 SQLite:Flutter 数据持久化终极整理
flutter
0wioiw01 天前
Flutter基础(②④事件回调与交互处理)
flutter
肥肥呀呀呀1 天前
flutter配置Android gradle kts 8.0 的打包名称
android·flutter