美文网首页FlutterFlutterflutter
Flutter面试题带答案

Flutter面试题带答案

作者: whqfor | 来源:发表于2021-04-06 16:56 被阅读0次

    Flutter是一个相对新的跨平台框架,但是它的流行度正在迅速提高。雇主也意识到单一代码库的好处,依托Flutter可以使他们将两个或者三个团队合并成一个,Flutter开发者的工作数量也在增加。

    在这篇文章中,你将看到一系列的关于Flutter和Dart面试题的问题与答案。

    如果你是正在找工作的开发者,通过下面的问题(在查看答案前,请先尝试自行解答)可以帮助你在相关技能点查缺补漏。
    如果你是潜在的雇主,浏览下面问题,以获得向你的候选人提问的想法。
    或者可以用来测试你的Flutter和Dart的知识掌握程度。

    下面的问题分为几个三个等级:

    • 初级:适合初级Flutter开发者,已经熟悉了基本知识,并且只做了一些示例应用程序。
    • 中级:适合对Flutter和Dart工作方式有浓厚兴趣的中级开发者,您已经阅读了很多,并且尝试了更多。
    • 高级:适合高级开发人员,乐于探索Flutter框架和Dart语言,并且知道如何管理项目的人。

    在每一个级别,又分为两种类型:

    • 笔试型:适合邮件或者在线编程测试,因为它们涉及到编写代码。
    • 问答型:适合视频或者面对面的交流。

    初级笔试题

    问题1

    给定如下类

    class Recipe {
      int cows;
      int trampolines;
    
      Recipe(this.cows, this.trampolines);
      
      int makeMilkshake() {
        return cows + trampolines;
      }
    }
    

    使用胖箭头语法将makeMilkshake()转换成命名为milkshake的getter语法。

    答:

    如果一个方法只有一行代码,则可以通过使用=>语法返回结果来减少代码行数。

    methodName(parameters) => statement;
    

    注意当使用=>的时候,不需要再使用关键词return.
    makeMilkshake()转换后的代码如下

    int get milkshake => cows + trampolines;
    

    问题2

    给定如下Widget

    class MyWidget extends StatelessWidget {
      final personNextToMe = 'That reminds me about the time when I was ten and our neighbor, her name was Mrs. Mable, and she said...';
    
      @override
      Widget build(BuildContext context) {
        return Row(children: [
          Icon(Icons.airline_seat_legroom_reduced),
          Text(personNextToMe),
          Icon(Icons.airline_seat_legroom_reduced),
        ]);
      }
    }
    

    在一些窄屏设备上,文本溢出了,你会如何修复呐?


    1text_overflow.png

    答:

    Expanded(
      child: Text(
        personNextToMe,
      ),
    ),
    

    使用Expandedwidget来包裹Textwidget,以告知Row忽略Textwidget的固有宽度,并且根据行中剩余的空间来为其分配宽度。

    RowColumn或者Flexwidget中使用超过一个Expandendwidget时,会均匀的分配剩余空间。当有多个Expandwidget时,可以使用flex属性对优先级进行排序。

    假如你还使用了Textwidget的overflow属性,那就太棒了。

    更多的介绍,可以阅读Flutter文档中的布局约束部分

    问题3

    重构下面代码,以便Row显示宽度太窄无法容纳它们时,子节点自动换行到下一行展示。

    class MyWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Row(children: [
          Chip(label: Text('I')),
          Chip(label: Text('really')),
          Chip(label: Text('really')),
          Chip(label: Text('really')),
          Chip(label: Text('really')),
          Chip(label: Text('really')),
          Chip(label: Text('really')),
          Chip(label: Text('need')),
          Chip(label: Text('a')),
          Chip(label: Text('job')),
        ]);
      }
    }
    

    答:

    只需要将Row替换为Wrap就可以了。
    阅读Medium文章Flutter Wrap Widget以了解更多关于Wrapwidget。

    问题4

    如下代码,使用var声明list1final声明list2const声明list3,这些关键字的不同之处是什么,最后两行代码能够编译吗?

    var list1 = ['I', '💙', 'Flutter'];
    
    final list2 = list1;
    list2[2] = 'Dart';   // Will this line compile?
      
    const list3 = list1; // Will this line compile?
    

    答:

    当使用var关键词时,数据的类型是推断出来的,并且值可以改变。除了显示的声明了数据类型之外,下面的代码和上面第一行代码是等效的:

    List<String> list1 = ['I', '💙', 'Flutter'];
    

    使用finalconst时,你不能在初始值分配后重新分配新值。final修饰的变量在运行时分配一次,const修饰的变量,在运行程序之前的编译期就需要知道、设置或者硬编码变量的值。

    第三行代码可以编译成功,因为并没有对list2重新赋值,只是改变了第三个位置的元素的值(请记住,下标从0开始),默认情况下,在Dart中List是可变的。

    假如你试图按照下面的样子做,将会编译不通过,因为你在对final修饰的变量重新赋值。

    list2 = ['I', '💙', 'Dart'];
    

    第四行不会编译,因为list1得值并没有确定,直到runtime时。阅读Dartlang的文章Const, Static, Final, Oh my!以了解更多。

    问题5

    给定下面类

    class Pizza {
      String cheese = 'cheddar';
    }
    

    你如何将cheese变成私有变量,怎样将它变成全局变量,什么时候你使用全局变量?

    答:

    在变量的前面添加下划线_,可以使它在库中私有化。

    class Pizza {
      String _cheese = 'cheddar';
    }
    

    Dart没有类私有变量的概念。一个库通常是一个文件,一个文件可以包含多个类。
    假如你想要一个全局变量,只需要将变量移到类的外面就可以了。

    String cheese = 'cheddar';
    

    将其放在类的外面会使其变成顶级变量,导入其所在文件后,就可以在任何地方使用它了。

    全局变量通常不建议使用,因为很难知道是哪里修改了它们,难以追踪修改路径,这会使调试和测试变得困难,但是有时它们也会很有用:

    • 快速搭建你并不打算长期维护的demo示例时。
    • 创建单例以提供类似于数据库或者网络身份验证的服务。
    • 制作const变量以共享颜色、尺寸、样式、主题等内容。这些类型的全局变量通常存储在单独的文件中,例如Constants.dart,然后引入待库中。

    阅读Dart语言的库和可见性文章以了解更多。

    初级问答题

    问题1

    hot reloadhot restart的区别是什么?

    答:

    hot reload在立刻更新UI的同时保持程序的状态,相比之下hot restart花费更长一点的时间,因为它会在更新UI之前将程序的状态置为初始状态。两者都比完全重新启动(full restart)要快,这需要重新编译应用程序。

    当有重大的更改时,你需要停止并重新运行该程序,在极少数的情况下,你可能还需要在模拟器或者真机上删除应用程序,然后重新安装。

    问题2

    StatelessWidgetStatefulWidget的区别是什么?

    答:

    StatelessWidget是一个不可变的类,充当UI布局中某些部分的蓝图,当某个组件在显示期间不需要改变,或者说没有状态(State),你可以使用它。
    StatefulWidget也是不可变的,但是它和一个State对象关联在一起,该对象允许你每次通过调用setState()时,使用新值重建这个widget,当UI可以动态改变时使用StatefulWidget

    假如State变得越来越复杂,或者一些状态存在于两个不同的widget中,则应该考虑更复杂的状态管理方案

    阅读stateless and stateful widgets以了解更多。

    问题3

    WidgetsAppMaterialApp的区别什么?

    答:

    WidgetsApp提供了基础的导航能力,和widgets库一起,它包含了很多Flutter使用的基础widget。

    MaterialApp和与之相应的的material库,是在WidgetsApp和与之相应的widgets库之上构建的一层,它遵循了Material设计风格,可以再任何平台或者设备上为应用程序提供统一的外观,material库提供了更多的Widget。

    在你的项目中,你并不一定要使用MaterialApp,也可以使用CupertinoApp来构建iOS风格的应用程序,这可以使iOS用户感觉更亲切,甚至你也可以自己定义一些widget。

    问题4

    可以嵌套使用Scaffold吗,为什么或者为什么不?

    答:

    当然可以,你绝对可以嵌套使用Scaffold,这体现Flutter的美,你可以控制整个UI。

    Scaffold也是个widget,因此你可以把它放在任何widget可以放置的地方。通过嵌套Scaffold,你可以对抽屉(drawers)、卡片(snack bars)、底页(bottom sheets)进行分层。

    2nested_scaffolds的副本.png

    问题5

    什么时候适合使用packagesplugins或者三方库?

    答:

    packages和plugins可以极大的节约你的时间,当别人已经解决了一个复杂问题时,你没必要再解决一遍,尤其是该解决方案已经获得了很好的评价时。

    另一方面,过度依赖三方库也可能有一些风险,他们可能编译不过、有bug或者被丢弃,当你需要切换到新的package或者plugin,可能会对代码做巨大的更改。

    这就是为什么需要将业务逻辑和三方库隔离开的原因,你可以通过创建一个Dart的抽象类,来充当package或者plugin的接口。一旦你设置完这种结构后,再遇到需要切换package或者plugin情况,你所要做的就只是重写接口层的具体实现了。

    中级笔试题

    问题1

    你正在编写一个称之为RubberBaby的购物程序,它可以用来卖玩偶,不幸的是在订单页,遇到了一个问题。假如顾客下了一个蓝色的订单和一个红色的订单,但是当顾客试图删除蓝色订单的时候,红色订单出了异常。

    3buggy_buttons.gif

    下面是具体代码,你会如何修复RubberBaby的有问题的按钮呐?

    class OrderPage extends StatefulWidget {
      @override
      _OrderPageState createState() => _OrderPageState();
    }
    
    class _OrderPageState extends State<OrderPage> {
      bool isShowing = true;
      @override
      Widget build(BuildContext context) {
        return Column(children: [
          RaisedButton(
            child: (Text('Delete blue')),
            onPressed: () {
              setState(() {
                isShowing = false;
              });
            },
          ),
          if (isShowing) CounterButton(color: Colors.blue),
          CounterButton(color: Colors.red),
        ]);
      }
    }
    

    答:

    当你使用stateful widget时,当widget树发生了改变时,框架会比较widget的类型,看看是否能够重用。

    因为两个CounterButton是相同的类型,Flutter并不知道哪个widget和state进行了绑定。这样的结果就是红色按钮进行更新时,使用了蓝色按钮内部的state。

    为了解决这个问题,可以为每个widget使用key属性,此属性为每个widget添加了一个ID:

    CounterButton(
      key: ValueKey('red'),
      color: Colors.red,
    ),
    

    通过添加key,你已经唯一的标记了红色计数按钮,Flutter将能够保留其状态。你可以在Medium文章Keys! What are they good for?.中了解更多关于如何使用key。

    问题2

    GitHub Jobs有一个开放的接口用于查询软件工程相关的职位,下面是接口地址,将会返回一个远程工作的岗位的列表:

    https://jobs.github.com/positions.json?location=remote
    

    下面给了一个简单的数据模型,你只需要关心公司名字和岗位名称,请编写一个返回值类型是Future<List<Job>>的方法,在这个问题中你可以忽略先错误检查。

    class Job {
      Job(this.company, this.title);
    
      final String company;
      final String title;
    }
    

    答:

    因为Api返回一个JSON map类型的数组,添加一个fromJson析构方法到Job类中将会是解析变得容易一些。

    class Job {
      Job(this.company, this.title);
    
      Job.fromJson(Map<String, dynamic> json)
          : company = json['company'],
            title = json['title'];
    
      final String company;
      final String title;
    }
    

    有很多packages可以用来进行网络请求,Dart官方团队维护了基本的http库,为了使用它,可以添加下面依赖到你的pubspec.yaml中:

    dependencies:
      http: ^0.12.1
    

    然后使用这个库创建一个方法,在后台从GitHub拉去网络数据:

    import 'dart:convert';
    import 'package:http/http.dart' as http;
    
    Future<List<Job>> fetchJobs() async {
      final host = 'jobs.github.com';
      final path = 'positions.json';
      final queryParameters = {'location': 'remote'};
      final headers = {'Accept': 'application/json'};
      final uri = Uri.https(host, path, queryParameters);
      final results = await http.get(uri, headers: headers);
      final jsonList = json.decode(results.body) as List;
      return jsonList.map((job) => Job.fromJson(job)).toList();
    }
    

    在定义Uri变量后,创建了http.get请求,它将会返回一个JSON字符串。

    下一步。使用json.decode,将JSON数据解析成一个map数据,然后转换成job对象的list。

    之前的文章Parsing JSON in Flutter,将会向你介绍如何使用web API,来进行更高效的创建模型和解析JSON数据。

    问题3

    给定一个Dart stream 产出无限的字符串,这些字符串可能是salmon或者trout

    final fishStream = FishHatchery().stream; 
    // salmon, trout, trout, salmon, ...
    

    将这个stream进行转换,要求仅当当前五次产出salmon字符串时,返回sushi字符串。

    答:

    stream转换如下:

    final fishStream = FishHatchery().stream;
    final sushiStream = fishStream
        .where((fish) => fish == 'salmon')
        .map((fish) => 'sushi')
        .take(5);
    

    假如你想了解更多,下面是FishHatchery类的代码

    class FishHatchery {
      FishHatchery() {
        Timer.periodic(Duration(seconds: 1), (t) {
          final isSalmon = Random().nextBool();
          final fish = (isSalmon) ? 'salmon' : 'trout';
          _controller.sink.add(fish);
        });
      }
    
      final _controller = StreamController<String>();
      Stream<String> get stream => _controller.stream;
    }
    

    你可以在Flutter团队的视频Dart Streams — Flutter in Focus,和Dart的文档Creating Streams中学到更多关于streams的内容。

    问题4

    为什么下面的代码会阻塞你的Flutter应用程序呐?

    String playHideAndSeekTheLongVersion() {
      var counting = 0;
      for (var i = 1; i <= 1000000000; i++) {
        counting = i;
      }
      return '$counting! Ready or not, here I come!';
    }
    

    把它改成async异步方法会有帮助吗?

    答:

    这将会阻塞你的应用程序,因为计算到10亿,即使对计算机来说也是一个代价昂贵的任务。

    Dart代码在自己的被称之为isolate的内存区域运行,也被称为内存线程。每个isolate有自己的堆空间,这确保没有isolate可以访问到其它isolate的状态。

    将这个方法改造成async方法也无济于事,因为它仍旧在同一个isolate上运行,如下:

    Future<String> playHideAndSeekTheLongVersion() async {
      var counting = 0;
      await Future(() {
        for (var i = 1; i <= 10000000000; i++) {
          counting = i;
        }
      });
      return '$counting! Ready or not, here I come!';
    }
    

    解决方案是将它运行在不同的isolate上:

    Future<String> makeSomeoneElseCountForMe() async {
      return await compute(playHideAndSeekTheLongVersion, 10000000000);
    }
    
    String playHideAndSeekTheLongVersion(int countTo) {
      var counting = 0;
      for (var i = 1; i <= countTo; i++) {
        counting = i;
      }
      return '$counting! Ready or not, here I come!';
    }
    

    这样就不会阻塞你的UI线程了。

    想要了解更多关于异步任务和isolate的内容,可以看Flutter团队的视频Isolates and Event Loops — Flutter in Focus和的文章Futures — Isolates — Event Loop

    在下个问题中,你将会见到另一种类型的isolate。

    中级问答题

    问题1

    什么是event loop,它和isolate的关系是什么?

    答:

    Dart是早期遵循社交距离的采用者,Dart代码运行在一个独立的被称之为isolate的线程上,相互隔离的的isolate不会一起出去玩,最多也就是互相发信息。用计算机术语来说就是,isolate之间不共享内存,它们之间的通信仅通过端口(port)进行。

    每一个isolate都有一个event loop,用于管理异步任务的运行,这些任务可能来自于两个队列之中:microtask queue,或者event queue

    Microtasks任务总是优先运行,它们主要是内核任务,开发者不必关心。调用Future时将会把任务放置到event queue中。

    很多新手Dart开发者,认为async方法运行在一个单独的线中,尽管对于系统处理的I/O操作这样说可能是正确的,但是它并不适用于你所写的代码,这就是为什么假如你有一个计算量很大的任务,你需要将它运行在一个单独的isolate之中。

    如果你想了解更多关于isolate、event loop、和并发相关的内容,可以参考Medium上的文章Dart asynchronous programming: Isolates and event loopsFutures — Isolates — Event Loops

    问题2

    怎么减少Widget的重新构建?

    答:

    当state发生改变时,你将重新构建widget,这种正常且理想的状态,因为它允许用户查看反映在UI中的状态更改。但是重新构建那些不需要改变的UI是性能浪费的。

    你可以采取以下措施来减少不必要的Widget重建。

    • 首先要做的就是将大的Widget树重构成较小的单个的Widget,每一个Widget都有它自己的build方法。
    • 尽可能的使用const构造函数,这将告知Flutter不需要重建这个widget。
    • 使stateful widget的子树尽可能的小,如果stateful widget有一个widget子树,那么为这个stateful widget创建一个自定义widget,并为其提供一个child参数。

    你可以在Flutter文档中,阅读更多关于性能优化的注意事项

    问题3

    什么是BuildContext,它有什么用?

    答:

    BuildContext实际上是在Element树中的Widget的元素,因此每个Widget都有其自己的BuildContext

    你通常使用BuildContext来获取主题(theme)或者另一个Widget的引用,例如:假如你想要展示一个material dialog,那么你需要获取scaffold的引用,可以通过Scaffold.of(context)来得到它,其中context就是上下文信息,通过of()来往上搜索树,直到找到最近的Scaffold。

    阅读didierboelens.com网站的文章Widget — State — Context — Inherited Widget 不仅可以了解到BuildContext,也可以了解到stateful widget的生命周期和inherited widget。

    此外,我们的文章Flutter Text Rendering将会带你窥探Flutter底层源码,通过这篇文章,你会了解到build context、elements甚至render对象。

    问题4

    在Flutter应用程序中,你怎么和native进行交互?

    答:

    通常你不需要和原生进行交互,因为Flutter或三方插件会处理这些问题,但是,如果你发现确实有特殊需要访问一些底层平台,你可以使用平台channel。

    其中一种类型是method channel,数据在Dart侧进行序列化,然后会将数据发送到原生侧,你可以在原生侧编写代码响应交互,然后回传序列化后的数据。在Android侧可以选用Kotlin或者Java,在iOS侧可以使用Objective-C或者Swift进行编写。

    但是,在开发web的时候,你不需要使用channel,这时非必要的步骤。

    第二种channel类型是event channel,你可以用来从native发送stream数据到flutter侧,这对监控传感器数据的场景很有用。

    可以在Flutter的文档platform channels中看到更详细的介绍

    问题5

    你可以做哪种类型的测试?

    答:

    Flutter中有三种类型的测试:unit testswidget testsintegration tests,单元测试是关于检查业务逻辑的有效性,widget测试确保UI Widget能够正确的响应你的期望,集成测试用于检测你的APP能否整体正常运行。

    还有一种测试不为大家所知,称作golden test,在golden test中,你有Widget或者屏幕的图像,以查看实际展示的Widget是否和它匹配。

    可以通过Flutter教程了解更多关于测试的内容,通过Medium的文章Flutter: Golden tests — compare Widgets with Snapshots了解更多golden test内容。

    raywenderlich.com网站上也有一篇文章介绍Flutter unit testing

    高级笔试题

    问题1

    遵从以下要求,演示Dart isolate通过port的交互过程:
    1、将downloadAndCompressTheInternet()函数发送到新的isolate中。
    2、上面方法的返回值是42

    答:

    import 'dart:isolate';
    
    void main() async {
      // 1
      final receivePort = ReceivePort();
      // 2
      final isolate = await Isolate.spawn(
        downloadAndCompressTheInternet,
        receivePort.sendPort,
      );
      // 3
      receivePort.listen((message) {
        print(message);
        receivePort.close();
        isolate.kill();
      });
    }
    
    // 4
    void downloadAndCompressTheInternet(SendPort sendPort) {
      sendPort.send(42);
    }
    

    在上述代码中,你:
    1、创建了一个端口用于从接收新的isolate中接收数据。
    2、创建一个新的isolate,给他一些工作去做,并且提供一个方式回传数据。
    3、监听新isolate中发送的任何数据,然后关闭这个isolate。
    4、使用main isolate正在监听的端口,将数据发送回来。

    下载和解压算法仍在开发中...

    阅读Joe的文章Dart Fundamentals — Isolates,以了解更多关于isolate通信的知识。

    问题2

    有两个树数据结构的数据,其中树的节点都是随机整数,这些数字不一定是唯一的,也不一定是有序存储的,两棵树的深度也是任意的。编写一个算法以识别出在第一颗树中但是不在第二棵树中的数字。

    下面是一个例子:

    4trees.png

    算法应该识别出,数字1在第一棵树中,并且不在第二棵树中。

    答:

    首先定义出输的节点:

    class Node {
      int data;
      List<Node> children;
    
      Node(this.data, {this.children});
    }
    

    编写逻辑,递归查找树,对整数进行去重。

     class UniqueTreeItems {
      final Set<int> _uniqueIntegers = HashSet<int>();
    
      Set<int> search(Node tree) {
        _addInOrder(tree);
        return _uniqueIntegers;
      }
    
      void _addInOrder(Node node) {
        _uniqueIntegers.add(node.data);
        if (node.children == null) return;
        for (final child in node.children) {
          _addInOrder(child);
        }
      }
    }
    

    设置测试数据

    final treeOne = Node(1, children: [
      Node(4, children: [
        Node(10),
        Node(12),
      ]),
      Node(3, children: [
        Node(3),
        Node(10),
        Node(1),
      ]),
    ]);
    
    final treeTwo = Node(4, children: [
      Node(10),
      Node(3),
      Node(12),
    ]);
    

    剔除在Tree1中并且也在Tree2中的数据:

    void main() async {
      final uniqueOne = UniqueTreeItems().search(treeOne);
      final uniqueTwo = UniqueTreeItems().search(treeTwo);
      final answer = uniqueOne.where((element) => !uniqueTwo.contains(element));
      answer.forEach(print); // 1
    }
    

    得到的结果是1

    高级问答题

    问题1

    不同状态管理框架的优缺点是什么?

    答:

    有多种多样的框架,其中一些比较知名状态管理框架,包括BLOC、伴随ChangeNotifier的Provider、Redux、MobX以及RxDart。这些都适用于中大型的应用程序。如果你只是快速开发一个小demo,那么stateful widget通常就足够了。

    与其列出不同状态管理框架的优缺点,不如查看这些框架更适用哪种场景。例如,对于某些人与其淹没在不胜枚举的选择中,不如选择一种比较容易掌握的方案,Provider和MobX都是不错的选择,它们可以直接在state类上调用方法以响应事件,使得这种场景更加直观。

    假如你重度依赖流,例如使用Firebase的ApI,那么自然会选择给予数据流的解决方案,比如BLOC和RxDart。

    假如你需要撤销/重做功能,那么你需要类似BLOC或者Redux这样,能够很好的处理不可变状态的解决方案。

    最后,更多的是归结于个人喜好,你可以在Flutter官方的文章list of state management approaches中找到更多流行的的关于状态管理的框架。

    在raywenderlich.com 网站上也有一些介绍BLOC和介绍Provider的文章。

    问题2

    如何设计一个控制电梯的应用程序?

    答:

    这个问题测试您的分析技能,以及组织和使用SOLID原则的能力。

    下面是一个参考答案。
    1、首先,确定核心的功能是什么:开门、关门,向上向下移动到不同楼层,寻求帮助,与其他电梯进行协调。这时您的业务逻辑,画一个流程图可能会更有帮助。

    2、以测试驱动(TDD)的方式去实现商业逻辑。也就是说,编写一个失败的测试用例,编写足够的以使它通过的业务逻辑代码,进行重构,然后编写另一个测试用例,再次进行所有操作。

    3、刚开始时,有没有物理按钮或者Flutter驱动的触摸屏都没关系,电梯的外观或者位置在哪无关紧要,紧急呼叫系统是什么也不重要,你可以在开发测试阶段,将这些外部因素抽象到模拟的接口后面。

    4、一旦完成了核心逻辑,你便可以实现前面仅通过接口表示的各个组件。对于UI来说,你需要设置一套状态管理系统,该系统用来处理按钮点击、电梯到达之类的事件,然后更新状态,这可能导致按钮编号点亮或者更新屏幕。你可能还需要实现与系统交互的服务,以处理紧急呼叫或打开们的硬件。

    5、安全对于乘坐电梯的人来说非常重要,因此除了单独的测试核心的业务逻辑和各个系统组件之外,你还需要进行全面的集成测试。对于电梯,将由机器人或者人进行手动测试。

    进阶引申

    恭喜你,你做到了最后,如果你不能完全回答上述问题也不要觉得难过,在写这篇文章的过程中,我也做了很多搜索。

    把这看成一个起点,记下你觉得薄弱的地方,然后在这些方面进行更多的研究。阅读Flutter文档Dart指南会教会你很多东西。

    如果你想学习跟多关于Dart的内容,关注我们的视频课程Dart基础,我们也会在raywenderlich.com网站上持续更新所有关于Flutter的新内容

    假如你有更多面试问题的建议,更好的答案,甚至是代码挑战,请把它们写在下面的评论区。

    相关文章

      网友评论

        本文标题:Flutter面试题带答案

        本文链接:https://www.haomeiwen.com/subject/nnxbhltx.html