Может ли чистая функция иметь побочные эффекты, например, писать в консоль?
Что проверяют
Знаете ли вы строгое определение чистоты и можете ли применить его к React, где от чистоты рендера зависит корректность. Следом обычно спрашивают, почему в режиме разработки console.log в компоненте срабатывает дважды и куда переносить побочные эффекты.
Как отвечать
Строго говоря, нет. Чистая функция удовлетворяет двум условиям: при одинаковых аргументах возвращает одинаковый результат и не производит наблюдаемых эффектов вовне. Вывод в консоль это ввод-вывод, то есть эффект, пусть и безобидный. Вызов с console.log уже нельзя заменить его результатом без изменения поведения программы, то есть теряется ссылочная прозрачность.
Оговорка: отладочный лог на практике терпят, на результат он не влияет, в Haskell для этого даже есть Debug.Trace. Но формально функция уже не чистая.
Что ещё делает функцию нечистой во фронтенде:
- внешнее изменяемое состояние: чтение глобальной переменной,
Date.now(),Math.random(),localStorage; - мутация аргументов:
arr.sort()иarr.reverse()меняют массив на месте, чистые аналоги этоtoSorted(),toReversed()или копия через spread; - сеть и DOM:
fetch, изменениеdocument.title, подписки.
Как это связано с React. Рендер компонента должен быть чистым: те же props, state и контекст дают тот же JSX. StrictMode в разработке намеренно вызывает дважды тело компонента, инициализаторы useState, функции-апдейтеры и колбэки useMemo, чтобы вы заметили нечистоту. Лог в теле компонента тоже появится дважды, DevTools показывает второй вызов приглушённым. Эффекты уходят в обработчики событий, синхронизация с внешним миром в useEffect, а редьюсеры useReducer и Redux обязаны быть чистыми полностью.
Где спотыкаются
Отвечают «да, лог же ничего не меняет». Нужно сначала определение через побочные эффекты, потом оговорка про отладку.
Считают функцию чистой, раз она не пишет в глобальные переменные, и не замечают arr.sort() на пропсах, который мутирует чужой массив и ломает сравнение по ссылке.
Воспринимают двойной вызов в StrictMode как баг React и отключают режим, вместо того чтобы вынести эффект из рендера.