flutter開發指南 for 前端開發工程師

flutter是一個高效跨平臺的ui框架
相較react native 和 weex ,它不須要轉化成原生控件
也不須要 jscore 做爲中間層進行橋接
它能夠經過skia引擎直接進行ui渲染
可謂優勢頗多前端

那本文呢,是以一個前端開發工程師視角來談談flutter
以及響應式框架的一些東西~vue

響應式框架


這是一個mvvm框架的基本結構
基本上,mvvm框架在一切須要渲染UI的場景中,都能發揮強大的做用
由於本質上它就是徹底解耦了數據與視圖react

mvc/p


再來回望下前端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中,尤爲強調這點

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

相關文章
相關標籤/搜索