react) SCROLL-SPY 구현 multiple Ref + typescript + io

김명성·2022년 7월 25일
1

App.tsx

import {useEffect, useRef, useState } from 'react';
import Content from './Content';
import Navigate from './Navigate';

const pages = Array.from({length:8}).map((_,i) => i + 1);

const App = () => {
  // 현재 보고 있는 page를 알 수 있게 해주는 viewIndex
  const [viewIndex,setViewIndex] = useState(0)
  
  // 관찰 대상은 Div elements이기에 배열로 타입 지정
  // ref를 사용한 이유는 observing에 사용하기 위함이다.
  const pageRef = useRef<HTMLDivElement[] | null>([])

  
  //scrollIntoView는 js에 내장된 element 인터페이스의 method다.
  // 해당 element로 이동하게 만들어준다.
  // navigation에서 사용할 함수로,
  // index를 받아 원하는 페이지로 이동하게 한다.
  // 인자를 받기 위해 고차함수로 적용.
  const moveToTargetPage = (index:number) => () => {
    if(pageRef.current){
      pageRef.current[index].scrollIntoView({
        block:'start',
        behavior:"smooth",
        inline:'center'
      })
    }
  }
 

  const options:IntersectionObserverInit = {
    root: null,
    threshold:0.5
  }
  const callback:IntersectionObserverCallback = (entries:IntersectionObserverEntry[],observer) => {
   entries.forEach((entry) => {
    const {isIntersecting,boundingClientRect} = entry;
    // pageYOffset 문서가 수직으로 얼마나 스크롤 됐는지 픽셀 단위로 반환한다.
    const scrollTop = window.pageYOffset;
    // 이벤트가 발생된 target의 height
    const {height} = boundingClientRect;
    
     // 관찰 요소와 교차될때마다 index를 계산하여 viewIndex의 값을 바꿔준다.
    if(isIntersecting) {
      const index = Math.round(scrollTop / height);
      setViewIndex(index);
    }
   })
  }
  
  const io = new IntersectionObserver(callback,options);

  useEffect(() => {
    // 첫 랜더링때에만 io를 부착한다
    pageRef.current!.forEach((item) => io.observe(item))
    
    // clean-up function. componentDidMount 이후 실행된다.
    return () => {
      pageRef.current!.forEach((item) => io.unobserve(item))
    }
  },[])
  
  
  return (
  <div>
    <Navigate
	  pages={pages}
      viewIndex={viewIndex}
      moveToTargetPage={moveToTargetPage} />  
    {pages.map((page,index) =>
	<Content
      key={index}
      page={page}
      // type narrowing
      ref={(r) =>{ if(r && pageRef.current)
                   return pageRef.current[index] = r}} />)}
  </div>
  )};

export default App;

navigation 내부의 버튼을 클릭할 때마다moveToTargetPage 함수가 실행되어 해당 element로 이동하게 만들어 준다

import React from 'react';

interface NavigateProps {
  moveToTargetPage: (index: number) => () => void
  viewIndex: number;
  pages: number[]
}

const Navigate = ({moveToTargetPage,viewIndex,pages}:NavigateProps) => {
  return (
    <nav className='fixed left-0 right-0 top-0 p-0 m-0 bg-blue-300 overflow-hidden'>
      <ul className='h-16 w-full flex flex-row justify-center items-center p-1'>
      {pages.map((page,index) => <li className={`relative flex-1 text-center py-2 rounded-lg
      ${viewIndex === index ? "bg-white text-rose-500 font-bold text-xl" : ""}
      `}><button className='w-full h-full' onClick={moveToTargetPage(index)}>{page}</button></li> )}
      </ul>
    </nav>
  );
};

export default Navigate;

Content.tsx

  • 단순한 view container

import React,{ComponentPropsWithRef, forwardRef} from 'react';

interface ContentProps extends ComponentPropsWithRef<'div'> {
  page:number
}


const Content = forwardRef<HTMLDivElement,ContentProps>(({page},ref) => {
  return (
    <div className='h-[100vh] leading-[100vh] text-center text-[15rem]' ref={ref}>
      {page}
    </div>
  );
});

export default Content;

여러개의 ref를 사용할 때 type narrowing까지 생각하는게 어려웠다.

1개의 댓글

comment-user-thumbnail
2022년 10월 16일

Nowadays, infidelity has caused many relationships to end. I was able to get information on how to determine whether or not someone is cheating on you. Everything is on this website signs someone is hiding something on their phone . For instance, this website helped me learn a lot about treason. The website is highly useful, as are the spy software applications. I suggest you test this material out because it will be useful to you in the future.

답글 달기