Saltar al contenido principal

Atajos con punto

Aprende sobre la sintaxis abreviada de punto en Dart.

Descripción general

#

La sintaxis abreviada de punto .foo te permite escribir código Dart más conciso al omitir el tipo cuando el compilador puede inferirlo del contexto. Esto proporciona una alternativa limpia a escribir el ContextType.foo completo al acceder a valores de enums, miembros estáticos o constructores.

En esencia, la sintaxis abreviada de punto permite que una expresión comience con una de las siguientes y luego, opcionalmente, encadene otras operaciones:

  • Identificador .myValue

  • Constructor .new()

  • Creación de constante const .myValue()

Aquí tienes un vistazo rápido a cómo simplifica la asignación de un enum:

dart
// Use dot shorthand syntax on enums:
enum Status { none, running, stopped, paused }

Status currentStatus = .running; // Instead of Status.running

// Use dot shorthand syntax on a static method:
int port = .parse('8080'); // Instead of int.parse('8080')

// Uses dot shorthand syntax on a constructor:
class Point {
  final int x, y;
  Point(this.x, this.y);
  Point.origin() : x = 0, y = 0;
}

Point origin = .origin(); // Instead of Point.origin()

El papel del tipo de contexto (context type)

#

La sintaxis abreviada de punto utiliza el tipo de contexto (context type)Tipo de contextoEl tipo que el código circundante espera de una expresión. Obtén más información para determinar el miembro al que se resuelve el compilador. El tipo de contexto es el tipo que Dart espera que tenga una expresión en función de su ubicación. Por ejemplo, en Status currentStatus = .running, el compilador sabe que se espera un Status, por lo que infiere que .running significa Status.running.

Estructura léxica y sintaxis

#

Una abreviatura de miembro estático es una expresión que comienza con un punto inicial (.). Cuando el tipo se conoce a partir del contexto circundante, esta sintaxis proporciona una forma concisa de acceder a miembros estáticos, constructores y valores de enums.

Enums

#

Un caso de uso principal y muy recomendado para la sintaxis abreviada de punto es con enums, especialmente en asignaciones y sentencias switch, donde el tipo de enum es muy obvio.

dart
enum LogLevel { debug, info, warning, error }

/// Returns the color code to use for the specified log [level].
String colorCode(LogLevel level) {
  // Use dot shorthand syntax for enum values in switch cases:
  return switch (level) {
    .debug => 'gray', // Instead of LogLevel.debug
    .info => 'blue', // Instead of LogLevel.info
    .warning => 'orange', // Instead of LogLevel.warning
    .error => 'red', // Instead of LogLevel.error
  };
}

// Example usage:
String warnColor = colorCode(.warning); // Returns 'orange'

Constructores nombrados

#

La sintaxis abreviada de punto es útil para invocar constructores nombrados o constructores de tipo factory. Esta sintaxis también funciona al proporcionar argumentos de tipo al constructor de una clase genérica.

dart
class Point {
  final double x, y;
  const Point(this.x, this.y);
  const Point.origin() : x = 0, y = 0; // Named constructor

  // Factory constructor
  factory Point.fromList(List<double> list) {
    return Point(list[0], list[1]);
  }
}

// Use dot shorthand syntax on a named constructor:
Point origin = .origin(); // Instead of Point.origin()

// Use dot shorthand syntax on a factory constructor:
Point p1 = .fromList([1.0, 2.0]); // Instead of Point.fromList([1.0, 2.0])

// Use dot shorthand syntax on a generic class constructor:
List<int> intList = .filled(5, 0); // Instead of List.filled(5, 0)

Constructores sin nombre

#

La abreviación de punto .new proporciona una forma concisa de llamar a un constructor sin nombre de una clase. Esto es útil para asignar campos o variables donde el tipo ya está declarado explícitamente.

Esta sintaxis es particularmente eficaz para limpiar inicializadores de campos de clase repetitivos. Como se muestra en el siguiente ejemplo "después" usando la sintaxis abreviada de punto, se puede usar para constructores tanto con como sin argumentos. También infiere cualquier argumento de tipo genérico del contexto.

Without dot shorthands:

dart
class _PageState extends State<Page> {
  late final AnimationController _animationController = AnimationController(
    vsync: this,
  );
  final ScrollController _scrollController = ScrollController();

  final GlobalKey<ScaffoldMessengerState> scaffoldKey =
      GlobalKey<ScaffoldMessengerState>();

  Map<String, Map<String, bool>> properties = <String, Map<String, bool>>{};
  // ...
}

Using dot shorthands:

dart
// Use dot shorthand syntax for calling unnamed constructors:
class _PageState extends State<Page> {
  late final AnimationController _animationController = .new(vsync: this);
  final ScrollController _scrollController = .new();
  final GlobalKey<ScaffoldMessengerState> scaffoldKey = .new();
  Map<String, Map<String, bool>> properties = .new();
  // ...
}

Miembros estáticos

#

