Как стать автором
Обновить

Комментарии 19

В useAsync забыли про то что компонент может размонтироваться во время операции:


try {
  setLoading(true);
  const response = await asyncFunction(...params);
  // вот тут компонент может уже не существовать
  setResult(response);
} catch (e) {
  setError(e);
}

В библиотеке, на которую ссылается автор этот момент уже предусмотрен.


В хуке useDebounce тоже заложили грабли. Вот такой код работать не будет:


const debouncedAPICall = useDebounce(() => console.log('called'), 500);

У автора на эту тему есть оговорка, что функция не должна пересоздаваться, но на самом деле это можно поправить внутри хука, и не заморачивать пользователя этими деталями


function useDebounce(func, delay) {
  const latestFunRef = useRef();
  useEffect(() => {
    latestFunRef.current = func;
  })

  return useMemo(() => {
      return debounceAction((...args) => latestFunRef.current(...args), delay)
  }, [])
}

Таким образом и получается, то даже в простых штуках есть неочевидные моменты, о которых нужно знать.

Зачем в useDebounce используется useEffect? Потому что так написано в документации React в примере с useEventCallback? Это вызывало проблемы в моём проекте, я пытался придумать пример, в котором useEffect необходим, но не смог. Проще и надёжнее оказалось писать в ref незамедлительно:


function useDebounce(func, delay) {
  const latestFunRef = useRef();
  latestFunRef.current = func;

  return useMemo(() => {
      return debounceAction((...args) => latestFunRef.current(...args), delay)
  }, [delay])
}

Во-первых, если так написано в документации, то это неспроста. Команде реакта виднее.
Во-вторых, могу сходу придумать два варианта, чем это может быть полезно – 1) этот код не вызывается на сервере 2) это нужно для ConcurrentMode, где рендер может вызываться много раз


А какие проблемы это вызывает?

Во-первых, если так написано в документации, то это неспроста. Команде реакта виднее.
Ситуации применения могут отличаться. Или best practices изменились, а документации не обновилась. И т.п.

А какие проблемы это вызывает?
В данном случае (для использования debounceAction) ошибки вроде не будет, но в других ситуациях использования useRef в useEffect и в useMemo, вариант с useEffect может быть некорректен, т.к. useEffect вызывается после useMemo из-за чего ref не успеет обновится и useMemo выполнится с предыдущим переданным значением.

useCallback и useEventCallback — это отпимизация простой записи функции в переменную. То есть, следующие примеры должны быть эквивалентны с точки зрения логики работы компонента:


const handle = () => console.log(foo);
const handle = useCallback(() => console.log(foo), [foo]);
const handle = useEventCallback(() => console.log(foo), [foo]);

Но на самом деле useEventCallback (предлагаемый в документации) работает не так, как другие варианты.


Во-первых, если так написано в документации, то это неспроста. Команде реакта виднее.

Они позиционируют это как антипример, поэтому отношение соответствующее.


этот код не вызывается на сервере

Та функция будет объявлена в обоих случаях и не будет вызвана тоже в обоих случаях.


это нужно для ConcurrentMode, где рендер может вызываться много раз

Запись в ref проще для JS-интерпретатора, чем вызов функции useEffect. Сайд эффекты не производятся. Поэтому не вижу проблемы с точки зрения множественного рендера.


А какие проблемы это вызывает?

strannik_k привёл хороший пример. Когда функция, которую возвращает useEventCallback вызывается сразу же в процессе рендера (например, в ответ на изменение одного из пропов), получается баг.

Не модифицируйте ref-ы во время render-а. Они предполагаются быть чистыми (pure functions). В противном случае вы можете столкнуться с гхм… со странностями.


Например при активной вкладке с react dev tools. Он (пока вы там кликаете по компонентам) рендерит их с ненастоящими хуками (и таким образом строит древо используемых хуков). По итогу получается что если вы, скажем, сохранили в ref.current ссылку на какой-нибудь, скажем, setValue из useState, то теперь у вас там просто пустая болванка из-за react-dev-tools. И когда этот ref.current() где-нибудь будет вызван, то ничего не произойдёт (там () => {}). А вы будете ломать голову — в чём же баг?!


Полагаю это не единственный возможный случай. И судя по валидатору от React-а они это пишут в документации не с проста. У них там всякие грандиозные планы (вроде рендера в пустоту). Что-нибудь потом да сломается.


В общем используйте useLayoutEffect

