Flutter 中 const 使用

1/20/2021 flutter

在查看 Flutter Widget 的源码的时候,常常会遇到 const 这个关键字。

1)带有 const 关键字的 TextStyle

TextStyle effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));

2)带有 const 的全局变量

const Color _kLightThemeHighlightColor = Color(0x66BCBCBC);

3)带有 const 关键字的构造函数

const Text.rich(
    this.textSpan, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
    this.textWidthBasis,
    this.textHeightBehavior,
  }) : assert(
         textSpan != null,
         'A non-null TextSpan must be provided to a Text.rich widget.',
       ),
       data = null,
       super(key: key);
  1. 在方法形参中的 const 关键字
Flow({
    Key key,
     this.delegate,
    List<Widget> children = const <Widget>[],
  }) : assert(delegate != null),
       super(key: key, children: RepaintBoundary.wrapAll(children));

5)...

官方的代码 const 随处可见,自己写的代码找不到它的影子。

任何事物的存在必有其意义

当然我也不推崇为了用而用,但是官方大把使用,事情肯定不会这么简单,在某方面肯定是有其益处的。

自己也简单分析了下用的少的原因:

  • 没去了解
  • 没去思考
  • 没去使用

所以本文的目的就是,了解使用 const,以及知道为什么?

# const

看到它的第一眼,就会想到 C 语言中的 指针常量和常量指针,老犯难了。但是在 Dart 里我们不需要明面上跟指针打交道,所以不用怕。

对于 const 的使用,我们可以简单归耐成 3 种场景:

  • const 用在 = 左边:定义常量
  • const 用在 = 右边:创建常量
  • const 构造函数:创建常量

# const 用在 = 左边

对于 const 用在=左边,这个比较容易理解,使用关键字 const 修饰变量表示该变量为 编译时常量

const 在声明变量时可以省略变量的类型, double, int 等。 必须在声明变量时赋值,一旦赋值就不允许修改,而声明值一定要是编译时常数

什么是编译时常数?

编译时值就要确定下来

  • 数值、字符串、其它的 const 变量

    const bar = 1000000; // 直接赋值 
    const b = 'hello';
    const double atm = 1.01325 * bar;
    
  • 表达式,表达式的所有值都是编译时可知的。

    const a = 1;
    const b = a > 1 ? 2 : 1;
    
  • 集合或对象 常量集合或者 const构造函数创建的对象

    const a = const [1,2,3];
    const b = ConstObj(2);
    

    对于const构造函数下面会进行详解

# const 用在 = 右边

当const用在=右边,其作用是 修饰值,它意味着对象的整个深度状态可以在编译时完全确定,并且对象将被冻结并且完全不可变。

一般用于修饰集合,它要求两点:

  • 集合的元素必须是递归的编译时常数。

    var c = 2;
    //ERROR, 集合元素必须是编译时常数。
    var a = const [c,2,3];
    
  • 不允许对集合做任何改变。

    const a = const [1,2,3];
    // ERROR, 不允许修改。
    a[1] = 2;
    

那么对于这两点我们做个小测验:

/// 测验 1
var a = [1,2,3]
a[0] = 3
print(a); 

/// 测验 2 
const a = [1,2,3];
a[0] = 3;
print(a);

/// 测验 3
var a = const [1,2,3];
a[0] = 3;
print(a);

如果你想验证自己答案,请对这 3 个小测验分别在 https://dartpad.dev (opens new window) 运行下。

写到这里,个人觉得最让人困惑的是 const 构造函数创建对象,之前我的理解是对象是在运行时期生成的,为什么可以用 const?

# Dart const 常量构造函数

一般对象是在运行时期生成,所以常量构造函数的条件还是比较苛刻的。

  • 常量构造函数需以 const 关键字修饰
  • const 构造函数必须用于成员变量都是 final 的类
  • 构建常量实例必须使用定义的常量构造函数
  • 如果实例化时不加const修饰符,即使调用的是常量构造函数,实例化的对象也不是常量实例

