Novos recursos no Dart 3.0

Tempo de leitura: 5 minutes

Neste artigo, escreverei sobre novos recursos impressionantes na linguagem Dart.

 

Patterns

De acordo com a documentação:

Em geral, um pattern pode corresponder a um valor, desestruturar um valor ou ambos, dependendo do contexto e da forma do padrão.

O que podemos fazer com Patterns? Vamos nos aprofundar nos exemplos.

1. Podemos obter elementos da lista:

void main() {
  final list = [10, 20, 30, 40, 50];
  final [a, b, c, d, e]  = list;
   print('$a $b $c $d $e'); // ele imprimirá 10 20 30 40 50
 }

2. Podemos escrever patterns mais complexos e casos destruidores no switch:

/// este exemplo primeiro verifica se o primeiro elemento da lista é a ou b
/// então ele destrói o segundo elemento da lista
switch (list) {
  case ['a' || 'b', var c]:
    print(c);
}

void main() {
  check(1); // it is equal to 1
  check(7); // it is between 5 and 10
  check(20); // it is 20
}


void check(int a) {
  switch(a) {
    case 1:
      print('it is equal to 1');
      break;
    case >= 5 && <= 10:
      print('it is between 5 and 10');
      break;
    default:
      print('it is $a');
  }
}

3. Podemos validar o json de forma mais legível:

void main() {
 final json = <String, dynamic>{
   'name': 'Kanan',
   'age': 24,
 };

  final (name, age) = getNameAndAge(json);
  print('$name: $age');
}

(String, int) getNameAndAge(Map<String, dynamic> json) {
  if(json case {'name': 'Kanan', 'age': 24}) {
     return (json['name'], json['age']);
  }

  throw Exception('it is not valid!');
}

4. Podemos usar padrões em loops for e for-in:

void main() {
  final personList = <Person>[
    Person(name: 'Kanan', age: 24),
    Person(name: 'Zahra', age: 23)
  ];

  for(var Person(name: name, age: age) in personList) {
    print('name: $name, age: $age');
  }

  /// it will print
  // name: Kanan, age: 24
  // name: Zahra, age: 23
}


class Person {
  const Person({
    required this.name,
    required this.age,
  });

  final String name;
  final int age;
}

Para obter mais informações, consulte os documentos.

 

Records (podemos dizer tuplas)

Em alguns casos, podemos encontrar o caso em que queremos retornar vários resultados do método ou queremos analisar o JSON para apenas dois ou mais campos. Nesses casos, podemos usar registros. Vejamos os exemplos a seguir.

Podemos simplesmente definir tuplas com parâmetros opcionais ou nomeados e acessar com seu nome ou $index+1 caminho.

void main() {
  var person = (name: 'Kanan', surname: 'Yusubov', age: 24);
  var person2 = ('Kanan', 'Yusubov', 24);

  print(person.age);
  print(person.surname);
  print(person2.$2);
}

Podemos usar registros e parâmetros de função e valores de retorno:

void main() {
 final swappedValues = swap((10, 20));
 print('${swappedValues.$1} e ${swappedValues.$2}');
}

(int, int) swap((int, int) values) {
 final (a, b) = values;
 return (b, a);
}

Podemos usar registros e parâmetros de função e valores de retorno:

void main() {
 final swappedValues = swap((a: 10, b: 20));
 print('${swappedValues.first} e ${swappedValues.second}');
}

(int first, int second) swap((int a, int b) values) {
 return (first: values.b, second: values.a);
}

Você pode usá-lo para analisar alguns membros do JSON:

void main() {
 final json = <String, dynamic>{
   'name': 'Kanan',
   'surname': 'Yusubov',
   'age': 24,
   'livesIn': 'Baku',
   'worksAt': ' Nasimi Bazari'
 };

  final (name, age) = getNameAndAge(json);
  print('$name: $age');
}

(String, int) getNameAndAge(Map<String, dynamic> json) {
  return (json['name'], json['age']);
}

Para mais informações, verifique os documentos.

 

Modificadores de classe

Final modificador
Usando esta palavra-chave na declaração da classe, garantirá:

  • Outras classes não podem estender ou implementar sua classe
  • Outras classes não podem usar esta classe como um mixin

Observe o seguinte exemplo:

final class A {}

class B extends A {}
class C implements A {}
mixin D on A {}
class E with A {}

Todos os exemplos acima darão o erro de sintaxe. Ele garante que:

  • Você pode adicionar com segurança novas alterações à sua biblioteca e API com mais segurança
  • Você pode usar a classe final de qualquer outra biblioteca com segurança porque garante que ela não pode ser estendida ou implementada em nenhuma outra biblioteca.

Mas lembre-se de que você pode estender e implementar sua classe final na mesma biblioteca. Para isso, você pode definir a classe do subtipo como base, final ou sealed.

