투두리스트에 스크롤이 생겼을 때 할 일을 추가하면 추가한 일로 스크롤이 이동하여 사용자가 방금 추가한 할 일을 바로 확인할 수 있도록 하고 싶었다.
1. useEffect 사용
처음엔 useLayoutEffect() hook 으로 '새로 추가된 아이템이 반영된 리스트의 높이를 미리 계산해야 하지 않을까' 했지만, useEffect() hook 만 사용해도 문제없이 잘 동작했기 때문에, useEffect() hook 을 사용하였다.
할 일이 추가되거나, 삭제될 때 동작이 달라야 하므로 prop으로 내려받고 있는 data의 길이를 dependency로 설정하였다.
2. useRef 사용
사용자가 할 일을 추가하거나 삭제할 때 할 일을 렌더링하고 있는 리스트의 길이가 변화하므로, useRef로 해당 DOM에 접근할 수 있도록 연결해 주었다.
3. elem.scrollTop 프로퍼티 조작
🔗 https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop
- scrollTop : 세로 스크롤바에 가려져 보이지 않는 '위쪽' 컨텐츠의 높이
- scrollHeight : 세로 스크롤바에 가려진 부분을 포함하는 콘텐츠 영역 안쪽 전체의 높이
스크롤의 가장 최하단에 스크롤을 위치 시키기 위해, scrollTop을 scrollHeight과 동일하도록 맞춰주었다.
구현
코드
export default function TodoList({ data }) {
const [listHeight, setListHeight] = useState(0); // 이전 렌더링의 list 값을 기억할 변수를 state로 관리
const listRef = useRef(null);
useEffect(() => {
if (listRef.current) {
const height = listRef.current.scrollHeight;
// data가 추가될 때만 스크롤 이동하도록
if (listHeight < height) {
listRef.current.scrollTop = listRef.current.scrollHeight;
}
return setListHeight(height);
}
}, [data.length]);
return (
<ul className="TodoList" ref={listRef}>
Todos
</ul>
)
}
가장 간단하게 단순히 스크롤을 최하단으로 이동시키는 것으로 구현하긴 했지만, 다른 방법으로도 구현할 수 있을 것 같다. 추가되는 요소가 리스트의 가장 마지막 요소일 테니 해당 요소에 ref를 연결해서 화면에 보이도록 elem.scrollIntoView() 메서드를 활용해도 될 것 같다는 생각이 들었다. 이외에도 좀더 요소에 집중해서 해당 요소를 기준으로 스크롤을 조작하는 방법이 있을 것 같기는 한데 좀더 공부가 필요할 것 같다.
참고문서