hover
을 체크한다.
const useHover = () => {
const [state, setState] = useState(false);
const ref = useRef(null);
const handleMouseOver = useCallback(() => setState(true), []);
const handleMouseOut = useCallback(() => setState(false), []);
useEffect(() => {
const element = ref.current;
if (!element) {
return;
}
element.addEventListener("mouseover", handleMouseOver);
element.addEventListener("mouseout", handleMouseOut);
return () => {
element.removeEventListener("mouseover", handleMouseOver);
element.removeEventListener("mouseout", handleMouseOut);
};
}, [ref, handleMouseOut, handleMouseOver]);
return [ref, state];
};
useCallback
이 어떤 커스텀훅에서는 쓰이고 어떤 커스텀 훅에서는 쓰이지 않는다(대부분 쓰긴함). 왜 쓰이는걸까?
=> handleMouseOver
처럼 이벤트 핸들러는 자주 호출된다. 매 렌더링시 재생성되는 비효율을 막기위함인가?
스크롤시 일어나는 이벤트를 감지한다.
이 커스텀 훅에서 무려 requestAnimationFrame을 사용한다..!!
이벤트 루프시간에 배웠던 새로운 대기열!
//useScroll.js
const useScroll = () => {
const [state, setState] = useRafState({ x: 0, y: 0 });
const ref = useRef(null);
useEffect(() => {
const element = ref.current;
if (!element) {
return;
}
const handleScroll = () => {
setState({
x: ref.current.scrollLeft,
y: ref.current.scrollTop,
});
};
element.addEventListener("scroll", handleScroll, { passive: true });
return () => {
element.removeEventListener("scroll", handleScroll);
};
}, [ref]);
return [ref, state];
};
//useRafState.js
const useRafState = (initialState) => {
const frame = useRef(0);
const [state, setState] = useState(initialState);
const setRafState = useCallback((value) => {
cancelAnimationFrame(frame.current);
frame.current = requestAnimationFrame(() => {
setState(value);
});
}, []);
return [state, setRafState];
};
여기서 왜 useRef
에 숫자를 할당하여 사용할까?
=> debounce를 구현할때도 setTimeout에 변수를 할당해준 것 처럼, requestAnimationFrame특정한 Id를 할당한다. 이 Id를 가지고 애니메이션을 지운다. setInerval의 clearTimeout기능이라고 보면됨.
//useKey.js
const useKey = (event = "keydown", targetKey, handler) => {
const handleKey = useCallback(
({ key }) => {
if (key === targetKey) {
handler();
}
},
[targetKey, handler]
);
useEffect(() => {
window.addEventListener(event, handleKey);
return () => {
window.removeEventListener(event, handleKey);
};
}, [event, targetKey, handleKey]);
};
//useKeyPress
const useKeyPress = (targetKey) => {
const [keyPressed, setKeyPressed] = useState(false);
const handleKeyDown = useCallback(
({ key }) => {
if (key === targetKey) {
setKeyPressed(true);
}
},
[targetKey]
);
const handleKeyUp = useCallback(
({ key }) => {
if (key === targetKey) {
setKeyPressed(false);
}
},
[targetKey]
);
useEffect(() => {
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp);
};
});
return keyPressed;
};
이제 슬슬 커스텀훅 패턴이 보인다. 반복호출되는 함수들(이벤트 핸들러)등은 useCallback
으로 감싸고 사용할 상태나 상태지정메서드를 리턴해준다.
타겟 외부 부분 클릭했을때 이벤트 호출! 모달창 끌때 사용하면 편리할거같다.
const events = ["mousedown", "touchstart"];
const useClickAway = (handler) => {
const ref = useRef(null);
const savedHandler = useRef(handler);
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
const element = ref.current;
if (!element) {
return;
}
const handleEvent = (e) => {
!element.contains(e.target) && savedHandler.current(e);
};
for (const eventName of events) {
document.addEventListener(eventName, handleEvent);
}
return () => {
for (const eventName of events) {
document.removeEventListener(eventName, handleEvent);
}
};
}, [ref]);
return ref;
};
useEffect
가 2번쓰인이유는 ref
가변경됐을때와 handler
가 변경되었을때를 나눠준다.(useEffect의 의존성 배열)
handler
가 변경되었을때 이벤트를 document에 붙였다, 뗏다 하는 과정을 더 최적화 한 것임.
노드의 크기가 변경될때마다 값을 얻어오는 훅. 이미지를 컨테이너 높이에 딱 맞게 설정할때도 유용하다.
const useResize = (handler) => {
const savedHandler = useRef(handler);
const ref = useRef(null);
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
const element = ref.current;
if (!element) {
return;
}
const observer = new ResizeObserver((entries) => {
savedHandler.current(entries[0].contentRect);
});
observer.observe(element);
return () => {
observer.disconnect();
};
}, [ref]);
return ref;
};
ResizeObserver
는 특정 요소의 크기변화를 감지한다.
ref
로 취득한 노드를 observer.observe
에 전달함으로서 요소를 특정함.
강사님은 분리해서 만드셨지만, 이름만 다르기에 나는 공통 커스텀 훅인 useStorage
로 만들었다...!
const useStorage = (key, initialValue, storage = localStorage) => {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = storage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore =
typeof value === "function" ? value(storedValue) : value;
setStoredValue(valueToStore);
storage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
};
잘 작동하는구먼
커스텀훅을 만들며 느낀점은 함수형 프로그래밍을 빨리 배워야 할것 같다. 리액트 자체도 함수 컴포넌트고 훅도 함수고 vue의 compositionAPI도 함수고...세상 모든게 함수다!