Puedes usar la sintaxis abreviada de punto para llamar a métodos estáticos o acceder a campos estáticos/getters. El compilador infiere la clase de destino a partir del tipo de contexto de la expresión.

dart
// Use dot shorthand syntax to invoke a static method:
int httpPort = .parse('80'); // Instead of int.parse('80')

// Use dot shorthand syntax to access a static field or getter:
BigInt bigIntZero = .zero; // Instead of BigInt.zero

Expresiones constantes

#

Puedes usar la sintaxis abreviada de punto dentro de un contexto constante si el miembro al que se accede es una constante en tiempo de compilación. Esto es común para valores de enums e invocar constructores const.

dart
enum Status { none, running, stopped, paused }

class Point {
  final double x, y;
  const Point(this.x, this.y);
  const Point.origin() : x = 0.0, y = 0.0;
}

// Use dot shorthand syntax for enum value:
const Status defaultStatus = .running; // Instead of Status.running

// Use dot shorthand syntax to invoke a const named constructor:
const Point myOrigin = .origin(); // Instead of Point.origin()

// Use dot shorthand syntax in a const collection literal:
const List<Point> keyPoints = [.origin(), .new(1.0, 1.0)];
// Instead of [Point.origin(), Point(1.0, 1.0)]

Reglas y limitaciones

#

La sintaxis abreviada de punto se basa en un tipo de contexto claro, lo que conlleva algunas reglas y limitaciones específicas que debes conocer.

Se requiere un tipo de contexto claro en cadenas

#

Aunque puedes encadenar operaciones como llamadas a métodos o accesos a propiedades en una abreviación de punto, toda la expresión se valida con respecto al tipo de contexto.

El compilador primero usa el contexto para determinar a qué se resuelve la abreviación de punto. Cualquier operación posterior en la cadena debe devolver un valor que coincida con ese mismo tipo de contexto inicial.

dart
// .fromCharCode(72) resolves to the String "H",
// then the instance method .toLowerCase() is called on that String.
String lowerH = .fromCharCode(72).toLowerCase();
// Instead of String.fromCharCode(72).toLowerCase()

print(lowerH); // Output: h

Comprobaciones de igualdad asimétricas

#

Los operadores == y != tienen una regla especial para la sintaxis abreviada de punto. Cuando se utiliza directamente en el lado derecho de una comprobación de igualdad, Dart utiliza el tipo estático del lado izquierdo para determinar la clase o enum de la abreviación.

Por ejemplo, en una expresión como myColor == .green, el tipo de la variable myColor se utiliza como contexto. Esto significa que el compilador interpreta .green como Color.green.

dart
enum Color { red, green, blue }

// Use dot shorthand syntax for equality expressions:
void allowedExamples() {
  Color myColor = Color.red;
  bool condition = true;

  // OK: `myColor` is a `Color`, so `.green` is inferred as `Color.green`.
  if (myColor == .green) {
    print('The color is green.');
  }

  // OK: Works with `!=` as well.
  if (myColor != .blue) {
    print('The color is not blue.');
  }

  // OK: The context for the ternary is the variable `inferredColor`
  // being assigned to, which has a type of `Color`.
  Color inferredColor = condition ? .green : .blue;
  print('Inferred color is $inferredColor');
}

La abreviación de punto debe estar en el lado derecho del operador == o !=. Tampoco se permite comparar con una expresión más compleja, como una expresión condicional.

análisis estático: fallo dart
enum Color { red, green, blue }

void notAllowedExamples() {
  Color myColor = Color.red;
  bool condition = true;

  // ERROR: The shorthand must be on the right side of `==`.
  // Dart's `==` operator is not symmetric for this feature.
  if (.red == myColor) {
    print('This will not compile.');
  }

  // ERROR: The right-hand side is a complex expression (a conditional expression),
  // which is not a valid target for shorthand in a comparison.
  if (myColor == (condition ? .green : .blue)) {
    print('This will not compile.');
  }

  // ERROR: The type context is lost by casting `myColor` to `Object`.
  // The compiler no longer knows that `.green` should refer to `Color.green`.
  if ((myColor as Object) == .green) {
    print('This will not compile.');
  }
}

Las sentencias de expresión no pueden comenzar con .

#

Para evitar posibles ambigüedades de análisis en el futuro, no se permite que una sentencia de expresión comience con un token ..

análisis estático: fallo dart
class Logger {
  static void log(String message) {
    print(message);
  }
}

void main() {
  // ERROR: An expression statement can't begin with `.`.
  // The compiler has no type context (like a variable assignment)
  // to infer that `.log` should refer to `Logger.log`.
  .log('Hello');
}

Manejo limitado de tipos de unión

#

Aunque hay un manejo especial para los tipos anulables (T?) y FutureOr<T>, el soporte es limitado.

  • Para un tipo anulable (T?), puedes acceder a los miembros estáticos de T, pero no de Null.

  • Para FutureOr<T>, puedes acceder a los miembros estáticos de T (principalmente para soportar los retornos de funciones async), pero no puedes acceder a los miembros estáticos de la clase Future misma.