Функции, стрелочный синтаксис и параметры

Функция — это переиспользуемый кусок логики; в Dart у неё гибкие параметры и компактная запись.

Суть: функции принимают параметры (позиционные, опциональные [ ] или именованные { }), возвращают значение и могут быть записаны коротко через стрелку =>.

Функции в Dart — это ещё и полноценные значения, которые можно передавать как аргументы. Когда вы пишете обработчик нажатия onPressed: () { ... }, вы передаёте функцию внутрь другой функции. Такие анонимные функции называют замыканиями: они запоминают переменные из окружающего кода. Эта идея — функция как параметр — пронизывает весь Flutter, ведь почти каждый интерактивный виджет принимает колбэк, который вызовет в нужный момент.

Без функций код превращается в одну длинную простыню, которую невозможно читать и переиспользовать. Функция упаковывает действие под именем: один раз описали — много раз вызвали. В Dart функция объявляется так: тип возвращаемого значения, имя, скобки с параметрами и тело в фигурных скобках.

int add(int a, int b) {
  return a + b;
}

// то же самое короче — стрелочный синтаксис:
int addShort(int a, int b) => a + b;

void greet(String name) {
  print('Привет, $name!');
}

Стрелка => — это сокращение для функций, тело которых состоит из одного выражения. => a + b означает { return a + b; }. В Flutter вы будете встречать стрелки на каждом шагу, особенно в обработчиках нажатий.

Как работают параметры под капотом

У Dart три вида параметров, и это его сильная сторона. Позиционные передаются по порядку. Опциональные позиционные оборачиваются в квадратные скобки [ ] и могут быть пропущены. Именованные оборачиваются в фигурные { } и передаются по имени — именно их использует почти каждый виджет Flutter.

  void build(child, {color, padding})
                |          |
       обязательный    именованные (по имени, в любом порядке)
                       |
       вызов:  build(text, color: blue, padding: 8)
                                |
              можно поменять местами: padding: 8, color: blue
// именованные параметры с значением по умолчанию и required
void createButton({
  required String label,   // обязателен, хоть и именованный
  Color color = Colors.blue,
  double width = 100,
}) {
  // ...
}

createButton(label: 'OK');                  // color и width — дефолтные
createButton(label: 'OK', color: Colors.red);

Ключевое слово required делает именованный параметр обязательным: компилятор не даст вызвать функцию без него. Это и есть причина, по которой код Flutter так читаем — у каждого аргумента есть подпись.

Запустим Python-аналог именованных параметров со значениями по умолчанию — логика идентична Dart:

# Аналог именованных параметров Dart с дефолтами и обязательным аргументом
def create_button(label, color='blue', width=100):
    return f"Кнопка [{label}] цвет={color} ширина={width}"

print(create_button('OK'))                      # дефолты
print(create_button('Стоп', color='red'))       # переопределили цвет
print(create_button('Шире', width=200, color='green'))  # порядок любой

Частые ошибки

  • Забыть required у обязательного именованного параметра — тогда его можно не передать, и значение окажется null.
  • Путать [ ] и { }. Квадратные — опциональные позиционные, фигурные — именованные. Это разные механизмы.
  • Возвращать значение из void-функции. void означает «ничего не возвращаю».

Best practices

  • Для функций с двумя и более параметрами предпочитайте именованные — вызов становится самодокументируемым.
  • Однострочные функции пишите через => — короче и чище.
  • Давайте функциям имена-глаголы: calculateTotal, fetchUser, а не data.

Ещё одна деталь, которая сэкономит вам часы отладки: порядок и обязательность параметров. Позиционные аргументы нельзя пропустить в середине — если второй опциональный нужен, придётся указать и первый. Именованные же свободны от этого ограничения, и потому виджеты Flutter с их десятком настроек используют именно их. Привыкайте читать сигнатуру функции как список возможностей: что обязательно, что имеет умолчание, а что можно опустить.

Итог: функции — это кирпичики логики. Освойте именованные параметры с required и значениями по умолчанию — на них держится весь API Flutter, и без них код виджетов покажется набором случайных скобок.

Проверьте себя
1. Что означает стрелочный синтаксис int f(int x) => x * 2;?
AФункция ничего не возвращает
BСокращение для тела { return x * 2; }
CФункция вызывается асинхронно
Dx — необязательный параметр
2. Зачем нужно ключевое слово required перед именованным параметром?
AЧтобы задать значение по умолчанию
BЧтобы сделать параметр позиционным
CЧтобы компилятор требовал обязательно передать этот аргумент
DЧтобы параметр стал null