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

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

Вопрос: А почему в разделе "Какие проблемы решают callback refs в коде" в первом примере вы говорите, что обычный useRef не работает? Там же:

function useResizeObserver(...) {
  useEffect(() => {
    ...

    return () => {
      resizeObserver.unobserve(element);
    };
  }, [...]);
}

Т.е. должно отписаться и все ок. Так же у вас есть: observer.disconnect(). Вы его и используете в примерах с коллбеками, так как там нет ссылки на элемент, когда его выключаете. Можно этот метод тут использовать.

Я к тому, что почему выше указанный пример не работает? Вроде бы все должно быть ок.

В целом техника нормальная. Не знал про нее. Благодарю за статью. Как минимум можно иногда избежать хранения ref переменной в компоненте.

Не будем тратить время на детальное объяснение того, что происходит в коде выше

Давайте все таки разберемся. Вы используете хук useRef, который создает объект с полем current на все время жизни компонента. И ссылка на объект останется неизменной. Это важно.

Далее в этот объект вы в зависимости от условия кидаете новые ссылки на DOM элементы.

Но в хук useResizeObserver вы все также передаете ту статичную ссылку на объект рефа. Это первый звоночек, который я заметил. И далее в хуке заметил, что useEffect тоже подписан на ту самую ссылку, которая статична.

Попробуйте не переписывая ничего заменить:
useResizeObserver({ elemRef, onResize: handleResize });
на
useResizeObserver({ elemRef: elemRef.current, onResize: handleResize });

Ну как же. В самом описании хука useEffect написано, что если значение в массиве зависимостей отлично от предыдущего рендера, то реакт дернет useEffect

Да, там нюанс, когда эта проверка происходит. Изменение ref не провоцирует рендер, соответственно хук не вызовется. А вот когда будет следующий рендер (если будет) тогда будет чек зависимостей и вызов хука с новым значением.

export function App(){
	const ref = useRef<any>(null);
	useEffect(() => {
		console.log('ref changed', ref.current);
		ref.current = { };
	}, [ref.current]);
	return <div ref={ref}/>;
}

В консоль 1 запись с дивом.
Даже если мы в кнопке будет задавать ref.current, не будет ни рендера ни эффекта.
Но если мы в кнопке вызовем useState и спровоцируем ререндер, эффект вызовется второй раз даже если не менять ref.current: при первом рендере там null, при следующих там {} (ну или div если фигней не страдать)

Я кажется понял вас.

Нет, я не хочу изменяя ref.current заставлять компоненту ререндерится.

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

Я бы предложил решать проблему все таки через ссылку и первую реализацию(якобы косячную), потому что она приятно и легко воспринимается.

Всего лишь вместо

const elemRef = useRef();

useResizeObserver({ elemRef, onResize: handleResize })

<div ref={ref}/>

Сделать

const [elemRef, setRef] = useState();

useResizeObserver({ elemRef, onResize: handleResize })

<div ref={setRef}/>

Тут "движок" реакта разберется когда сетнуть новую ссылку на элемент, а когда она осталась неизменной. Но все таки есть конечно слабые места, но пока до них не дошли, мне кажется, что первая "косячная" реализация хука более приятная, очевидная и менее запутанная

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации