В общих словах про ЯП Dart
Привет, Хабр!
История Darth Vader началась в 2011 году. Целью создания Dart было предложить альтернативу JavaScript, которая позволила бы создавать более сложные, высокопроизводительные веб-приложения, облегчая при этом процесс разработки.
Со временем Dart претерпел множество апдейтов, но его основной рывок произошел с запуском Flutter — фреймворка для создания нативных интерфейсов для мобильных, веб- и настольных приложений из единой кодовой базы.
Язык поддерживает ООП с классами и множественным наследованием, а также функциональные возможности, такие как высшие порядки функций, замыкания и асинхронность и т.п.
Система типов в Dart поддерживает как статическую типизацию, так и типизацию во время выполнения.
Основные возможности
Переменные и типы данных
var
используется для объявления переменной без явного указания её типа. Dart автоматически определяет тип переменной на основе присвоенного ей значения:
var name = 'Dart'; // dart понимает, что переменная name имеет тип string
var version = 2.12; // dart определяет, что переменная version` имеет тип double
Что касательно неизменяемых данных, Dart предлагает имеет две возможности: final
и const
.
final
используется для переменных, значение которых известно только во время выполнения программы и не может быть изменено после первого присвоения.const
применяется к переменным, значение которых известно во время компиляции и остаётся константным на протяжении всего времени выполнения.
final userName = 'otus';
const double PI = 3.14;
Dart поддерживает статическую типизацию:
String language = 'Dart';
int year = 2023;
double version = 2.10;
List developers = ['artem', 'ivan', 'igor'];
Map info = {
'name': 'Dart',
'created': 2008,
};
Dart также предлагаетList
, Set
, и Map
:
List: упорядоченная коллекция объектов одного типа.
Set: неупорядоченная коллекция уникальных объектов.
Map: коллекция пар ключ-значение, где каждый ключ встречается не более одного раза.
List primeNumbers = [2, 3, 5, 7, 11];
Set languages = {'Dart', 'Flutter', 'Java'};
Map headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer your_token',
};
Функции
Именованные параметры объявляются в фигурных скобках {}
:
void printUserInfo({String name, int age}) {
print('Name: $name, Age: $age');
}
void main() {
// вызов
printUserInfo(name: 'ivan', age: 30);
}
Dart поддерживает необязательные позиционные параметры с помощью []
:
void printMessage(String message, [String author = 'Anonymous']) {
print('$message — $author');
}
void printUserInfoOptional({String name = 'Unknown', int age}) {
print('Name: $name, Age: $age');
}
void main() {
// функция с необязательным позиционным параметром
printMessage('Hello, Dart!');
// функция с необязательным именованным параметром
printUserInfoOptional(age: 25);
}
Функции высшего порядка принимают другие функции в качестве аргументов или возвращают их:
void operateOnNumbers(int a, int b, Function(int, int) operation) {
var result = operation(a, b);
print('Result: $result');
}
int sum(int x, int y) => x + y;
int product(int x, int y) => x * y;
void main() {
// функции высшего порядка
operateOnNumbers(3, 4, sum); // Result: 7
operateOnNumbers(3, 4, product); // Result: 12
// use анонимной функции
operateOnNumbers(3, 4, (x, y) => x - y); // Result: -1
}
Классы и ООП
В Dart классы представляют собой средство для инкапсуляции и объединения данных и функционала:
class Car {
String make;
String model;
int year;
// конструктор
Car(this.make, this.model, this.year);
// именованный конструктор
Car.named({required this.make, required this.model, required this.year});
// метод для вывода данных автомобиля
void displayInfo() {
print('Make: $make, Model: $model, Year: $year');
}
}
void main() {
var myCar = Car('Toyota', 'Corolla', 2020);
myCar.displayInfo();
var mySecondCar = Car.named(make: 'Ford', model: 'Mustang', year: 1969);
mySecondCar.displayInfo();
}
Наследование позволяет классу наследовать свойства и методы другого класса, юзается слово extends
для наследования:
class ElectricCar extends Car {
double batteryCapacity;
ElectricCar(String make, String model, int year, this.batteryCapacity)
: super(make, model, year);
@override
void displayInfo() {
super.displayInfo();
print('Battery Capacity: $batteryCapacity kWh');
}
}
void main() {
var myElectricCar = ElectricCar('Tesla', 'Model Y', 2023, 85.0);
myElectricCar.displayInfo();
}
Абстрактные классы в Dart используются как базовые классы, которые не предполагается инстанцировать напрямую:
abstract class Shape {
void draw(); // абстрактный метод
}
class Circle extends Shape {
@override
void draw() {
print('Drawing circle...');
}
}
class Square extends Shape {
@override
void draw() {
print('Drawing square...');
}
}
void main() {
var circle = Circle();
circle.draw();
var square = Square();
square.draw();
}
В Dart нет специального синтаксиса для объявления интерфейсов. Любой класс может действовать как интерфейс, и другой класс может его реализовать с implements
:
class Vehicle {
void start() => print("Vehicle started");
void stop() => print("Vehicle stopped");
}
class Bike implements Vehicle {
@override
void start() => print("Bike started");
@override
void stop() => print("Bike stopped");
}
void main() {
var bike = Bike();
bike.start();
bike.stop();
}
Управляющие конструкции
Здесь все интуитивно.
Условные операторы if-else
позволяют выполнить определенный блок кода в зависимости от того, истинно ли условие:
int number = 10;
if (number % 2 == 0) {
print('$number is even');
} else {
print('$number is odd');
}
Циклы позволяют повторять выполнение блока кода, пока выполняется заданное условие:
for (int i = 0; i < 5; i++) {
print('i = $i');
}
while выполняет блок кода, пока условие истинно:
int num = 5;
while (num > 0) {
print('num = $num');
num--;
}
do-while: похож на while
, но гарантирует, что тело цикла выполнится хотя бы один раз:
int count = 0;
do {
print('count = $count');
count++;
} while (count < 5);
switch-case
позволяет выполнить различные блоки кода в зависимости от значения переменной:
String grade = 'B';
switch (grade) {
case 'A':
print('Excellent');
break;
case 'B':
print('Good');
break;
case 'C':
print('Fair');
break;
case 'D':
case 'F':
print('Poor');
break;
default:
print('Invalid grade');
}
Можно генерировать исключения с помощью throw
и перехватывать их с помощью try-catch
:
void checkAge(int age) {
if (age < 18) {
throw Exception('Not eligible for voting');
} else {
print('Eligible for voting');
}
}
try {
checkAge(16);
} catch (e) {
print('Caught an exception: $e');
} finally {
print('This is always executed');
}
null safety
В Dart с включённым null safety переменные по умолчанию не могут быть null:
int a = null; // err
Чтобы объявить переменную, которая может быть null, нужно использовать знак вопроса после типа переменной:
int? a = null; // ОК
Когда переменная может быть null, Dart требует от учитывать эту возможность перед тем, как использовать её значение:
int? a;
if (a != null) {
print(a + 2); // ОК, потому что мы проверили, что a не null
}
Оператор !
говорит компилятору: «Я уверен, что эта переменная не null». Его нужно юзать с большой осторожностью:
int? a = null;
print(a! + 2); // исключение во время выполнения
Операторы ??=
и ??
позволяют работать с переменными, которые могут быть null, присваивая им значения по умолчанию.
??=
присваивает значение переменной, если она null.??
возвращает значение справа от себя, если значение слева от него — null.
int? a;
a ??= 10; // a будет равно 10, потому что до присваивания a было null
print(a); // 10
int? b = null;
print(b ?? 20); // 20, потому что b равно null
Dart с Flutter
В Flutter есть два основных типа виджетов: StatefulWidget
и StatelessWidget
. StatelessWidget
используется для виджетов, которые не изменяют свое состояние во время выполнения, а StatefulWidget
используется для виджетов, которые могут изменять свое состояние в ответ на взаимодействия пользователя или другие факторы
StatelessWidget:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Пример StatelessWidget'),
),
body: Center(
child: Text('Привет, Хабр!'),
),
),
);
}
}
StatefulWidget:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Пример StatefulWidget'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Вы нажали на кнопку столько раз:'),
Text('$_counter', style: Theme.of(context).textTheme.headline4),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
),
);
}
}
Flutter облегчает работу с асинхронными операциями через использование FutureBuilder
:
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final Future _calculation = Future.delayed(
Duration(seconds: 2),
() => 'Данные загружены',
);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Пример FutureBuilder'),
),
body: Center(
child: FutureBuilder(
future: _calculation,
builder: (BuildContext context, AsyncSnapshot snapshot) {
List children;
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
children = [
Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
),
];
} else {
children = [
Icon(
Icons.check_circle_outline,
color: Colors.green,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Result: ${snapshot.data}'),
),
];
}
} else {
children = [
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
const Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
)
];
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
);
},
),
),
),
);
}
}
Передача данных между экранами обычно осуществляется через конструкторы и Navigator API:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Передача данных между экранами',
home: FirstScreen(),
);
}
}
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Первый экран'),
),
body: Center(
child: ElevatedButton(
child: Text('Перейти ко второму экрану'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(data: 'Привет от первого экрана'),
),
);
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
final String data;
SecondScreen({Key key, @required this.data}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Второй экран'),
),
body: Center(
child: Text(data),
),
);
}
}
Напомню, что в рамках онлайн-курсов OTUS вы можете изучить самые популярные ЯП, а также зарегистрироваться на ряд бесплатных мероприятий.