美文网首页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