阅读 125

[译] 类型及其在参数中的应用:利用 Dart 特性优化代码

类型及其在参数中的应用:利用 Dart 特性优化代码

本篇教程将会介绍 Dart 语言的一些基础特性,以及如何将其应用于代码中。

正确的使用这些特性,能够让你的代码更加整洁、轻量,并且健壮。

1. 类型推断

Dart 编译器能够在变量初始化的时候自动推断它的类型,所以我们也就不必声明变量的类型。

在代码应用中,也就是我们可以将这样的代码:

String name = 'Andrea';
int age = 35;
double height = 1.84;
复制代码

转化为:

var name = 'Andrea';
var age = 35;
var height = 1.84;
复制代码

这段代码之所以能生效,是因为 Dart 可以从表达式右边的值推断出变量的类型。

我们可以像这样声明变量:

var x;
x = 15;
x = 'hello';
复制代码

在这个例子中,x 声明在前,初始化在后。

此时它的类型是动态的,即 dynamic,这意味着,它可以被多个表达式赋值为不同的类型。

小结

  • 当使用 var 的时候,只要变量的声明和初始化是同时完成的,那么 Dart 将能正确的推断出变量类型。

2. final 和 const

当我们使用 var 来声明变量的时候,这个变量可以被多次赋值:

var name = 'Andrea';
name = 'Bob';
复制代码

也就是说:

使用 var 意味着可以多次赋值

但是如果我们使用了 final,就不能给变量多次赋值了:

final name = 'Andrea';
name = 'Bob'; // 'name' 是一个 final 类型的变量,不可以被再次赋值
复制代码

final 的应用

在 widget 类中,很常见使用 final 声明的属性。例如:

class PlaceholderContent extends StatelessWidget {
  const PlaceholderContent({
    this.title,
    this.message,
  });
  final String title;
  final String message;
  
  // TODO:实现构建方法
}
复制代码

在这段代码中,titlemessage 在这个 widget 内是不可以被修改的,因为:

使用 final 意味着只能一次赋值

所以,使用 varfinal 的区别就是是否允许多次或只能一次赋值。现在我们再来看看 const

const

const 能够定义编译时常量

const 用来定义硬编码值,例如颜色、字体大小和图标等。

同时我们也可以在定义 widget 类的时候使用 const 构建函数。

这是完全可行的,因为所有 widget 内部的变量和方法都是编译时常量。例如:

class PlaceholderContent extends StatelessWidget {
  const PlaceholderContent({
    this.title,
    this.message,
  });
  final String title;
  final String message;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(
            title,
            style: TextStyle(fontSize: 32.0, color: Colors.black54),
          ),
          Text(
            message,
            style: TextStyle(fontSize: 16.0, color: Colors.black54),
          ),
        ],
      ),
    );
  }
}
复制代码

如果这个 widget 的构建函数是 const 类型,它就可以被这样构建:

const PlaceholderContent(
  title: 'Nothing here',
  message: 'Add a new item to get started',
)
复制代码

结果就是,这个 widget 可以被 Flutter 优化为,当它的父级变化时,widget 本身不会重复构建

小结:

3. 命名参数和位置参数

在 Dart 中,我们将变量使用大括号({})包起来,由此可以定义命名参数:

class PlaceholderContent extends StatelessWidget {
  // 使用命名参数的构建函数
  const PlaceholderContent({
    this.title,
    this.message,
  });
  final String title;
  final String message;
  
  // TODO:实现构建方法
}
复制代码

这段代码意味着,我们可以像这样创建 widget:

PlaceholderContent(
  title: 'Nothing here',
  message: 'Add a new item to get started',
)
复制代码

还有一种替代方案是,我们可以在构建函数中将大括号省略,声明位置参数:

// 使用位置参数的构建函数
const PlaceholderContent(
  this.title,
  this.message,
);
复制代码

结果就是,参数可以通过它们所在的位置来定义:

PlaceholderContent(
  'Nothing here', // title 参数位于 0 号位
  'Add a new item to get started', // message 参数位于 1 号位
)
复制代码

这完全行得通,但是当我们有多个参数的时候,这样很容易引起混乱。

此时命名参数就展露优势了,它们让代码更易写也更易读。

顺便说一句,你还可以将位置参数和命名参数结合起来:

// 位置参数优先,然后是命名参数
void _showAlert(BuildContext context, {String title, String content}) {
  // TODO:展示提示信息
}
复制代码

Flutter widget 中随处可见使用一个位置参数,然后使用多个命名参数的方式。Text widget 就是一个很好的例子。

我写代码的指导思想就是,代码一定要保持整洁、自洽。我会依照此合理选择命名参数和位置参数。

4. @required 和默认值

默认情况下,命名参数可以被省略。

省略命名参数就等于给它赋值为 null

有时候这会导致无法预期的后果。

在上面的例子中,我们可以在定义 PlaceholderContent() 时并不传入 titlemessage 参数。

这将会导致错误,因为这样的话我们会将 null 值传入 Text widget,但这是不允许的。

@required 是一种补救方法

我们可以为任何变量添加 required 注释:

const PlaceholderContent({
  @required this.title,
  @required this.message,
});
复制代码

这样当我们忘记传入参数的时候,编译器将会报出警告。

此时如果我们需要,我们仍旧可以明确写出传递 null 值:

PlaceholderContent(
  title: null,
  message: null,
)
复制代码

此时编译器就不会报警告了。

如果想要避免传入 null 值,我们可以增加一些断言(assert):

const PlaceholderContent({
  @required this.title,
  @required this.message,
}) : assert(title != null && message != null);
复制代码

这些修改让我们的代码安全系数更高,因为:

  • @required 会增加编译时检查
  • assert 会增加运行时检查

如果我们为代码加入断言,那么运行时的错误就更容易改正,因为此时的报错会明确指出导致错误的代码位置。

非空类型

@requiredassert 让我们的代码安全系数更高了,但是它们看上去有些笨重。

如果我们可以指定对象在编译时不可为空就更好了。

通过使用非空类型我们可以做到这一点,而它在一开始就内建在 Swift 和 Kotlin 中了。

而且非空类型现在也正计划应用于 Dart 语言。

让我们祈祷它可以快点到来吧。🤞

默认值

有时候,指定合理的默认值也很有用。

在 Dart 中这很容易就能做到:

const PlaceholderContent({
  this.title = 'Nothing here',
  this.message = 'Add a new item to get started',
}) : assert(title != null && message != null);
复制代码

使用这种语法,如果 titlemessage 参数被忽略了,那么默认值就会被使用。

顺便提一下,默认值也可以应用于位置参数:

int sum([int a = 0, int b = 0]) {
  return a + b;
}
print(sum(10)); // 打印出 10
复制代码

总结

代码是让机器执行的,但是也是要程序员阅读的。

时间宝贵。乖乖的写好代码 😉

  • 这样才能让你的应用更健壮,性能也更好。
  • 同时也能帮助你和你的团队有更好的发展。

编程愉快!

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