小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
一、什么是声明式编程
我们常规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传过来了。
官方的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的状态管理就先介绍到这吧。最近项目迭代要开始了,码字时间不多了,且写且珍惜了。哈哈😄 😄 😄