当年React Native 正火的时候,我撸了一个一席的客户端,最近抽空把我本身的项目用Flutter 写一下,项目地址戳这里,走过路过随手给个star🌟,不胜感激; 如下是做为前端对Flutter 的一些见解和经验的总结;html
我在上手写Flutter 的时候,其实一开始并无学习Dart,以为有点相似TypeScript,Dart 很好上手,只在遇到一些不熟悉的问题时才去翻阅Dart文档,说一下一些不同的概念:前端
变量声明java
vargit
在JavaScript 和Dart 中,它均可以接受任意类型,但Dart中var的变量一旦赋值,类型便会肯定,则不能再改变其类型;github
var a;
a = 'hello'; // a 已经肯定为String类型
a = 1; // 报错,类型不能更改
复制代码
dynamic & Objectjson
javaScript中没有dynamic 变量声明,与var 不一样,这两个都支持声明后改变变量类型,但Object 声明的变量只能使用Object所拥有的属性和方法,而dynamic 则支持全部属性canvas
final & const安全
从字面上能够看出这两个都是声明常量,可是const 变量是编译时常量,而final 变量则在第一次使用时初始化;bash
异步支持网络
在Javascript 和Dart中都有相同用法的async、await,但没有Promise,取而代之的是Future,但没有resolve 和reject
构造函数 在Dart 中,子类不会继承父类的命名构造函数。若是不显式提供子类的构造函数,系统就提供默认的构造函数。同时,写法也变得更简洁;
class Point {
num x;
num y;
Point(this.x, this.y);// 这句等同于
/* Point(num x, num y) { this.x = x; this.y = y; } */
}
复制代码
箭头函数
在Javascript 中,箭头函数是做为一个影响this 做用域等的存在,但在Dart 中则是做为缩写语法的存在,二者的概念是不一样的,应该区分清楚;
首先咱们来看看一样的布局,使用HTML + CSS 和Flutter 的写法区别
在Flutter 中,一切UI 都基于Widget,在上图中,Container 即是一个Widget,靠style 来设置样式(也可使用Theme,后文中细讲),子类嵌套在child 中,。
class MainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
复制代码
实际上这种写法有点相似虚拟Dom,以树形嵌套来编写,可是这种写法我的以为维护起来很要命,若是没有足够细分组件的话,可读性也会变得不好,实际上,Flutter 的issues 中也有关于类JSX 写法的讨论,对这种写法的吐槽,最近在掘金沸点上看到一张很贴切的图:
关于Widget 能够参考Flutter 中文网的Widget 目录,具体的我就不展开写了,下面讲讲一些不常见的须要注意的问题:
Expanded
不能用在不肯定或者无限高度Widget(如SingleChildScrollView
) 中
BuildContext
的概念
BuildContext
其实是当前Widget 所建立的Element对象,在获取组件尺寸,就须要用到MediaQuery.of(context).size
,路由跳转时,也要用到Navigator.of(context)
,比较详细的展开和理解说明能够参考深刻理解BuildContext 这篇文章;
Widget 的状态管理
这里要介绍一下InheritedWidget
,InheritedWidget
是一个特殊的Widget,你能够将其做为另外一个子树的父级放在Widgets树中。该子树的全部子Widget 都能与该InheritedWidget
公开的数据进行交互,从而实现了Widget 间的通讯;更多状态管理的方式能够参考 深刻探索 flutter 中的状态管理方式
在Flutter 中,样式并无抽离出来,而是以各类(混乱甚至有点怪异)组合的方式来使用,设置文本要用TextStyle
,设置边框背景等要用decoration
,感兴趣的能够看看样式的一些用法对比;
这里要吐槽一下样式的管理,在Flutter 中,可使用Theme
来共享样式,可是单个Widget 的样式除了DefaultTextStyle
设置默认文本样式外没得继承,仍是要本身一个个写,这里就推进了对组件进行细化(否则懒得重复写),主题有如下使用方式
全局主题
new MaterialApp(
title: title,
theme: new ThemeData(
brightness: Brightness.dark,
),
);
复制代码
局部主题
new Theme(
data: new ThemeData(
accentColor: Colors.yellow,
),
child: new Text('Hello World'),
);
复制代码
拓展主题
若是你不想覆盖全部的样式,能够继承App的主题,只覆盖部分样式,使用copyWith方法。
new Theme(
data: Theme.of(context).copyWith(accentColor: Colors.yellow),
child: new Text('extend theme'),
);
复制代码
获取主题
Theme.of(context)
会查找Widget 树,并返回最近的一个Theme对象。若是父层级上有Theme对象,则返回这个Theme,若是没有,就返回App的Theme。建立好主题,只要在Widget的构造方法里面经过Theme.of(context) 方法来调用。
new Container(
color: Theme.of(context).accentColor,
chile: new Text(
'Text with a background color',
style: Theme.of(context).textTheme.title,
),
);
复制代码
用过React 的都知道无状态组件和有状态组件,在Flutter中,StatelessWidget
即是无状态组件,它不依赖于除了传入的数据之外任何其余数据,意味着改变传入其构造函数的参数是改变其显示的惟一方式。而StatefulWidget
则是有状态组件,可是跟React有一点不一样,在React 中,组件的render
和state 是在一块儿的,而Flutter 中,StatefulWidget
须要重写createStae()
,返回一个State,而build
方法须要放在State 中,至于为何不放在StatefulWidget 呢?有两点缘由:
状态访问问题
因为build
方法在state 每次改变时都会调用,在StatefulWidget
有不少状态时,build
方法须要传入一个State 参数,那么,只能将State的全部状态公开才能在State类外部访问,但公开状态后,状态将再也不具备私密性,这样对状态的修改将变得不可控;
Widget build(BuildContext context, State state){
//state.a etc...
...
}
复制代码
继承StatefulWidget问题
当第一个状况发生后,若是有个子Widget 继承自一个引入了抽象方法build(BuildContext context)
的父Widget,那么子Widget 在实现这个build
时都须要传入一个state,此时父Widget 就必须将本身的state 传入给子Widget,这样就十分不合理,由于父Widget 的state 只与自身逻辑有关,且传递给子Widget 还需另外的传递机制,所以,应该将build
方法放在State 中。
class ChildWidgert extends ParentWidget{
@override
Widget build(BuildContext context, State state){
super.build(context, _parentWidgetState)
}
}
复制代码
Flutter 的生命周期以下图:
initState
这个函数至关于在React 中的构造函数中初始化State,能够在这一步进行数据请求加载
didUpdateWidget
当调用了 setState
改变Widget 状态时,Flutter 会建立一个新的 Widget 来绑定这个 State 并在此方法中传递旧 Widget ,若是你想比对新旧 Widget 而且对 State 作一些调整,或者某些 Widget 上涉及到 controller 的变动时,就能够在此回调方法中移除旧的 controller 并建立新的 controller;
@override
void didUpdateWidget(AVCycleLess oldWidget){
super.didUpdateWidget(oldWidget);
}
复制代码
dispose
当Widget 被释放(如路由切换),Widget 中存在一些监听或持久化的变量,你就须要在 dispose 中进行释放。
当咱们进入页面进行一些耗时的操做,好比请求数据、初始化某些设置等时,咱们一般须要显示一个加载页面,通常作法都是判断数据状态来切换显示的组件,而在Flutter 中则有FutureBuilder
这种便利的解决方案,这里展开篇幅会很长,能够参考FutureBuilder的使用方法和注意事项
在Flutter 中,路由分为静态路由和动态路由,静态路由没法传递参数,因此在须要传递参数的状况下只能使用动态路由;
静态路由在新建App 时定义,使用Navigator.of(context).pushNamed('/router/a');
进行切换,pushNamed 返回一个Future,能够接收来自下一个页面的返回值。
return new MaterialApp(
home: new Text('hello'),
routes: <String, WidgetBuilder> {
'/router/a': (_) => new APage(),
'/router/b': (_) => new BPage(),
},
);
// then 说明
// 当前页面
Navigator.of(context).pushNamed('/router/b').then((value) {
// value 为下一个页面的返回值
});
// b 页面
Navigator.of(context).pop('some data');
复制代码
动态路由使用push
方法,传入一个route 对象,在builder 中建立一个新页面对象,若是须要自定义动画效果,只须要使用PageRouteBuilder
替换MaterialPageRoute
,在transitionsBuilder
中定义动画便可。
Navigator.of(context).push(new MaterialPageRoute(builder: (_) {
return new NewPage(data: 'some data');
}));
复制代码
在Flutter 中,网络请求是由HttpClient
进行的,但其操做十分麻烦,因此有Dio 这么一个优秀的请求库来简化咱们的工做,须要注意的是,当App 只有一个数据源时,Dio 应该使用单例模式
当咱们获取到数据时,一般咱们都会拿到一个json,在JavaScript 中,咱们能够很任意地直接使用点操做符来获取数据中的字段,可是在Dart中,你须要引入dart:convert
,并使用JSON.decode(json)
,但它返回的是一个Map<String, dynamic>
,意味着咱们直到运行时才知道值的类型,也就失去了大部分静态类型语言特性:类型安全、自动补全和最重要的编译时异常。
但这样一来,咱们的代码可能会变得很是容易出错。咱们一般须要编写模型类来序列化JSON,官方推荐了json_serializable
(相关操做看这里) 来辅助咱们生成库序列化JSON,经过这种方式,咱们就能够直接用点操做符来操做数据了。
若是仍是嫌麻烦,能够试试JSONFormat4Flutter这一工具(我还没用过,看着很不错的样子。)
在Vue 中,咱们只须要使用@click
之类的方法便可监听事件,而React 中则是onClick
之类的方法,但在Flutter 中,咱们须要将须要监听事件的元素包裹在GestureDetector
中,使用onTap
等方法来处理事件,对事件的行为表现,咱们能够经过设置behavior
来控制,
enum HitTestBehavior {
deferToChild, // 子widget会一个接一个的进行命中测试,若是子Widget中有测试经过的,则当前Widget经过,这就意味着,若是指针事件做用于子Widget上时,其父(祖先)Widget也确定能够收到该事件。
opaque,// 在命中测试时,将当前Widget当成不透明处理(即便自己是透明的),最终的效果至关于当前Widget的整个区域都是点击区域
translucent,// 当点击Widget透明区域时,能够对自身边界内及底部可视区域都进行命中测试,这意味着点击顶部widget透明区域时,顶部widget和底部widget均可以接收到事件
}
复制代码
在Flutter 中,若是须要使用Canvas,咱们须要继承CustomPainter 并重写paint方法来绘制自定义图形。在使用Canvas时,咱们须要知道三个概念:
canvas
画布对象,包括了各类绘制方法,用来绘制各类图形
size
当前绘制区域的大小
paint
画笔,用来控制画出来的各类属性,如颜色、描边及抗锯齿等;
使用例子以下:
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(Offset.zero & size, Paint()
..isAntiAlias = true // 抗锯齿
..style = PaintingStyle.fill // 填充,stroke则为使用描边
..color = Color(0xFF000000) // yanse
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false; // 强制不重绘,提升性能
}
复制代码
说到mixin ,相信Vue 和React 的使用者都很熟悉,虽然React中mixin已 被高阶函数或Decorator取代,但在Flutter 中,mixin 仍是得以保留。 它使用with
来引入一个mixin,定义的方式以下:
class A {
int a = 1;
void b(){
print('c');
}
}
class B with A{
}
B b = new B();
print(b.a);
b.b();
复制代码
不过,mixin 在 Dart 中是有如下使用条件的:
在使用Tab 时,切换Tab后,每一个Tab 都会被销毁而后重建,因而会屡次调用initState,那有没有相似Vue 中的<keep-alive>
组件同样的存在呢?答案是有的,那就是AutomaticKeepAliveClientMixin
。只须要继承这个mixin并实现wantKeepAlive
方法便可。但widget在不显示以后也不会被销毁仍然保存在内存中,因此慎重使用这个方法
class APageState extends State<APage> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
// ...
}
复制代码
以上只是我这10天断断续续作出第一个粗糙的Flutter App所学到的东西,有些是查资料过程当中看到的一些知识点,并无用在项目中,还有不少细致的或者没遇到过的东西值得探讨,等之后遇到了有机会再讲讲。