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以及布局等等方面的东西 只是从组件,框架方面结合前端的一些东西来分享一下本身的见解 也是看成本身一个小经验总结吧,但愿能稍微给到前端开发们一点启发

相关文章
相关标签/搜索