Dart:具有可为空属性的自定义 "copyWith" 方法

Dart: Custom "copyWith" method with nullable properties

我正在尝试为我的 class 创建一个“copyWith”方法,它适用于大多数情况。

问题是当我尝试将可为 null 的 属性 设置为 null 时,因为我的函数无法识别它是否是有意的。

例如:

class Person {
  final String? name;
  
  Person(this.name);
  
  Person copyWith({String? name}) => Person(name ?? this.name);
}

void main() {
  final person = Person("Gustavo");
  print(person.name); // prints Gustavo
  
  // I want the name property to be nul
  final person2 = person.copyWith(name: null);
  print(person2.name); // Prints Gustavo
}

有人知道这种情况的解决方法吗?这真的很困扰我,我不知道如何避免这种情况。

Person.name被声明为不可空的,所以copyWith不可能给它赋空值。如果您希望 Person.name 可以为空,您应该问问自己是否真的想要区分 null 和空字符串。通常你不会。

如果您确实希望 null 都允许空字符串,那么您将需要使用其他标记值:

class Person {
  static const _invalid_name = '_invalid_name_';

  final String? name;
  
  Person(this.name);
  
  Person copyWith({String? name = _invalid_name}) =>
    Person(name != _invalid_name ? name : this.name);
}

或者您需要将其包装在另一个 class 中,例如:

class Optional<T> {
  final bool isValid;
  final T? _value;

  // Cast away nullability if T is non-nullable.
  T get value => _value as T;

  const Optional()
      : isValid = false,
        _value = null;
  const Optional.value(this._value) : isValid = true;
}

class Person {
  final String? name;

  Person(this.name);

  Person copyWith({Optional<String?> name = const Optional()}) =>
      Person(name.isValid ? name.value : this.name);
}

void main() {
  final person = Person("Gustavo");
  print(person.name);

  final person2 = person.copyWith(name: Optional.value(null));
  print(person2.name);
}

现有的包实现了 Optional-like classes,可能可以帮助您。

我正在使用 Optional package 来解决这个问题,所以代码看起来像这样:

final TZDateTime dateTime;
final double value;
final Duration? duration;

...

DataPoint _copyWith({
    TZDateTime? dateTime,
    double? value,
    Optional<Duration?>? duration})  {

    return DataPoint(
      dateTime ?? this.dateTime,
      value ?? this.value,
      duration: duration != null ?
        duration.orElseNull :
        this.duration,
    );
  }

在此示例中,duration 是一个可为空的字段,copyWith 模式正常工作。唯一需要做的不同的是,如果您设置 duration,将其包装在 Optional 中,如下所示:

Duration? newDuration = Duration(minutes: 60);
_copyWith(duration: Optional.ofNullable(newDuration));

或者,如果您想将持续时间设置为空:

_copyWith(duration: Optional.empty());

copyWith 的实现两倍大为代价,您实际上可以使用标志来允许 null-ing 字段而不使用任何“默认空对象”或选项class:

class Person {
  final String? name;
  final int? age;

  Person(this.name, this.age);

  Person copyWith({
    String? name,
    bool noName = false,
    int? age,
    bool noAge = false,
    // ...
  }) =>
      Person(
        name ?? (noName ? null : this.name),
        age ?? (noAge ? null : this.age),
        // ...
      );
}

void main() {
  final person = Person('Gustavo', 42);
  print(person.name); // prints Gustavo
  print(person.age); // Prints 42

  final person2 = person.copyWith(noName: true, age: 84);
  print(person2.name); // Prints null
  print(person2.age); // Prints 84

  final person3 = person2.copyWith(age: 21);
  print(person3.name); // Prints null
  print(person3.age); // Prints 21

  final person4 = person3.copyWith(name: 'Bob', noAge: true);
  print(person4.name); // Prints Bob
  print(person4.age); // Prints null

  runApp(MyApp());
}

确实有无意义的情况:

final person = otherPerson.copyWith(name: 'John', noName: true);

但我想如果你真的想禁止它,你可以为此做出断言。

另一种解决方案是使用函数来设置值。这样,您可以确定未提供的函数 (null)、提供的函数和 returns () => null 以及 returns 名称的函数() => 'Gustavo')

class Person {
  final String? name;

  Person(this.name);

  Person copyWith({String? Function()? name}) =>
      Person(name != null ? name() : this.name);
}

void main() {
  final person = Person('Gustavo');
  print(person.name); // prints Gustavo

  // I want the name property to be nul
  final person2 = person.copyWith(name: () => null);
  print(person2.name); // Prints null

  final person3 = person.copyWith(name: () => 'new name');
  print(person3.name); // Prints new name

  final person4 = person.copyWith();
  print(person4.name); // Prints Gustavo
}

