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以及佈局等等方面的東西 只是從組件,框架方面結合前端的一些東西來分享一下本身的見解 也是看成本身一個小經驗總結吧,但願能稍微給到前端開發們一點啓發