Dart Extension Methods

5,440 阅读4分钟

前言

这篇文章记录了在探索Extension Methods的道路上遇到的坑与收获。查阅太多文章都是直接机翻的国外文章,好多坑点根本没有提到。希望这篇文章可以帮助到大家。

什么是Dart Extension Methods

Extension Methods 属于Dart高级用法。含义也非常容易理解,就是字面意思-拓展方法,用于给无法或不想直接修改的类或库拓展自己想要函数。熟悉移动端开发的小伙伴基本都可以轻车熟路,它与 iOS 的 Extension 和 Android 的 Extension Methods都是一样的。

环境配置

要想使用 Extension Methods的话,Flutter SDK版本最低要1.12.13,Dart SDK的版本必须高于2.7.0,在pubspec.yaml里配置:

environment:
sdk: ">=2.7.0 <3.0.0"

同时需要在pubspec.yaml同级创建analysis_options.yaml文件,里面配置:

analyzer:
    enable-experiment:
        - extension-methods

analysis_options.yaml还可以添加一些Dart其他用法,比如代码规范校验等,此次就不在这里做拓展。代码规范校验可以参看法老的Flutter Analysis Options

用法

Extension Method的语法也非常简单,如下:

extension <extension_name> on <type> {
  (<member_definition>)*
} 

比如我想给String扩展一个打印自己的方法

extension StringExtension on String{
  printMyself(){
    print("printMyself---" + this);
  }
}
void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;

      widget.title.printMyself();
      
    });

系统加载中。。。

应用场景

1.功能定制化

日常开发过程中总会遇到一些业务项比较定制化的需求,比如对金额做特定处理展示千位符或单位的,对时间做处理进行格式化的。Extension Methods就可以非常好的处理这些,并汇总在一起。

2.第三方库拓展

在没有遇到Extension Methods之前,我们想要对某个第三方类进行一些统一的配置预设值。大多数做法可能是跟我下面差不多,做一个包装类来进行操作:

class ZNHttpClient {
  static Dio _dio;

  //initDio
  Future<Dio> getDio() async {
    //Do Something
    ...
    _dio.options.responseType = ResponseType.JSON;
    return _dio;
  }
}

现在直接在原库上进行拓展

extension DioExtension on Dio{
  Future<Dio> getDio() async {
    //Do Something
    ...
    return dio;
  }
}

注意事项

上面吹了Extension Methods 能在项目中帮我们优化代码的应用场景,这边就要把它从神坛上拉下来。

1.虽然Extension Methods可以对类进行扩展,但不是什么都可以扩展的,以下的情况就会让代码直接产出编译错误:

  • 声明一个具有与扩展名相同的基本名称的成员。
  • 声明与扩展名相同的类型参数。
  • 声明一个具有与任何扩展程序类型参数名称相同的基本名称的成员。
  • 声明两个具有相同基本名称的成员,除非一个成员是getter而另一个是setter。
  • 声明一个具有相同名字的setter和getter,一个是静态的,另一个不是。
  • 声明具有相同名字成员作为成员声明由Object(==,hashCode,toString,noSuchMethod,runtimeType)。这适用于静态和实例成员声明。
  • 声明一个构造函数。
  • 声明一个实例变量。
  • 声明一个抽象成员。
  • 声明成员,其正式参数标记为covariant。

2.Extension Methods写法如下,则只对当前文件有效:

extension _StringExtension on String{
  printMyself(){
    print("printMyself---" + this);
  }
}
extension on String{
  printMyself(){
    print("printMyself---" + this);
  }
}

因为上面两个声明都为私有声明。第一个很好理解,扩展名加了_;第二就很难理解了,我也在这个上面耗费了好长时间,最后在官方的文档找到下面短短的一句话。

To create a local extension that’s visible only in the library where it’s declared, either omit the extension name or give it a name that starts with an underscore (_).

下面这个解释我在什么地方见到的,但是后来找不到了。后续找到了或者有朋友找到的麻烦帮忙指正一下,感激不尽。

extension on String{
  printMyself(){
    print("printMyself---" + this);
  }
}

等同于

extension _ on String{
  printMyself(){
    print("printMyself---" + this);
  }
}

3.当因方法名冲突时,可以使用下面的形式进行调用:

  • show & hide
// Defines the String extension method parseInt().
import 'string_apis.dart';

// Also defines parseInt(), but hiding NumberParsing2
// hides that extension method.
import 'string_apis_2.dart' hide NumberParsing2;
  • 显式调用
extension StringExtension on String{

  int get length {
    print("length---" + this.length.toString());
    return this.length;
  }

  printMyself(){
    print("printMyself---" + this);
  }
}
print(StringExtension(widget.title).length);

4.不能使用类型推断

这个我还没理解到位,可以参看参考链接 Dart Extension Methods Fundamentals里的It Is All Static段落。

参考链接