typedef permite definir un alias para un tipo existente. No estás creando un tipo nuevo, sino un “apodo” para uno que ya existe. Esta es una distinción fundamental: para la máquina virtual de Dart y el compilador AOT, el alias es invisible; el tipo real es el que has asignado.
Antes de Dart 2.0, typedef solo servía para definir firmas de funciones. Con la llegada de la nueva sintaxis [disponible desde Dart 2.0], ahora puedes asignar cualquier tipo, incluyendo tipos complejos, genéricos o anidados.
¿Por qué no simplemente usar el tipo original? Principalmente por dos razones: legibilidad y semántica. Si tienes un mapa con una estructura anidada como Map<String, Map<String, List<int>>>, es un caos de leer. Un typedef lo convierte en algo comprensible. Por otro lado, puedes usarlo para dar intención. Escribir typedef UserId = String; no cambia nada a nivel de ejecución, pero le comunica al desarrollador que esa String no es cualquier cadena, sino un identificador de usuario.
Sin embargo, debes tener cuidado: typedef no proporciona tipado nominal. Si defines typedef Email = String;, para el compilador un Email y un String son exactamente lo mismo. Si una función pide un Email y le pasas un String cualquiera, el compilador no protestará. Para crear tipos que realmente se comporten de forma distinta siendo la misma estructura (tipado nominal), tendrías que recurrir a extension types [disponible desde Dart 3.3].
Si usas typedef de forma incorrecta, especialmente para intentar forzar reglas de negocio a través de tipos, terminarás con un sistema de tipos que “miente”, donde el código compila pero la intención semántica se pierde en el primer error de lógica.
// 1. Aliases de tipos simples para semántica
typedef UserId = String;
typedef UserRole = String;
// 2. Aliases para tipos complejos y anidados
typedef UserMetadata = Map<String, dynamic>;
// 3. Aliases para firmas de funciones (Function Types)
// La sintaxis es: typedef NombreAlias = TipoDeRetorno (Parámetros);
typedef UserValidator = bool Function(UserId id, UserMetadata meta);
// 4. Aliases genéricos
typedef Transformer<T, R> = R Function(T input);
class UserRegistry {
final UserValidator validator;
final Map<UserId, UserMetadata> _data = {};
UserRegistry(this.validator);
void register(UserId id, UserMetadata info) {
if (validator(id, info)) {
_data[id] = info;
print('Usuario $id registrado con éxito.');
} else {
print('Error: Validación fallida para $id.');
}
}
}
// Ejemplo de un transformador genérico
Transformer<String, int> lengthTransformer = (text) => text.length;
void main() {
// Implementación del validador usando el alias de función
final UserValidator myValidator: UserValidator = (id, meta) {
final hasEmail = meta.containsKey('email');
final isCorrectFormat = id.startsWith('USR-');
return hasEmail && isCorrectFormat;
};
final registry = UserRegistry(myValidator);
// Uso de los alias para tipos semánticos
final validId = 'USR-1024';
final userInfo: UserMetadata = {
'email': 'dev@example.com',
'level': 5,
};
registry.register(validId, userInfo);
// Prueba del transformador genérico
final length = lengthTransformer('Dart');
print('Longitud: $length');
// Nota: 'validId' es una String, pero el código es más legible
// gracias a que el contexto nos dice que es un 'UserId'.
}
Desglose del ejemplo
En el ejemplo anterior, observa cómo UserValidator simplifica drásticamente la firma del método register en UserRegistry. Sin el typedef, la firma de validator sería bool Function(String, Map<String, dynamic>), lo cual es mucho más ruidoso.
Al usar UserId dentro de UserValidator, estamos documentando la intención. Cuando navegas por el código, ya no ves “un string”, ves “un identificador de usuario”. Es documentación viva que el compilador no ignora porque, aunque para la VM sea un String, para el programador es una unidad de significado.
El UserMetadata resuelve el problema de los tipos anidados. En lugar de escribir Map<String, dynamic> en cada función que manipula datos de usuario, usamos un nombre que describe el dominio del problema.
Fíjate en Transformer<T, R>. Aquí estamos usando un typedef genérico. Esto nos permite definir una firma de función que puede aplicarse a cualquier par de tipos. En main, la variable lengthTransformer es una instancia de Transformer<String, int>, lo que significa que recibe un String y devuelve un int. Esto es extremadamente útil para inyectar lógica de transformación en procesos de procesamiento de datos (pipelines).
El error frecuente
El error más común es confiar en typedef para la seguridad de tipos de dominio. Muchos desarrolladores vienen de lenguajes con tipos nominales fuertes y asumen que typedef es la solución para evitar errores.
// ❌ ESTE ES UN ERROR DE DISEÑO SI BUSCAS SEGURIDAD
typedef Email = String;
typedef Password = String;
void login(Email email, Password password) {
print('Intentando loguear con $email y $password');
}
void main() {
final String miEmail = 'test@test.com';
final String miPassword = 'secret_password';
// El compilador permite esto sin quejarse, aunque es un error lógico.
// Has intercambiado los argumentos, pero como ambos son String,
// el sistema de tipos de Dart no detecta el error.
login(miPassword, miEmail);
}
Si necesitas que Email sea un tipo que no se pueda confundir con un String cualquiera, no uses typedef; usa una class o, si quieres máxima eficiencia de memoria y rendimiento, usa un extension type.
N° 56