Аргумент про то, что render должен быть чистой функцией, звучит убедительно. Можно, пожалуйста, какой-нибудь конкретный пример с записью в ref во время рендера, который можно запустить и сломать с помощью React dev tools?

Вот тут мы это обсуждали. Там же в обсуждениях и ссылка на issue на github-е с разъяснениями.

А вот, к примеру, МобХ делает свои точечные подписки как раз во время рендера. Тут есть проблема? Или он как-то "коммитится" в useLayoutEffect?

Честно говоря понятия не имею. Думаю проще всего — просто открыть исходники mobX и посмотреть. Плюс стоит учесть, что раз мы говорим про хуки, то мы говорим про функциональные компоненты, а не про классы. У них своя специфика. Полагаю к рендеру классовых компонент меньше требований.

Прочитал про первый хук и дальше уже читать не стал. Это на уровне старого мема про подключение жуквери для суммирования двух переменных. То есть это на столько простая реализация, что вообще не понятно как она может потянуть на лишнюю зависимость. Но с модалками очень много тонкостей, включая скрытие скролла у документа, закрытие окна по клику в любой части документа за пределами модалки (если она не на весь экран) и т.д. и т.п. Вот если бы хоть часть из этих моментов перекрывалась, то еще имело смысл. А так? Ну такое…
Хочется узнать мнение разработчиков React. Почему так популярны хуки? Для меня хуки напоминают callback hell. С этим боролись в свое время как могли. Для примера rxjs, async await. Код плохо воспринимается, присутствует ощущение неявного и косвенного взаимодействия.

Комнада реакта писала про мотивацию. Хуки пришли на замену render props и частично HOC, или ещё более древним миксинам. Они решают следующие проблемы:
Композиция — в хуки легко прокинуть данные, получить из них данные и засунуть из в другой хук. https://github.com/acdlite/recompose для сранения, как было до хуков.
Легко использовать несколько инстансов одного хука.
Нету конфликтов, когда засоряется общее пространство имен в props.
Плюс ещё функции лучше минифицируются, чем классы.
Всё это хорошо работает с typescript, в отличие от того, что было.
И ещё что-то там про то, что новички испытывали проблемы с классами и this.


Если подытожыть — то это лучший на текущий момент подход.

Хуки дают возможность добавлять функциональность в компонент одной строчкой кода. Изначально это делали миксинами, но быстро от этого отошли по ряду причин. Еще пытались такое делать через high-order components, но с ними тоже есть проблемы. Вот теперь появлились хуки, они работают лучше чем предыдущие варианты, пусть с ними тоже не все идеально. Просто лучший вариант из существующих.

Хуки позволяют очень здорово вычленять и переиспользовать куски логики компонентов. С хуками это делается намного элегантнее, чем с классовыми компонентами. По моему опыту в среднем на хуках писать компоненты проще.

К вышеперечисленным бонусам хуков хочу ещё отдельно добавить про хук useContext. Старое context API невозможно вспоминать без содрогания.

Довольно легко создавать свои хуки. Почти так же легко, как и писать свои функции. Только допустить неприметные ошибки в них тоже легко.

Команда реакта теперь продвигает функциональные компоненты и не развивает компоненты-классы.

Хуки — первое и единственное распространенное решение для React в котором применили подобие паттерна стратегия. А композиция с этим паттерном лучше подходит для сущностей вроде react-компонентов, чем композиция с использованием декораторов (HOC) или чем миксины, наследование.
В useTrackErrors Eslint показывает ошибку повторного объявления
const setErrors

возможно автор подразумевал это
export const useTrackErrors = () => {
	const [errors, _setErrors] = useState({});

	const _setErrors = (errsArray) => {
		const newErrors = { ...errors };
		errsArray.forEach(({ key, value }) => {
			newErrors[key] = value;
		});

		_setErrors(newErrors);
	};

	const clearErrors = () => {
		_setErrors({});
	};

	return { errors, setErrors, clearErrors };
};
* fixed
export const useTrackErrors = () => {
	const [errors, _setErrors] = useState({});

	const setErrors = (errsArray) => {
		const newErrors = { ...errors };
		errsArray.forEach(({ key, value }) => {
			newErrors[key] = value;
		});

		_setErrors(newErrors);
	};

	const clearErrors = () => {
		_setErrors({});
	};

	return { errors, setErrors, clearErrors };
};
Зарегистрируйтесь на Хабре, чтобы оставить комментарий