比如定义一个 Point 类:

class Point {
  final int x;
  final int y;
  const Point(this.x, this.y);
}

# 常量构造函数需以 const 关键字修饰

如下代码定义一个const对象,但是调用的构造方法不是 const 修饰的,则会报 Cannot invoke a non-'const' constructor where a const expression is expected.错误

void main() {
  const point = Point(1, 2); // 报错
}

class Point {
  final int x;
  final int y;
  Point(this.x, this.y);
}

# const 构造函数必须用于成员变量都是 final 的类

如下代码中成员变量x为非final,会报 Constructor is marked 'const' so all fields must be final. 的错误

void main() {
  const point = Point(1, 2); // 报错
}

class Point {
  int x;
  final int y;
  const Point(this.x, this.y);
}

# 构建常量实例必须使用定义的常量构造函数

如下代码,定义一个const对象,但是调用的却是非常量构造函数,会报Cannot invoke a non-'const' constructor where a const expression is expected.错误

void main() {
  var point = const Point(1, 2); // 报错
}
 
class Point {
  int x;
  int y;
  Point(this.x, this.y); // 非const构造函数
}

# 如果实例化时不加 const 修饰符,即使调用的是常量构造函数,实例化的对象也不是常量实例

如下代码,用常量构造函数构造一个对象,但是未用const修饰,那么该对象就不是const常量,其值可以再改变

void main() {
  var point = Point(1, 2); // 调用常量构造函数,但是未定义成常量
  print(point.toString());
  point = Point(10, 20); // 可以重新赋值,说明定义的变量为非常量
  print(point.toString());
}
 
class Point {
  final int x;
  final int y;
  const Point(this.x, this.y);
  
  String toString() {
    return 'Point(${x}, ${y})';
  }
}

# 为什么官方很多地方使用 const ?

  • 具有 const 关键字的变量在编译时初始化,常量值必须在编译期就确定。

  • 在运行时无法修改常量,无法重新创建。 对于任何给定的常量值,无论常量表达式计算了多少次,都将在内存中创建一个对象。常量对象在需要时可以重用,但从不重新创建

  • 对于 Flutter 而言,状态更新后, build 方法中不会再次初始化。

    build(context) {
        return Row(
            children: <Widget>[
                const Text("Hello"),
                const SizedBox(width: 10),
                const Text("Hello"),
                const SizedBox(width: 10),
                const Text("Can you hear me?"),
            ]
        )
    }
    

    Row 有五个孩子。当我们使用 const 关键字创建具有 const 构造函数的类的实例时,这些值在编译时创建,并且每个唯一值仅在内存中存储一次。前两个 Text 实例将解析为对内存中同一对象的引用,两个 SizedBox 实例也是如此。如果要添加 const SizedBox(width: 15),则将为该新值创建一个单独的常量实例。

    同样也可用使用 new 关键字(默认省略)来创建实例而不是使用 const,最终的运行效果也是一样的,但是如果你想减少你程序运行占用的内存或者想提高程序的性能,那么使用 const 是比较好的选择。

  • 常量的不可变性是具有依赖关系

    final size = 12.0;
    const Text(
      "Hello",
      style: TextStyle(
        fontSize: size,    // error
      ),
    )
    

    我们尝试创建 Text 的常量实例,但是,我们说过 const 构造函数的条件比较苛刻,其成员必须是常量,"hello" 是字面量,可以省略 const 关键字。同样 Dart 也会尝试将 TextStyle 创建为常量,因为它知道 TextStyle 必须为常量才满足 const Text() 的调用。但是由于 TextStyle 依赖于变量 size, 但 size 不是常数,直到运行时才具有值。所以不满足 const 构造函数条件报错。要解决此问题,必须替换 size 为常量或者直接使用数字。

上次更新: 10/20/2022, 5:36:35 AM