Что такое колбэк — и почему лишние скобки ломают кнопку

Смотри, забавная штука: ты уже писал колбэки, даже если слова такого не знал. Каждая кнопка, которую ты сделал с ИИ, — это колбэк.
И у него есть ловушка размером ровно в две скобки. onClick={save} работает. onClick={save()} ломает приложение — иногда насмерть, с бесконечной перерисовкой. Разберёмся, почему одна пара символов решает так много.
Что такое колбэк
Колбэк — это функция, которую ты передаёшь кому-то, чтобы он вызвал её сам, когда придёт время.
Обычно с функциями по-другому: ты сам решаешь, когда их запускать. save() — и она выполнилась немедленно. Но есть куча ситуаций, где момент выбираешь не ты:
- Пользователь нажмёт кнопку. Когда — неизвестно.
- Сервер ответит на запрос. Через 200 миллисекунд или через три секунды.
- Пройдёт таймер. Файл дочитается. Придёт сообщение.
Во всех этих случаях ты не можешь написать «вызови save вот здесь» — здесь ещё рано. Поэтому ты отдаёшь саму функцию: «вот инструкция, выполни её, когда случится событие».
Отсюда и название: call back — «перезвони мне». Ты оставляешь номер, а не висишь на линии.
Где ты его уже видел
Три места, которые встречаются в любом сгенерированном коде:
button.addEventListener("click", save)
setTimeout(showHint, 3000)
items.filter(isCheap)
В каждой строке функция передана по имени, без скобок. save, showHint, isCheap — это не вызовы, это передача.
Последний пример особенно полезный. filter — он сам обойдёт весь массив, но какие элементы считать подходящими, решаешь ты. Механизм обхода чужой, правило твоё — и передаётся оно колбэком.
Та самая пара скобок
Теперь про поломку. Разница между двумя записями фундаментальная:
save— это сама функция. Объект, который можно положить в переменную и отдать дальше.save()— это результат её выполнения. Функция уже отработала, а в руках у тебя то, что она вернула.
Поэтому в React-коде onClick={save} означает «вот функция, зови её при клике». А onClick={save()} означает «вызови save прямо сейчас, при отрисовке, и повесь на клик то, что она вернула» — обычно undefined.
Последствия бывают громкими. Если save меняет состояние компонента, то вызов при отрисовке запустит новую отрисовку, та — новый вызов, и приложение уходит в бесконечный круг. Так что если экран намертво завис после безобидной правки — первым делом ищи лишние скобки в обработчиках.
А если нужно передать аргумент? Тогда оборачивают в стрелочную функцию: onClick={() => save(item.id)}. Читается как «при клике выполни вот это» — и именно так ИИ пишет 90% обработчиков. Теперь ты знаешь, что это не магический синтаксис, а колбэк, созданный на месте.
Откуда берётся аргумент, который ты не передавал
Вопрос, который тормозит почти всех. Смотришь на код и не понимаешь, откуда взялось item:
const cheap = items.filter(item => item.price < 100)
Ты нигде не писал, чему равен item. Его подставляет тот, кто вызывает твой колбэк. filter идёт по списку и на каждом круге зовёт твою функцию, передавая ей очередной элемент. Ты только придумал имя переменной — назвал бы x, работало бы так же.
То же самое с событиями: button.addEventListener("click", (event) => ...). Аргумент event приходит от браузера, и внутри него — что нажали, где и когда. Правило простое: аргументы колбэка задаёт вызывающая сторона, ты их принимаешь. Отсюда и практический приём — если не помнишь, что тебе передают, выведи это: console.log(arguments) первой строкой покажет всё, что пришло.
Колбэки и асинхронность
Исторически колбэки были главным способом работать с асинхронностью: «сходи на сервер, а когда вернёшься — вызови вот эту функцию».
Проблема вылезала, когда шагов становилось несколько. Загрузить пользователя, потом его заказы, потом товары в заказе — каждый колбэк вкладывался в предыдущий, и код уезжал лесенкой вправо. У этого даже есть имя: callback hell, «ад колбэков».
Поэтому появились промисы и async/await — тот же смысл, но код читается сверху вниз. В новом коде ИИ почти всегда пишет await, а не вложенные колбэки. Но колбэки никуда не делись: обработчики событий, map, filter, setTimeout — всё это по-прежнему они. Просто плоские, без вложенности.
Колбэк — это то же самое, что стрелочная функция?
Нет, это разные вещи. Стрелочная функция — способ записи. Колбэк — роль: функция, которую передали, чтобы её вызвал кто-то другой. Стрелочными их пишут просто потому, что коротко.
Почему колбэк не сработал?
Три типовые причины. Ты вызвал функцию вместо передачи — лишние скобки. Опечатка в имени: onCLick вместо onClick — JavaScript промолчит, ошибки не будет. Или событие просто не дошло: элемент перекрыт другим, а клик ушёл в соседа. Проверяется быстро — поставь console.log первой строкой функции и посмотри логи: пусто значит не позвали, а не «внутри сломалось».
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





