Flutter开发实战-零基础入门之旅3(状态管理)

267 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

本文同时参与 「掘力星计划」   ,赢取创作大礼包,挑战创作激励金

一、什么是声明式编程

我们常规App的写法一般都是命令式编程,比如一个按钮点击之后,文字改变,我们常规做法是,点击按钮之后,我们要拿到那个按钮,然后重新设置它的文字。但是Flutter是声明式的,我们在按钮上面需要绑定一套数据,视图的改变由数据驱动,当我们需要改变文字的时候,直接改变数据即可,数据一改变,视图即改变。

总结来说,就是数据驱动,一切视图的改变都是数据的改变,所有的行为改变的都是数据,改变不了视图,这个逻辑前端同学如果写过Redux的理解起来应该很容易,或者iOS同学写过Reswift的,理解起来也相对不难,但是框架仅仅起到约束作用,很难完全做到数据驱动,在Flutter里面则只能这样写。

二、Flutter如何更新状态

Flutter里面有两套Widget,一套叫StatelessWidget(无状态组件),一套叫StatefulWidget(有状态组件),StatefulWidget可以通过setState更新数据,从而更新视图,但StatelessWidget不可以,所以设计视图的时候要考虑清楚,视图需不需要可变。当然只需要设置需要可变的最小组件即可,不需要从父视图就设置stateful,滥用Stateful会影响到Flutter的渲染性能,因为视图的重新构建会从最外层的StatefulWidget开始。


    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
    

比如上面这段代码,我们只刷新了Text,但如果我们打个断点到appBar这里,就会发现每次刷新视图的时候,appBar也会跟着一起刷新,所以能用Stateless就用Stateless。

1、更新数据以更新视图

还是上面那个例子,Text绑定了counter,我们只需要改变counter变量,Text就会更新,但是counter变量的更新一定要放在setState方法里,Flutter里面最简单的状态改变就完成了。

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

2、跨组件传递数据-InheritedWidget

InheritedWidget是Flutter官方原生支持的,后面要讲的Provider也是基于这个思想实现的,原生写起来虽然有些麻烦,但了解它的思想很重要。

首先我们需要创建一个继承自InheritedWidget的Widget组件,child是required的,相当于里层包裹的才是真正搭建的页面,count是我定义的需要跨页面传递的数据。

class InheritedWidgetPage extends InheritedWidget {
  int count = 0;
  InheritedWidgetPage({Key? key, required Widget child, required this.count})
      : super(key: key, child: child);

  @override
  bool updateShouldNotify(covariant InheritedWidgetPage oldWidget) {
    return true;
  }
}

然后我们还要写一个它的child,就是真正展示的页面,页面需要用到跨页面传递的参数count。

class CounterDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var provider = context.dependOnInheritedWidgetOfExactType<InheritedWidgetPage>();
    return Scaffold(appBar: AppBar(title: Text('InheritedWidgetPage'),), body: Text('count is ${provider!.count}'),);
  }
}

push的时候其实是push InheritedWidget,它的child用到了InheritedWidget的某一个属性而已。

Navigator.push(context,
                    MaterialPageRoute(builder: (context) => InheritedWidgetPage(count: 1, child: CounterDemo(),)));

可以看到,我们把count传过来了。

Snip20211019_1.png

官方的InheritedWidget实现跨组件传递数据简单版就是这样了。只要是基于InheritedWidget创建的子组件就可以共享它的状态,简单来讲就是用父组件来控制子组件的数据。

3、Provider

但其实,我着重想讲的是Provider,也是官方推荐的状态共享的库,可以很简便的实现跨组件数据共享。

首先我们创建一个数据模型,基于ChangeNotifier

class Count with ChangeNotifier {
  int count = 0;

  void addCount() {
    count++;
    notifyListeners();
  }

  int getCount() {
    return count;
  }
}

在我们需要共享数据的最外层加上ChangeNotifierProvider,类似于父组件,由它来控制数据状态,这里我就写在最外层了。

  runApp(ChangeNotifierProvider(
    create: (context) => Count(),
    child: MyApp(),
  ));

使用的时候用Consumer,数据单向传递就是下面这样简单的写法。

class CountPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text('状态同步Demo页'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            GestureDetector(
              onTap: () {
                Navigator.push(context,
                    MaterialPageRoute(builder: (context) => listSepDemo()));
              },
              child: Text(
                'You have pushed the button this many times:',
              ),
            ),
            Consumer<Count>(
                builder: (context, value, child) => Text(
                      '${value.count}',
                      style: Theme.of(context).textTheme.headline4,
                    )),
          ],
        ),
      ),
      floatingActionButton: Consumer<Count>(
          builder: (context, value, child) => FloatingActionButton(
                onPressed: () {
                  value.addCount();
                },
                tooltip: 'Increment',
                child: Icon(Icons.add),
              )), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

然后我们下一个页面继续改变状态,并且同步改变上一个页面。大家共同用Consumer接收数据即可。

Widget listSepDemo() {
  return Scaffold(
    appBar: AppBar(
      title: Text('Test'),
    ),
    body: Consumer<Count>(
        builder: (context, value, child) => Text('${value.count}')),
    floatingActionButton: Consumer<Count>(
      builder: (context, value, child) => FloatingActionButton(
        onPressed: () {
          value.addCount();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    ),
  );
}

这里我用Provider的Consumer,它的刷新机制是只刷新Consumer下面的组件,所以推荐使用Consumer。

三、结语

Flutter的状态管理还有其他方法,这里就不一一列举了,后面写项目最常用的就是Provider了,当然provider也不是仅仅是上面介绍的那一点点,还是那句话,入门已经够了,等真正使用到的时候再去查文档也不迟。

Flutter的状态管理就先介绍到这吧。最近项目迭代要开始了,码字时间不多了,且写且珍惜了。哈哈😄 😄 😄