InheritedWidget的正确用法

480 阅读2分钟

概述

Provider 和 Scoped Model 状态管理框架都是基于 InheritedWidget 的,主要是实现共享数据 以及 实现局部更新(这个一般是ChangeNotifier的功能),比如系统中的 Theme.of(context) 以及 MediaQuery.of(context)都是利用 InheritedWidget 实现的。

原理以及用途

在 Flutter 中子widget无法单独感知父widget的变化,但是InheritedWidget可以让子组件获取父组件的数据。

InheritedWidget 和 ValueNotifier(ChangeNotifier 也可以) 组合实现 局部更新

代码里面有个Logger-github 或者 Logger-pub 库,是实现在Flutter中有级别打印以及不同的颜色

1625751834730.jpg

创建一个可共享的 User 类

class User {
  String? name;
  int? age;
  User({this.name, this.age});
}

创建一个 DemoInheritedWidget

继承于 InheritedWidget,并且里面的一个变量 ValueNotifier ,泛型是User,同时提供了 更改User 以及 只更改name的方法。注意of(context)方法内,是 getElementForInheritedWidgetOfExactType 不是 dependOnInheritedWidgetOfExactType

class DemoInheritedWidget extends InheritedWidget {
  late ValueNotifier<User> userNotifier;

  DemoInheritedWidget(User user, {Key? key, required Widget child})
      : super(key: key, child: child) {
    userNotifier = ValueNotifier<User>(user);
  }

  /// 主要是用来获取 DemoInheritedWidget
  static DemoInheritedWidget of(BuildContext context) {
    // 不要用 dependOnInheritedWidgetOfExactType
    InheritedElement element =
        context.getElementForInheritedWidgetOfExactType<DemoInheritedWidget>()!;
    return element.widget as DemoInheritedWidget;
  }

  void updateUser(User user) {
    userNotifier.value = user;
  }
  /// 实际上  ValueNotifier 就是对 ChangeNotifier 的一个简单封装
  void updateUserName(String name) {
    userNotifier.value.name = name;
    userNotifier.notifyListeners();
  }

  @override
  bool updateShouldNotify(covariant DemoInheritedWidget oldWidget) {
    return false;
  }
}

写一个页面 使用 上面的DemoInheritedWidget,

我们根据 DemoInheritedWidget.of(context).updateUser(User(name: "改变后张三1", age: 19)); 去更新User ,使用 DemoInheritedWidget.of(context).updateUserName("李四") 更新名字

class InheritedWidgetDemoPage extends StatefulWidget {
  InheritedWidgetDemoPage({Key? key}) : super(key: key) {
    Logger.e("Page 初始化");
  }

  @override
  _InheritedWidgetDemoPageState createState() =>
      _InheritedWidgetDemoPageState();
}

class _InheritedWidgetDemoPageState extends State<InheritedWidgetDemoPage> {
  @override
  Widget build(BuildContext context) {
    Logger.e("_PageState: build");
    return DemoInheritedWidget(User(name: "张三", age: 18), child: Builder(
      builder: (context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text("InheritedWidgetDemo"),
          ),
          body: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Center(
                child: WidgetA(
                  child: WidgetB(),
                ),
              ),
              MaterialButton(
                color: Colors.blue,
                onPressed: () {
                  DemoInheritedWidget.of(context)
                      .updateUser(User(name: "改变后张三1", age: 19));
                },
                child: const Text(
                  "改变User",
                  style: TextStyle(color: Colors.white),
                ),
              ),
              MaterialButton(
                color: Colors.blue,
                onPressed: () {
                  DemoInheritedWidget.of(context).updateUserName("李四");
                },
                child: const Text(
                  "只改变name",
                  style: TextStyle(color: Colors.white),
                ),
              ),
            ],
          ),
        );
      },
    ));
  }

  @override
  void dispose() {
    Logger.e("_PageState: dispose");
    super.dispose();
  }
}

widgetA 和 WidgetB

因为 WidgetB 需要用到User对象的数据,所以 widgetB 用 ValueListenableBuilder作为child,当 User发生变化的时候 只会 widgetB 会 rebuild,其他的不会走

class WidgetA extends StatefulWidget {
  final Widget child;

  const WidgetA({Key? key, required this.child}) : super(key: key);

  @override
  _WidgetAState createState() => _WidgetAState();
}

class _WidgetAState extends State<WidgetA> {
  @override
  void initState() {
    super.initState();
    Logger.e('WidgetA initState');
  }

  @override
  Widget build(BuildContext context) {
    Logger.e('WidgetA build');
    return Center(
      child: widget.child,
    );
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    Logger.e('WidgetA didChangeDependencies');
  }

  @override
  void dispose() {
    super.dispose();
    Logger.e('WidgetA dispose');
  }
}

class User {
  String? name;
  int? age;

  User({this.name, this.age});
}

class DemoInheritedWidget extends InheritedWidget {
  late ValueNotifier<User> userNotifier;

  DemoInheritedWidget(User user, {Key? key, required Widget child})
      : super(key: key, child: child) {
    userNotifier = ValueNotifier<User>(user);
  }

  /// 主要是用来获取 DemoInheritedWidget
  static DemoInheritedWidget of(BuildContext context) {
    // 不要用 dependOnInheritedWidgetOfExactType
    InheritedElement element =
        context.getElementForInheritedWidgetOfExactType<DemoInheritedWidget>()!;
    return element.widget as DemoInheritedWidget;
  }

  void updateUser(User user) {
    userNotifier.value = user;
  }

  void updateUserName(String name) {
    userNotifier.value.name = name;
    userNotifier.notifyListeners();
  }

  @override
  bool updateShouldNotify(covariant DemoInheritedWidget oldWidget) {
    return false;
  }
}

class WidgetB extends StatefulWidget {
  @override
  _WidgetBState createState() => _WidgetBState();
}

class _WidgetBState extends State<WidgetB> {
  @override
  void initState() {
    super.initState();
    Logger.e('WidgetB initState');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    Logger.e('WidgetB didChangeDependencies');
  }

  @override
  Widget build(BuildContext context) {
    Logger.e('WidgetB build');
    return ValueListenableBuilder<User>(
      builder: (BuildContext context, user, Widget? child) {
        Logger.e("ValueListenableBuilder");
        return RichText(
          text: TextSpan(children: [
            const TextSpan(text: "名字是:", style: TextStyle(color: Colors.red)),
            TextSpan(text: user.name, style: const TextStyle(color: Colors.black)),
            const TextSpan(text: "      年龄是:", style: TextStyle(color: Colors.red)),
            TextSpan(text: "${user.age}", style: const TextStyle(color: Colors.black)),
          ]),
        );
      },
      valueListenable: DemoInheritedWidget.of(context).userNotifier,
    );
  }

  @override
  void dispose() {
    super.dispose();
    Logger.e('F dispose');
  }
}

结果

当我们点击更新的时候 只有 ValueListenableBuilder 去 rebuild 了,其他都没有 rebuild image.png

可以用这个实现MVVM的架构