final class A {}
final class B extends A {}
base class C implements A {}
sealed class D extends A {}

 

Modificador de interface

Se você desenvolver a nova biblioteca, pacote e outras coisas, dentro da mesma biblioteca, podemos usar a classe de interface estendida e implementada. Mas se usarmos essa biblioteca em outra biblioteca, a classe de interface nos permitirá apenas implementá-la.

Então, usando este modificador:

  • Sua biblioteca chamará com segurança a mesma implementação de métodos de instância dentro da mesma biblioteca.
  • Outras bibliotecas não podem modificar a definição de classe base para seus métodos de instância. Isso reduzirá o risco de problema de classe base frágil.
// Library a.dart
interface class A {
  void greet() { ... }
}

// Library b.dart
import 'a.dart';

var instanceOfA = A();       // Can be constructed

class B extends A {  // ERROR: Cannot be inherited
    int instanceMember;
    // ...
}

class C implements A {  // Can be implemented     
    @override
    void greet() { ... }
}

 

Modificador base

O modificador de base garante:

  • O construtor da classe base é chamado sempre que uma instância de um subtipo da classe é criada.
  • Todos os membros privados implementados existem em subtipos.
  • Um membro implementado recentemente em uma classe base não quebra os subtipos, pois todos os subtipos herdam o novo membro.
  • Isso é verdadeiro, a menos que o subtipo já declare um membro com o mesmo nome e uma assinatura incompatível.

Vamos explorar o seguinte exemplo:

base class Person {
  void greet() {
    print('Hi!');
  }
}


class Programmer extends Person {

}

Vai dar o erro como o seguinte:

O tipo ‘Programmer’ deve ser ‘base’, ‘final’ ou ‘sealed’ porque o supertipo ‘Pessoa’ é ‘base’.

O modificador base funciona como uma versão reversa da palavra-chave interface. Você só pode estender sua classe base em outra biblioteca. Mas lembre-se de que, se você deseja estender a classe base, sua subclasse deve ser definida como uma classe base ou final.

 

Modificador Sealed

Ele nos permite criar um conjunto enumerável de subtipos. Usando o novo recurso de verificação exaustiva Podemos usar classes de subtipo em switch e expressões switch em qualquer lugar com sintaxe mais amigável.

O modificador sealed impede que uma classe seja estendida ou implementada fora de sua biblioteca.

As classes Sealed não podem ser instanciadas, mas podem conter construtores de fábrica. Portanto, é implicitamente abstrato.

void main() {
  final developer = Developer();
  final programmer = Programmer();

  print(getTitle(developer));
  print(getTitle(programmer));
}

String getTitle(Person p) {
  return switch(p) {
      Developer() => 'Developer',
      Programmer() => 'Programmer',
  };
}

sealed class Person {}
class Developer extends Person {}
class Programmer extends Person {}

Você pode usar classes sealed para lidar com diferentes estados em soluções de gerenciamento de estado (como Bloc, Riverpod, etc) de uma forma mais legível:

void main() {
  final initial = Initial();
  final inProgress = InProgress();
  final failure = Failure('something went wrong!');
  final success = Success(10);

  print(handleState(initial));
  print(handleState(inProgress));
  print(handleState(failure));
  print(handleState(success));
}

String handleState(DataState state) {
  return switch(state) {
      Initial() => 'this is initial state',
      InProgress() => 'this is in progress state',
      Failure(message: var m) => 'error occured: $m',
      Success(data: var s) => 'success with $s',
  };
}

sealed class DataState {}

class Initial extends DataState {}
class InProgress extends DataState {}
class Failure extends DataState {
  Failure([this.message]);
  final String? message;
}
class Success extends DataState {
  Success(this.data);

  final int data;
}

 

 

Combinando modificadores

Você pode seguir a seguinte ordem para adicionar modificadores de classe:

  • em primeiro lugar, você pode adicionar a palavra-chave abstract, mas é opcional, permitirá que a classe adicione alguns membros abstratos e bloqueará a classe para instanciar.
  • Em seguida, você pode adicionar uma das seguintes palavras-chave interface, final, base e sealed para adicionar mais regras para subclasses em outras bibliotecas. (também é opcional)
  • Pela ordem, você pode adicionar uma palavra-chave mixin para adicionar a capacidade de outras classes se misturarem. (opcional)
  • e é necessário adicionar a própria palavra-chave class.
Para alguns tipos, você não pode combinar. Tenha em mente que:
  • Você não pode combinar classes sealed e abstract, porque a classe sealed é implicitamente abstrata.
  • Você não pode usar mixin com final, interface e sealed, porque eles não podem permitir que outras bibliotecas os usem como mixin.

 

Espero que seja um artigo informativo para você.