flutter是一个高效跨平台的ui框架
相较react native 和 weex ,它不需要转化成原生控件
也不需要 jscore 作为中间层进行桥接
它可以通过skia引擎直接进行ui渲染
可谓优点颇多
那本文呢,是以一个前端开发工程师视角来谈谈flutter
以及响应式框架的一些东西~
响应式框架
这是一个mvvm框架的基本结构
基本上,mvvm框架在一切需要渲染UI的场景中,都能发挥强大的作用
因为本质上它就是完全解耦了数据与视图
mvc/p
再来回望下前端mvc/p框架,这是一些很纯粹的数据/视图分离框架
它模块化程度很高,但是没有解决 数据与视图之间同步 的问题
或者认为这个同步的事情应该交给平台(浏览器)去实现
直到前端工程化开始流行起来之后,各种mvvm框架也就应运而生
他们大致的原理都如下:
flutter中是如此:
组件的本质
好,回到flutter来,这是flutter中的hello world
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Welcome to Flutter',
home: new Scaffold(
appBar: new AppBar(
title: new Text('Welcome to Flutter'),
),
body: new Center(
child: new Text('Hello World'),
),
),
);
}
}
flutter中大多数东西都是widget,简单点说,可以理解为前端的组件
它既可以单独存在,也可以父子组件方式嵌套存在
那么大家有没有想过,组件(Component/Widget)的本质到底是什么?
时间拉回jQuery年代,那时候非常流行jQuery插件,
比如什么日期插件,分页插件等等
每一个插件都能输出实现特定的功能的DOM
那么它是一个组件吗? 是的。
再回到现在,我们也会写各式各样的Component/Widget去实现各种功能
这也是一个个组件
通俗点讲,组件就是 "生成可复用的UI的函数"
只是他们的输出不同
一个输出真的DOM,一个输出虚拟VNode
在MVVM中,VIEWMODEL 能做到自动同步视图与数据,VNode功不可没
它能保证视图的最小化变更,也能让框架拥有跨平台等等能力
它始终贯穿组件的整个生命周期
跨平台
基于VNode,很多MVVM框架都拥有了跨平台的能力,
因为它们只需要将VNode生成为对应平台的代码就行了
Weex和Rn也是这么做的
但是flutter不同,它直接通过skia引擎进行ui渲染了
带来的好处就是不需要 jscore 作为中间层进行桥接
在不同平台上的UI表现差异较少,性能上也会有较大的提升
Flutter for web 则是重新实现了dart:ui库,用针对DOM和Canvas的代码替换了手机端使用的对Skia引擎的绑定。
组件的种类
在前端开发中,我们会使用到普通组件和函数式组件
普通组件拥有自己的状态,生命周期
而函数式组件则只是单纯输出指定的VNode,且自身不能更改状态
在flutter中,尤其强调这点
statelessWidget
再来看一个简单的 flutter demo
class CounterDisplay extends StatelessWidget {
CounterDisplay({this.count});
final int count;
@override
Widget build(BuildContext context) {
return new Text('Count: $count');
}
}
class CounterIncrementor extends StatelessWidget {
CounterIncrementor({this.onPressed});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return new RaisedButton(
onPressed: onPressed,
child: new Text('Increment'),
);
}
}
flutter 鼓励大家多使用无状态组件
其一当然是为了性能考虑,函数式组件在生成以及diff中都有明显的优势
其二更重要的是我们要养成审视自己代码的习惯,很多组件其实不要一定需要状态管理
stateFullWidget
再来看看这个demo
class Counter extends StatefulWidget {
@override
_CounterState createState() => new _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return new Row(children: <Widget>[
new CounterIncrementor(onPressed: _increment),
new CounterDisplay(count: _counter),
]);
}
}
眼尖的同学们发现了几个关键字:State,setState,build
有内味了,好像和我们平时写react差不多嘛
但是区别还是有的,stateFullWidget是flutter中的带状态组件
我们始终要记住Widget是临时对象,可能被调用多次
,用于构建当前状态下的应用程序,只是一个配置
但是State对象在多次调用build()之间试保持不变的,允许它们记住状态
调用setState告诉Flutter框架,某个状态发生了变化导致应用程序重新运行build方法,以便应用程序可以反映出来更改。
build则相当于react中返回的jxs或者vue中的template(也就是render函数)
理解了这些之后,就可以朝着组件间的交互往下看了
状态管理
所有的MVVM框架组件间通信都遵循这样的逻辑:
父类通过属性传值给子类
子类通过事件传递值给父类
这是最清晰的数据流走向,我们能清晰的知道数据是怎么变更,呈现的
在flutter中当然也是如此:
class Parent extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return ParentState();
}
}
class ParentState extends State<Parent> {
String data = "无";
Sring props = "some thing";
void onChanged(val){
setState(() {
data = val;
});
}
@override
Widget build(BuildContext context) {
return new Center(
child: new Column(
children: <Widget>[
new Container(
child: new Column(
children: <Widget>[
new Child(props: props,callBack: (value)=>onChanged(value)),
new Text('from child : $data'),
],
),
),
],
),
);
}
}
但是现实中,业务永远不会这么简单,很多数据都是需要被共享的
在MVVM里就是某一个数据变更了,有关联的视图都需要重新构建
这里天然契合观察者模型
Redux,Vuex这些状态管理工具都是基于此
某种程度上说EventBus这种也是归于此,这里不多赘述
InheritedWidget
InheritedWidget是Flutter中非常重要的一个功能型组件,
它提供了一种数据在widget树中从上到下传递、共享的方式
本质上它就是添加了当前Widget作为依赖,当它的数据有变更时,就会通知所有依赖进行更新
它的使用较为繁琐,我们来看看基于inheritedWidget实现的ChangeNotifierProvider的demo
runApp(
// Provide the model to all widgets within the app. We're using
// ChangeNotifierProvider because that's a simple way to rebuild
// widgets when a model changes. We could also just use
// Provider, but then we would have to listen to Counter ourselves.
//
// Read Provider's docs to learn about all the available providers.
ChangeNotifierProvider(
// Initialize the model in the builder. That way, Provider
// can own Counter's lifecycle, making sure to call `dispose`
// when not needed anymore.
create: (context) => Counter(),
child: MyApp(),
),
);
}
class Counter with ChangeNotifier {
int value = 0;
void increment() {
value += 1;
notifyListeners();
}
}
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//使用数据
Consumer(
builder: (context, counter, child) => Text(
'${counter.value}',
style: Theme.of(context).textTheme.display1,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
//操作数据
onPressed: () =>
Provider.of(context, listen: false).increment(),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
我们使用ChangeNotifierProvider来注册被观察者,使用Consumer注册观察者
Provider.of(context)方法获得被观察者,处理数据逻辑然后通知观察者重新渲染
当然你也可以不用像上边一样这么麻烦,直接引用也能做到这点
前提是你真正了解了自己的意图,只能在特定的场景下使用
(如果你在正常组件中使用了这些,要审视一下代码,这样做会造成数据流混乱)
ParentState _p = context.findAncestorWidgetOfExactType<ParentState>().data;
_p.setState(() {
_p.data = "some thing";
});
globalState.setState(() {
globalState.name = "some thing";
});
可以通过context获得父组件
或者你直接把父组件的State赋值到一个全局变量,都能直接修改State,也能顺利更新
当然这都是不推荐的
复用性
愉快的上手之后,我们又会发现新的问题来了,
我们有可能写了很多的组件,但是其中有部分功能是可以共用的
那怎么将他们复用呢
我们很容易想到混合组件 和 高阶组件 和 hooks
那么他们在flutter中能实现呢
mixin
这里混合在flutter中是dart语言天然就支持的
mixin _dataStateMixin < T extends StatefulWidget> on State<T> {
var _data = 0;
void _incrementCounter() {
setState(() {
_data++;
});
}
}
class _CounterState extends State<CounterPage> with _dataStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Counter:',
),
Text(
'$_counter',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
我们能轻而易举的在dart中创建一个混合类
将公用的逻辑抽离成mixin复用
(注意如果有相同的属性会报错)
hoc
至于高阶组件这里就有点遗憾了
我们知道flutter中使用的dart是修改过的,移除了反射
所以我们动态创建组件是无法实现的,高阶组件暂时也是实现不了的
但是如果抛开动态创建这个条件,我们使用Builder也能做到一部分逻辑的复用
和HOC一样,它也有一些缺陷:
1.并列关系的状态逻辑被组合成了父子关系
2.多层嵌套会十分难以阅读
hooks
如果说上面两种方式都有些许不爽的话,那么你可以尝试下使用Hooks
hooks可以封装我们组件内不同生命阶段的通用逻辑,来看看官方的demo
class Example extends StatefulWidget {
final Duration duration;
const Example({Key key, @required this.duration})
: assert(duration != null),
super(key: key);
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: widget.duration);
}
@override
void didUpdateWidget(Example oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.duration != oldWidget.duration) {
_controller.duration = widget.duration;
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
}
这是一个典型的场景,每个使用AnimationController的组件都不可避免的重复写一些生命周期内的逻辑代码 而使用hooks之后,就变成这样
class Example extends HookWidget {
const Example({Key key, @required this.duration})
: assert(duration != null),
super(key: key);
final Duration duration;
@override
Widget build(BuildContext context) {
final controller = useAnimationController(duration: duration);
return Container();
}
}
会明显的感觉到比原先更细粒度的逻辑组织与复用
(我们在同一个Widget内可以使用多个hooks)
hooks的功能也不仅局限于此,它是一种新的组织方式,比如:
useState可以重新组织我们使用State的方式
useMemoized可以初始化和缓存一些东西
自定义hooks等等
可以移步这里:github.com/rrousselGit…
当然还是那句话,hooks也不是万能解药,它只应该是你组织代码逻辑中的一种方案
平时多审视,优化自己代码的逻辑方是上策
结语
今天没有讲太多关于flutter api以及布局等等方面的东西
只是从组件,框架方面结合前端的一些东西来分享一下自己的看法
也是当作自己一个小经验总结吧,希望能稍微给到前端开发们一点启发
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!