flutter是一个高效跨平台的ui框架
相较react native 和 weex ,它不须要转化成原生控件
也不须要 jscore 做为中间层进行桥接
它能够经过skia引擎直接进行ui渲染
可谓优势颇多前端
那本文呢,是以一个前端开发工程师视角来谈谈flutter
以及响应式框架的一些东西~vue
这是一个mvvm框架的基本结构
基本上,mvvm框架在一切须要渲染UI的场景中,都能发挥强大的做用
由于本质上它就是彻底解耦了数据与视图react
再来回望下前端mvc/p框架,这是一些很纯粹的数据/视图分离框架
它模块化程度很高,可是没有解决 数据与视图之间同步 的问题
或者认为这个同步的事情应该交给平台(浏览器)去实现
直到前端工程化开始流行起来以后,各类mvvm框架也就应运而生
他们大体的原理都以下:git
flutter中是如此:github
好,回到flutter来,这是flutter中的hello worldweb
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
那么它是一个组件吗? 是的。api
再回到如今,咱们也会写各式各样的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中,尤为强调这点
再来看一个简单的 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中都有明显的优点
其二更重要的是咱们要养成审视本身代码的习惯,不少组件其实不要必定须要状态管理
再来看看这个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是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中能实现呢
这里混合在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复用
(注意若是有相同的属性会报错)
至于高阶组件这里就有点遗憾了
咱们知道flutter中使用的dart是修改过的,移除了反射
因此咱们动态建立组件是没法实现的,高阶组件暂时也是实现不了的
可是若是抛开动态建立这个条件,咱们使用Builder也能作到一部分逻辑的复用
和HOC同样,它也有一些缺陷:
1.并列关系的状态逻辑被组合成了父子关系
2.多层嵌套会十分难以阅读
若是说上面两种方式都有些许不爽的话,那么你能够尝试下使用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以及布局等等方面的东西 只是从组件,框架方面结合前端的一些东西来分享一下本身的见解 也是看成本身一个小经验总结吧,但愿能稍微给到前端开发们一点启发