flutter: 依赖状态的状态管理

假如有这样的一个问题:

已经存在一个model对象, model内部数据发生变化时会以回调的形式给外部通知;现在需要根据model自己不同的判断条件创建不同的widget对象;build方法类似如下代码:

  @override
  Widget build(BuildContext context) {
    if (model.condition1) {
      return Widget1();
    } else if (model.condition2) {
      return Widget2();
    } else {
      return Widget3();
    }
  }

这再简单不过了,显然得用一个StatefulWidget包裹model:

class YourWidget extends StatefulWidget {
  
  @override
  YourState createState() => YourState();
}

class YourState extends State<YourWidget> {
  YourModel model;

  @override
  void initState() {
    super.initState();
    
    model = createModel();
    model.register(() {
      setState();
    });
  }

  @override
  Widget build(BuildContext context) {
    if (model.condition1) {
      return Widget1();
    } else if (model.condition2) {
      return Widget2();
    } else {
      return Widget3();
    }
  }
}

在框架给的初始化时机创建model对象并注册回调, 一旦数据变化响应回调函数,视图层知道需要在这个时机重建视图了,于是调用setState强制让当前widget再调用一次build,在build方法中不关心model的具体状态值,这时候走相应的逻辑分支就行了, 一点毛病没有.

现在问题来了, 如果我们的model的创建依赖BuildContext应该怎么办? 也就是createModel(BuildContext context)需要传递一个BuildContext对象,于是问题就变成这样,我们需要创建管理状态的对象,但这个对象本身依赖状态才能获取! 虽然在initState方法中可通过context这个getter获取BuildContext对象,但是经常报如下的异常(framework.dart:4742):

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown building Builder(dependencies: [_InheritedTheme,
_LocalizationsScope-[GlobalKey#baa26]]):
dependOnInheritedWidgetOfExactType<$targetType>() or dependOnInheritedElement() was called
before ConditionState.initState() completed.
When an inherited widget changes, for example if the value of Theme.of() changes, its dependent
widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor
or an initState() method, then the rebuilt dependent widget will not reflect the changes in the
inherited widget.
Typically references to inherited widgets should occur in widget build() methods. Alternatively,
initialization based on inherited widgets can be placed in the didChangeDependencies method, which
is called after initState and whenever the dependencies change thereafter.

意思是不能在initState()中获取InheritedWidget, 于是有人移除了initState这样写出:

  @override
  Widget build(BuildContext context) {
    if (model = null) {
      model = createModel(context);
    }
  
    if (model.condition1) {
      return Widget1();
    } else if (model.condition2) {
      return Widget2();
    } else {
      return Widget3();
    }
  }

不是需要BuildContext吗,那就在能获取它的地方创建,而且也能够避免重复创建,香得很! 乍一看貌似解决了问题, 但有一种说不上哪里不对, 但是总会出问题的感觉.

果然如果createModel中有类似Scaffold.of(context)这种方法就很有可能造成崩溃, 这样写断然是不行的.

提到Scaffold.of导致的崩溃立马就会想到用Builder来避免这个问题, 但具体在我们当前情形下要如何利用Builder呢, 还有这个问题的本质到底是什么呢?Builder只是延迟创建了一个widget对象, 而我们现在需要创建的model,它可是我们状态管理对象或者数据对象,可不应当是widget哦, 难道要把一个widget节点本身再传递给子孙节点吗?

我们这时就可以看一下源码了, 其实这些of函数返回的都是状态对象(或叫作数据对象), Scaffold.of返回的是ScaffoldState, Theme.of返回的是ThemeData, 并不是widget对象, 这就有意思了, 相当于把Scaffold控件和Scaffold数据作了区分!很多人把widget当作配置数据, 在多数情况下这么认知没问题, 但其实flutter框架并没有把widget当作配置数据本身, 这种区分在需要widget树节点之间传递数据的时候就非常明显了, 这种情况下需要把状态对象或者数据对象单独拎出来, 而我们当前这个例子正是这种情况!

怎么拎出来呢? 这就需要了解一下崩溃的原因了. 我们知道BuildContext就是Element,根据widget树依次把element创建(createElement)并挂载(mount)的, 在一般的Build(BuildContext)方法中直接用参数buildContext查找祖先节点(用类似context.findAncestorStateOfType<T>), 相当于子节点Widget对象已经创建好,但子节点的Element对象还没有创建,所以用旧的祖先的去上溯是找不到的,如下:

Widget build(BuildContext context) {
  return Scaffold( //生成Scaffold控件的时候Scaffold对应的Element还没有创建
    body: Center(
      child: TextButton(
        onPressed: () {
          // Fails because Scaffold.of() doesn't find anything
          // above this widget's context.
          print(Scaffold.of(context).hasAppBar); // 查找Scaffold用的context是Scaffold的祖先节点
        },
        child: Text('hasAppBar'),
      )
    ),
  );
}

这时候 Builder的意义就出来了,它相当于在指定widget对应的Element也创建好了再去通知创建子节点. 子节点中需要获取model对象,就是在这个时候才去创建, 也就是说不是在创建当前视图之时而是之前就先创建状态对象或数据对象, 即需要把创建状态对象(数据对象)的时机前置. 一般情况下我们创建视图对象时习惯了懒加载,延迟加载, 现在需要前置加载一下有时会有点懵, 还是直接上代码:

return Builder(
  builder: (BuildContext context) => YourWidget(createModel(context));
);

class YourWidget extends StatefulWidget {
  final YourModel model;

  const YourWidget(this.model);
  
  @override
  YourState createState() => YourState();
}

class YourState extends State<YourWidget> {

  @override
  void initState() {
    super.initState();
    
    widget.model.register(() {
      setState((){});
    });
  }

  @override
  Widget build(BuildContext context) {
    final model = widget.model;
    if (model.condition1) {
      return Widget1();
    } else if (model.condition2) {
      return Widget2();
    } else {
      return Widget3();
    }
  }
}

model相对于当前视图(YourWidget)创建之前先创建, 这么显而易见的实现需要啰嗦这么一大坨吗? 其实它不是那么的显而易见; 比如Model对象的创建操作本身也是需要依赖其它的状态或者响应生命周期变化呢? 这时候就用到上面说的状态控件和状态数据的分离了, 那就需要先定义一个包裹Model的WidgetModelWidget, 再把其创建的时机更加提前:

class ModelWidget {
  // 创建model实例并响应
  // 各种状态及生命周期变化
  YourModel model;
  ...

  static YourModel of(BuildContext context) {
    final widget = context.findAncestorStateOfType<ModelWidget>;
    return widget.model;
  }
}

return ModelWidget(
  //各种数据
  ...,
  child:
    // 各种层级
    child: Builder(
      builder: (BuildContext context) => YourWidget(ModelWidget.of(context));
    )
);

class YourWidget extends StatefulWidget {
...
}
原文地址:https://www.cnblogs.com/lindeer/p/14685171.html