美文网首页Flutter
flutter在项目中的思考

flutter在项目中的思考

作者: 某非著名程序员 | 来源:发表于2021-07-03 09:43 被阅读0次

1. setState时,变量需要放在方法内部吗?

官网的计数器实例:

setState(() {
  _counter++;
});

调用setState会调用build方法刷新,在调用setState之前同步设置变量的变化即可,不一定非要放在setState中。
下面这样也是可以的:

_counter++;
setState(() {});

在实际场景中,变化的代码可能很长,嵌套在一起不是很喜欢。

2. setState子widget如何刷新

实际开发中,可能会按功能拆分成多个widget。而多个widget是以树的形式存在,StatefulWidget和StatelessWidget会被频繁创建。StatelessWidget只要保证数据源刷新,其UI一定会刷新正确。
StatefulWidget中的State是包含声明周期的,也就是说StatefulWidget会被频繁创建,但其State初始化后被插入到配置树中,除切换tab,退出页面等,正常setState操作是不会销毁的。

class Filtrate extends StatefulWidget {
  List<FiltrateConfig> configs;
  @override
  _FiltrateState createState() => _FiltrateState(this.configs);
}

class _FiltrateState extends State<Filtrate> {
  List<FiltrateConfig> configs;
  _FiltrateState(this.configs);
}

上述代码在_FiltrateState使用configs时,如果外部configs对象在初始化后,重新new一个新的对象,调用到Filtrate(configs),再setState,是不会传入到_FiltrateState的。因为_FiltrateState只会初始化一次,而configs的对象已经发生了变化,造成StatefulWidget与State中的configs不是一个对象,导致UI更新错误。
在State中,如果需要使用外部参数,可以直接调用widget.configs,不要定义两个configs。

3.状态管理

在build中有子widget,而想要更新子widget数据。
正常的iOS开发思路,会把子widget声明全局变量,然后暴露方法,在需要的时候刷新。而在flutter中,widget基本是局部变量,设置数据源,setState即可。

子widget自己管理数据源,并不是从外部传入,如何刷新。子widget改变数据,需要同步到外面怎么刷新。

InheritedWidget在flutter是一个很重要的概念:

  1. 定义数据模型,继承自InheritedWidget
class ShareDataWidget extends InheritedWidget {
  ShareDataWidget({
    @required this.data,
    Widget child
  }) :super(child: child);
    
  final int data; //需要在子树中共享的数据,保存点击次数
  
  //定义一个便捷方法,方便子树中的widget获取共享数据  
  static ShareDataWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  }

  //该回调决定当data发生变化时,是否通知子树中依赖data的Widget  
  @override
  bool updateShouldNotify(ShareDataWidget old) {
    //如果返回true,则子树中依赖(build函数中有调用)本widget
    //的子widget的`state.didChangeDependencies`会被调用
    return old.data != data;
  }
}
  1. 在需要的地方调用ShareDataWidget.of()
@override
  Widget build(BuildContext context) {
    //使用InheritedWidget中的共享数据
    return Text(ShareDataWidget
        .of(context)
        .data
        .toString());
  }
  1. 在父widget使用ShareDataWidget包裹
ShareDataWidget( //使用ShareDataWidget
        data: count,
        child: Container()
);

dependOnInheritedWidgetOfExactType:会更新子widget
getElementForInheritedWidgetOfExactType:不会更新,局部刷新原理使用此方法。

具体文章参考:
7.2 数据共享(InheritedWidget)
7.3 跨组件状态共享(Provider)

小结:
在flutter中InheritedWidget是一个非常重要的概念,理解之后再使用Provider能知道为什么框架会那么写。

4.局部刷新

@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key key;
 ...
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

系统框架对是否需要更新Widget已经做了处理,在性能上是没什么问题。但开发能做的优化,就是只刷新目标UI。

Provider

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
     // 通过 Provider 组件封装数据资源
    return ChangeNotifierProvider.value(
        value: CounterModel(),// 需要共享的数据资源
        child: MaterialApp(
          home: FirstPage(),
        )
    );
  }
}

// 第一个页面,负责读数据
class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 取出资源
    final _counter = Provider.of<CounterModel>(context);
    return Scaffold(
      // 展示资源中的数据
      body: Text('Counter: ${_counter.counter}'),
      // 跳转到 SecondPage
      floatingActionButton: FloatingActionButton(
        onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (context) => SecondPage()))
      ));
  }
}
 
// 第二个页面,负责读写数据
class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 取出资源
    final _counter = Provider.of<CounterModel>(context);
    return Scaffold(
      // 展示资源中的数据
      body: Text('Counter: ${_counter.counter}'),
      // 用资源更新方法来设置按钮点击回调
      floatingActionButton:FloatingActionButton(
          onPressed: _counter.increment,
          child: TestIcon,
     ));
  }
}

// 用于打印 build 方法执行情况的自定义控件
class TestIcon extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("TestIcon build");
    return TestIcon();// 返回 Icon 实例
  }
}

在点击按钮时,TestIcon也会被刷新。

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 使用 Consumer 来封装 counter 的读取
      body: Consumer<CounterModel>(
        //builder 函数可以直接获取到 counter 参数
        builder: (context, CounterModel counter, _) => Text('Value: ${counter.counter}')),
      // 使用 Consumer 来封装 increment 的读取 
      floatingActionButton: Consumer<CounterModel>(
        //builder 函数可以直接获取到 increment 参数
        builder: (context, CounterModel counter, child) => FloatingActionButton(
          onPressed: counter.increment,
          child: child,
        ),
        child: TestIcon(),
      ),
    );
  }
}

Consumer 中的 builder 实际上就是真正刷新 UI 的函数

参考文章:30丨为什么需要做状态管理,怎么做?

总结

  1. 我只是知识的搬运工,目前flutter已看书籍:《Flutter实战》、《Flutter核心技术与实战》。
  2. 目前工程已接入flutter,从0到1搭建了一整套环境。对flutter有了更深的理解。
  3. 转换一门新的语言,能使用只是入门水平。需要在实践的同时,不断结合理论,才能让你的代码写的更好,让项目更稳定。

相关文章

网友评论

    本文标题:flutter在项目中的思考

    本文链接:https://www.haomeiwen.com/subject/ctfvyltx.html