본문으로 바로가기

useRef로 잡은 DOM이 화면에 보이도록 scroll을 하는 hook입니다.

 

일반 HTML에서는 <a> 태그를 이용하여 같은 페이지의 요소로 연결하는 Fragment를 사용할 수 있습니다.

darrengwon.tistory.com/227

 

그런데 React에서는 이게 잘 안 먹기도하고, 

* 왜 안먹히는가? => Component는 native html 태그가 아니기 때문입니다. 따라서 div로 한 번 묶어주면 작동은 합니다.

더 나아가 애니메이션적인 요소도 없이 단순 jump로 이동합니다.

따라서, useRef가 보일 때까지 smooth하게 스크롤을 당겨보기로 했습니다. scrollIntoView를 이용하면 되겠군요.

 

developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

 

Element.scrollIntoView() - Web APIs | MDN

The Element interface's scrollIntoView() method scrolls the element's parent container such that the element on which scrollIntoView() is called is visible to the userelement.scrollIntoView(); element.scrollIntoView(alignToTop); element.scrollIntoView(scro

developer.mozilla.org

 

개념 증명 및 문제점

 

최대한 단순하게 짜면 아래와 같이 사용할 수 있습니다.

ref로 잡고, 클릭하면 scrollIntoview로 이동시키는 것이 끝입니다.

 

* HTML이 아닌 Component에 직접 사용하면 작동하지 않습니다. <div>로 한번 wrapping 해주세요.

 

* class에서는 createRef, 함수형에서는 useRef를 사용한다고 알고 있지만 React 코드를 보면 createRef는 리렌더링될 때마다 ref 값이 null로 초기화됩니다. 그러니 useRef를 사용합시다.

 

function CourseSpecificBox({ courseInfomation }) {
  // 어디로 이동시킬지 DOM으로 잡기 위해 useRef
  const ReviewRef = useRef();

  // 클릭하면 이동시키는 로직
  const handleIndexClick = () =>
    ReviewRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'start',
    });

  return (
   <>
    // 이곳을 클릭하면
    <div className="index" onClick={handleIndexClick}>
      <div>Reviews</div>
    </div>
    
    // 여기로 이동시킨다.
    <div className="Review" ref={ReviewRef}>
      <Review courseInfomation={courseInfomation} />
     </div>
   </>
  );
}

export default CourseSpecificBox;

 

그러나 버튼이 많아진다면 똑같이 ref 달고, 이벤트 달고를 반복해야하는데 index가 3개만 되어도 이런 반복 작업을 3개나 해야 하는 겁니다.

 

ref만 3개를 잡아야 한다는 것... 똑같은 함수도 3개 생길 생각하니 현기증납니다.

// 이야 프로그래머 실격이다.
const LectureInfoRef = useRef();
const CurriculrumInfoRef = useRef();
const ReviewRef = useRef();

 

개선하기

 

조금 더 개선해보자면 다음과 같이 만들 수 있습니다.

 

* 주의할 점은, HTMLCollection은 배열이 아닌 유사 배열이기 때문에 forEach, map 등의 배열 메서드를 사용할 수 없다는 겁니다. 따라서, deconstructuring으로 배열로 만든 후에 map을 돌려야 합니다.

devsoyoung.github.io/posts/js-htmlcollection-nodelist

 

* IndexRef와 focusRef의 children의 갯수는 똑같아야 합니다. 로직을 보시면, IndexRef에 담긴 children 각각에 click 이벤트를 달았으며, 각 이벤트는 indexRef의 children과 대응되는 focusRef children으로 이동하는 식으로 짰기 때문입니다.

 

만약 indexRef와 focusRef의 갯수가 다르다? 아쉽지만 일일히 Ref를 달아주어야 합니다.

function CourseSpecificBox({ courseInfomation }) {
  const IndexRef = useRef();
  const focusRef = useRef();

  useEffect(() => {
    let nodes = [];

    const IndexClickEvent = (node, i) => {
      // removeEvent를 위해 외부 배열에 담아두자
      nodes.push(node);

      node.addEventListener('click', () => {
        focusRef.current.children[i].scrollIntoView({
          behavior: 'smooth',
          block: 'start',
        });
      });
    };

    if (IndexRef.current) {
      console.log(IndexRef.current.children);

      // HTMLCollection  유사 배열이기 때문에, 배열 메서드 중 일부를 지원 안함. forEach나 .map 사용 불가
      // 따라서 destucturing으로 배열에 다시 담아줬음
      [...IndexRef.current.children].map((node, i) => IndexClickEvent(node, i));
    }

    return nodes.map((node, i) => node.removeEventListener('click', IndexClickEvent(node, i)));
  }, []);

  return (
    <StyledCourseSpecificBox>
      {/* index */}
      <StyledCourseSpecificBoxIndex ref={IndexRef}>
        <div className="index">
          <div>Lecture Information</div>
        </div>
        <div className="index">
          <div>Curriculrum</div>
        </div>
        <div className="index">
          <div>Reviews</div>
        </div>
      </StyledCourseSpecificBoxIndex>

      {/* 실직적인 내용 */}
      <StyledCourseSpecificContents ref={focusRef}>
        <LectureInfo />
        <CurriculrumInfo />
        <Review courseInfomation={courseInfomation} />
      </StyledCourseSpecificContents>
    </StyledCourseSpecificBox>
  );
}

export default CourseSpecificBox;

 

 

 

* scrollIntoView를 React에서 사용할수 있도록 누가 package로 만들어 배포했는데

코드가 단순합니다. src/index.ts에 들어가보면 몇 줄 되지도 않습니다. 참고해보도록합시다.

(그리고 이런거 다운로드 받아서 사용하는 버릇들이면 좋지 않습니다. 직접 구현합시드...)

 

github.com/dominikbulaj/react-scroll-into-view

 

dominikbulaj/react-scroll-into-view

Declarative way for scrolling into view (any) page element. Tiny React utility component. - dominikbulaj/react-scroll-into-view

github.com

 

 

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체