Skill Constructeurs Primaires Dart & Nouvelle Syntaxe de Constructeur
Utilisez ce skill quand vous aidez les utilisateurs à écrire, refactoriser ou déboguer du code utilisant la fonctionnalité Constructeurs Primaires de Dart.
Exigences de Version Dart
- Dart 3.13 et supérieur : Les constructeurs primaires sont activés par défaut.
- Dart 3.12 : La fonctionnalité est disponible mais expérimentale. Les utilisateurs doivent explicitement activer le flag d'expérimentation
primary-constructorsvia--enable-experiment=primary-constructorsou dansanalysis_options.yaml:analyzer: enable-experiment: - primary-constructors - Dart 3.11 et antérieur : Les constructeurs primaires ne sont pas supportés.
1. Aperçu
Les Constructeurs Primaires permettent aux développeurs de déclarer un constructeur générative non-redirigeant ainsi qu'un ensemble de variables d'instance directement dans l'en-tête de la classe. Cela réduit considérablement le code passe-partout et améliore la lisibilité du code.
Avantages Clés
- Combine la déclaration de champ, la déclaration de paramètre et l'initialisation en une seule déclaration appelée déclaration de paramètre déclarant.
- Permet une référence sûre aux paramètres du constructeur dans les initialiseurs de champ non-late (Primary Initializer Scope).
- Permet aux corps de déclaration vides d'être représentés de manière concise avec un point-virgule (
;). - Introduit une syntaxe concise abrégée pour les constructeurs dans le corps.
2. Référence Syntaxique
2.1 Syntaxe de Base de l'En-tête de Classe
Pour déclarer un constructeur primaire, placez une liste de paramètres immédiatement après le nom du type (et les paramètres de type optionnels) :
// Déclare les champs x et y, et un constructeur générative Point(this.x, this.y)
class Point(var int x, var int y);
// Déclare les champs finals
class PointFinal(final int x, final int y);
2.2 Paramètres Déclarants, Initialisants et Ordinaires
Une liste de paramètres de constructeur primaire distingue trois types de paramètres :
- Paramètres Déclarants : Indiqués par le modificateur
varoufinal(par exemple,final int x). Ils créent implicitement un champ d'instance correspondant dans la classe. - Paramètres Initialisants : Indiqués par le préfixe
this.ousuper.(par exemple,this.xousuper.x). Ils initialisent un champ existant ou un paramètre de constructeur super, respectivement. - Paramètres Ordinaires : Déclarés sans modificateurs (par exemple,
int y). Ils ne deviennent pas des champs et ne sont disponibles que pendant l'initialisation (par exemple, dans les initialiseurs de champ ou la liste d'initialiseursthis :dans le corps de la classe).
// `x` est un champ et un paramètre car il a le mot-clé `final`. En particulier, nous pouvons utiliser le nom `x` dans la liste d'initialiseurs de la partie in-body du constructeur primaire. `y` est un seul paramètre car il n'a ni le mot-clé `final` ni `var`, mais `y` est passé au constructeur super via la liste d'initialiseurs `this :`.
class C(final int x, int y) extends Base {
this : super(y);
}
Les paramètres déclarants et les paramètres initialisants sont deux façons d'atteindre le même objectif : déclarer une classe avec des champs d'instance qui sont définis dans le constructeur. Les paramètres ordinaires sont différents en ce que leurs valeurs ne sont pas automatiquement acheminées vers un champ d'instance.
2.3 Constructeurs Primaires Constant
Pour rendre un constructeur primaire const, placez le mot-clé const avant le nom de la classe/du type dans l'en-tête de déclaration :
class const Point(final int x, final int y);
extension type const Ext(int x);
enum const MyEnum(final int x) {
entry(1);
}
2.4 Types d'Extension
Les types d'extension doivent utiliser les constructeurs primaires.
- Le paramètre unique dans l'en-tête est le champ de représentation.
- La variable de représentation ne peut pas utiliser le modificateur
var(utiliservardéclenche l'erreurrepresentation_field_modifier). - La variable de représentation peut optionnellement utiliser le modificateur
final. Sifinaln'est pas présent, il est déduit ; c'est-à-dire que le paramètre déclare si oui ou non il est explicitementfinal.
2.5 Raccourci Avec Braces Vides (;)
Quand une classe, une mixin class, une mixin, une extension ou un type d'extension a un corps vide, les accolades {} peuvent être remplacées par un point-virgule (;) :
class C(int x);
mixin class MC;
extension type ET(int x);
mixin M;
extension Ext on C;
2.6 La Partie In-Body d'un Constructeur Primaire (this ...)
Si un constructeur primaire nécessite des assertions ou des initialisations de champ personnalisées, elles peuvent être déclarées dans le corps en utilisant la syntaxe this : :
class Point(var int x, var int y) {
// Liste d'initialiseurs dans le corps de la classe
this : assert(x >= 0), y = y * 2;
}
Vous pouvez également écrire un corps de constructeur avec cette syntaxe (this {...}).
2.7 Syntaxe Concise Abrégée de Constructeur
Pour les constructeurs déclarés dans le corps de la classe, le nom de la classe peut être omis et remplacé par les mots-clés new ou factory :
| Syntaxe Traditionnelle | Syntaxe Concise Abrégée |
|---|---|
MyClass() {} |
new() {} |
MyClass.name() {} |
new name() {} |
const MyClass(); |
const new(); |
const MyClass.name(); |
const new name(); |
factory MyClass() => ... |
factory() => ... |
factory MyClass.name() => ... |
factory name() => ... |
3. Sémantique & Règles de Portée
3.1 Primary Initializer Scope
Quand un constructeur primaire est déclaré, ses paramètres formels sont introduits dans le Primary Initializer Scope. Cette portée est la portée courante pour les initialiseurs de champ non-late dans le corps de la classe et la liste d'initialiseurs du constructeur primaire (après this :).
Cela permet aux champs non-late de référencer directement les paramètres du constructeur pendant la déclaration :
class DeltaPoint(final int x, int delta) {
// 'x' et 'delta' sont en portée ici
final int y = x + delta;
}
3.2 Restriction des Variables d'Instance Late
Le primary initializer scope n'est pas actif pour les initialiseurs de variables d'instance late.
- Puisque les variables
latepeuvent être évaluées après la fin de la construction, leurs initialiseurs ne peuvent pas accéder de manière sûre aux paramètres du constructeur. - Tenter d'accéder à un paramètre du constructeur primaire dans un initialiseur de champ
laterésulte en une erreur de compilation.
3.3 Shadowing
Les paramètres du constructeur primaire occultent les membres de la classe (champs) du même nom dans le primary initializer scope :
- Dans un initialiseur non-late :
int y = xfait référence au paramètrex. - Dans un initialiseur
late:late int y = xfait référence au champx(s'il existe) car le paramètrexest hors de portée.
3.4 Restrictions du Constructeur Générative
Pour garantir que le constructeur primaire (et le primary initializer scope associé) s'exécute toujours :
- Une déclaration de classe, mixin class ou enum avec un constructeur primaire ne peut pas déclarer d'autres constructeurs génératives non-redirigeants (sauf les types d'extension).
- Tous les autres constructeurs génératives déclarés dans le corps doivent rediriger (directement ou indirectement) vers le constructeur primaire.
3.5 Erreurs de Mutation de Paramètre
Les paramètres du constructeur primaire ne peuvent pas être assignés pendant la phase d'initialisation.
- Toute assignation à un paramètre (par exemple,
p = value,p++) à l'intérieur des initialiseurs de champ ou de la liste d'initialiseursthis :est une erreur de compilation.
3.6 Erreurs d'Initialisation Double
Initialiser un champ deux fois (par exemple, une fois dans la déclaration/l'initialiseur de champ et une fois dans la liste d'initialiseurs this : ou comme un formel initialiseur) est une erreur de compilation.
4. Diagnostics & Dépannage
La plupart des erreurs et lints ont des correctifs rapides, exécutez dart fix pour corriger ces violations. Pour les autres erreurs courantes, corrigez-les en utilisant le tableau suivant :
| Code d'Erreur / Lint | Cause Courante | Résolution |
|---|---|---|
| Invalid Late Access | Référencer un paramètre du constructeur primaire dans un initialiseur de champ late. |
Rendez le champ non-late, ou passez la valeur via un autre champ non-late. |
fieldInitializedInInitializerAndDeclaration |
Initialiser une variable à la fois dans sa déclaration et dans la liste this :. |
Supprimez l'une des initialisations. |
nonRedirectingGenerativeConstructorWithPrimary |
Déclarer un constructeur générative in-body dans le corps sans rediriger vers le primaire. | Modifiez le constructeur in-body pour qu'il soit redirigeant (par exemple this(...)) ou supprimez le constructeur in-body. |
5. Workflows de Refactorisation Étape par Étape
Workflow 5.1 : Migration d'une Classe vers un Constructeur Primaire
Suivez ces étapes pour migrer une classe verbeux vers la nouvelle syntaxe de constructeur primaire :
-
Identifier les Champs Candidats et le Constructeur : Localisez les constructeurs génératives et les champs qu'ils initialisent. Dans ce cas, il s'agirait des champs
nameetage.// Avant class User { final String name; final int age; User(this.name, this.age); } -
Déplacer les Champs vers l'En-tête : Placez les champs dans l'en-tête avec les modificateurs
finalouvaret ajoutez un point-virgule (;) si le corps est vide. Les champsnameetagesont maintenant écrits dans le constructeur primaire comme paramètres déclarantsfinal String nameetfinal int age, respectivement.// Après class User(final String name, final int age); -
Traiter les Initialiseurs Personnalisés et les Assertions : S'il y a une liste d'initialiseurs ou un bloc assert, déplacez-le vers un bloc
thisdans le corps :// Avant class Point { final int x; final int y; Point(this.x, this.y) : assert(x >= 0); } // Après class Point(final int x, final int y) { this : assert(x >= 0); } -
Exploiter le Primary Initializer Scope pour les Calculs : Si une valeur de champ est calculée à partir de paramètres, déclarez-la dans le corps et assignez-la directement en utilisant les paramètres :
// Avant class Rect { final double width; final double height; final double area; Rect(this.width, this.height) : area = width * height; } // Après class Rect(final double width, final double height) { // 'width' et 'height' sont en portée ici final double area = width * height; } -
Convertir les Constructeurs In-Body en Redirigeants : Assurez-vous que tous les constructeurs génératives in-body redirigent vers le constructeur primaire :
// Avant class Point { final int x; final int y; Point(this.x, this.y); Point.zero() : x = 0, y = 0; } // Après class Point(final int x, final int y) { new zero() : this(0, 0); // Redirige vers le primaire }
Workflow 5.2 : Application de Constructeurs In-Body Concis Abrégés
Quand l'utilisateur préfère garder le constructeur dans le corps de la classe mais veut réduire la verbosité, suggérez la syntaxe de constructeur abrégée :
// Avant
class DatabaseService {
final String url;
DatabaseService(this.url);
DatabaseService.local() : url = 'localhost';
factory DatabaseService.create() => DatabaseService('default');
}
// Après
class DatabaseService {
final String url;
new(this.url); // Omettez le nom de la classe, utilisez 'new'
new local() : url = 'localhost'; // Utilisez 'new local' pour les constructeurs nommés
factory create() => DatabaseService('default'); // Omettez le nom de la classe de la factory
}