这使得设置名称稍微麻烦一些,但好的一面是,如果您尝试直接传递字符串,编译器会告诉您提供了错误的类型,因此会提醒您添加 () => 到它。

灵感来自答案:

您需要做的就是提供一个包装器。考虑这个例子:

class Person {
  final String? name;

  Person(this.name);

  Person copyWith({Wrapped<String?>? name}) =>
      Person(name != null ? name.value : this.name);
}

// This is all you need:
class Wrapped<T> {
  final T value;
  const Wrapped.value(this.value);
}

void main() {
  final person = Person('John');
  print(person.name); // Prints John

  final person2 = person.copyWith();
  print(person2.name); // Prints John

  final person3 = person.copyWith(name: Wrapped.value('Cena'));
  print(person3.name); // Prints Cena

  final person4 = person.copyWith(name: Wrapped.value(null));
  print(person4.name); // Prints null
}

有多个选项:

1. ValueGetter

class B {
  const B();
}

class A {
  const A({
    this.nonNullable = const B(),
    this.nullable,
  });

  final B nonNullable;
  final B? nullable;

  A copyWith({
    B? nonNullable,
    ValueGetter<B?>? nullable,
  }) {
    return A(
      nonNullable: nonNullable ?? this.nonNullable,
      nullable: nullable != null ? nullable() : this.nullable,
    );
  }
}

const A().copyWith(nullable: () => null);
const A().copyWith(nullable: () => const B());

2。从 Quiver package

中可选
class B {
  const B();
}

class A {
  const A({
    this.nonNullable = const B(),
    this.nullable,
  });

  final B nonNullable;
  final B? nullable;

  A copyWith({
    B? nonNullable,
    Optional<B>? nullable,
  }) {
    return A(
      nonNullable: nonNullable ?? this.nonNullable,
      nullable: nullable != null ? nullable.value : this.nullable,
    );
  }
}

const A().copyWith(nullable: const Optional.fromNullable(null));
const A().copyWith(nullable: const Optional.fromNullable(B()));

3。 copyWith 作为字段

class _Undefined {}

class B {
  const B();
}

class A {
  A({
    this.nonNullable = const B(),
    this.nullable,
  });

  final B nonNullable;
  final B? nullable;

  // const constructor no more avaible
  late A Function({
    B? nonNullable,
    B? nullable,
  }) copyWith = _copyWith;

  A _copyWith({
    B? nonNullable,
    Object? nullable = _Undefined,
  }) {
    return A(
      nonNullable: nonNullable ?? this.nonNullable,
      nullable: nullable == _Undefined ? this.nullable : nullable as B?,
    );
  }
}

A().copyWith(nullable: null);
A().copyWith(nullable: const B());

4. copyWith 重定向构造函数

class _Undefined {}

class B {
  const B();
}

abstract class A {
  const factory A({
    B nonNullable,
    B? nullable,
  }) = _A;

  const A._({
    required this.nonNullable,
    this.nullable,
  });

  final B nonNullable;
  final B? nullable;

  A copyWith({B? nonNullable, B? nullable});
}

class _A extends A {
  const _A({
    B nonNullable = const B(),
    B? nullable,
  }) : super._(nonNullable: nonNullable, nullable: nullable);

  @override
  A copyWith({B? nonNullable, Object? nullable = _Undefined}) {
    return _A(
      nonNullable: nonNullable ?? this.nonNullable,
      nullable: nullable == _Undefined ? this.nullable : nullable as B?,
    );
  }
}

const A().copyWith(nullable: null);
const A().copyWith(nullable: const B());

5. copyWith 重定向构造函数 2

class _Undefined {}

class B {
  const B();
}

abstract class A {
  const factory A({
    B nonNullable,
    B? nullable,
  }) = _A;

  const A._();

  B get nonNullable;
  B? get nullable;

  A copyWith({B? nonNullable, B? nullable});
}

class _A extends A {
  const _A({
    this.nonNullable = const B(),
    this.nullable,
  }) : super._();

  @override
  final B nonNullable;
  @override
  final B? nullable;

  @override
  A copyWith({B? nonNullable, Object? nullable = _Undefined}) {
    return _A(
      nonNullable: nonNullable ?? this.nonNullable,
      nullable: nullable == _Undefined ? this.nullable : nullable as B?,
    );
  }
}

const A().copyWith(nullable: null);
const A().copyWith(nullable: const B());