React 뒤로가기 데이터 유지 - react dwilogagi deiteo yuji

아 그거 뭐였지

Front-End

[Angular] 자바스크립트 뒤로가기 데이터 유지

승발자 2022. 1. 9. 20:57

※ 2022년 03월 14일 내용이 너무 빈약하여 수정하였습니다. ※

Q. 뒤로 가기해도 데이터 위치 유지 해주세요

쇼핑몰 프로젝트 진행 중.

상품 리스트 클릭후 뒤로 가기 시 해당 상품으로 포커싱이 되게 해 달라는 요청이 있어 작업하게 되었다.

해당 방법외에도 여러 가지 방법이 있으니 이 코드가 정답이 아니라는것을 유의하길 바란다. 책임회피 해버리기~

처음에는 localstorage를 사용해서 구현하였는데, 브라우저를 종료시켜도 데이터가 남아있는 경우가 있어

sessionstorage를 사용하여 구현하였다.

더보기

localstorage : 로컬에서 직접 삭제하기 전까지는 데이터가 남아있음

sessionstorage : 세션을 종료하기 전까지는 (ex_ 브라우저 창 닫기) 데이터가 남아있음

아이디어는 다음과 같다.

1. 뒤로 가기 이벤트가 아닐 시 async 요청 후 sessionstorage에 상품 리스트 데이터와 start값 저장

  • 더보기 요청시(또는 페이지네이션) start값이 필요하므로 상품 리스트 데이터와 start값을 같이 저장해준다.

2. 상품 리스트에서 상품 클릭 시 상품 위치 값을 sessionstorage에 저장

3. 클릭된 상품에서 뒤로 가기로 상품 리스트에 왔을 시 async 요청을 하지 않고 sessionstorage 데이터 호출

  • 클릭된 상품에서 뒤로 가기 이벤트 발생 시 sessionstroage에 체크 (true)
  • sessionstorage에서 상품 데이터를 가져와서 렌더링하고 상품 위치 값을 가져와서 포커싱

4. sessionstroage에서 렌더링이 끝났으면 sessionstroage에 체크 (false)

  // product-list component
  
  isBack = false;
  localBackItems = [];
  isMore = false;
  start = 0;
  products = [];
  
  getBackDatas(){
    // 뒤로가기로 해서 온건지 확인
    this.isBack = JSON.parse(sessionStorage.getItem('isMainBack'));
    //sessionstroage에서 데이터 가져오기
    let backItems = JSON.parse(sessionStorage.getItem('backMainItems'));
    //start값
    let index = parseInt(sessionStorage.getItem('backMainIndex'));
    //뒤로가기시 포커싱 되어야할 상품 위치
    //뒤로가기로 해서 온것이아니라면 그냥 0값을주어 최상단으로 위치한다.
    let scroll = parseInt(sessionStorage.getItem('mainScrollY')) ? JSON.parse(sessionStorage.getItem('mainScrollY')) : 0;
    //뒤로가기로 해서 왔을경우 sessionstroage값을 렌더링 후 해당 위치로 포커싱
    if(this.isBack && !this.isMore){
      this.products = backItems;
      this.localBackItems = backItems;
      this.goTab(scroll)
    }
    // 더보기 버튼 눌렀을경우 start값 적용해서 기존의 async요청
    // start는 데이터 Get요청할때 어디서부터 가져오는지를 뜻한다. 
    else if(this.isMore){
      this.start = index;
      this.getrProducts();
    }
    //뒤로가기로 해서 온것이 아닐경우 기존의 async요청
    else{
    //더보기시 데이터를 적재하는 방식으로 구현해서 home을 클리어해주어야함.
    //products는 getrProducts를 요청했을때 받아온 상품목록 데이터이다
      this.products=[];
      this.getrProducts();
    }
    //이부분이 없으면 새로고침을해도 sessionstroage데이터를 가져옴
    sessionStorage.setItem('isMainBack',JSON.stringify(false));
 }
 
  // 상품을 클릭했을때 이 함수를 콜한다.
  // 상품위치를 저장하는 함수이다.
  scroll:nubmer
  @HostListener("window:scroll",['$event'])
  onScroll(){
    this.scroll = window.pageYOffset
    sessionStorage.setItem('mainScrollY',JSON.stringify(this.scroll));
  }

???: 아니 뒤로가기 이벤트는 어떻게 감지함? 그걸 빼먹으면 어떡함!

바로 밑에 준비되어있다.

  // product-detail component
  // Angular에서 사용되는 HostListener이다.
  // import { HostListener} from '@angular/core'; 로 import해주어야함
  // 뒤로가기 이벤트 발생시 sessionstroage에 체크한다.
  
  @HostListener('window:popstate',['$event'])
  onBack(e:Event){
    sessionStorage.setItem('isMainBack',JSON.stringify(true));
  }

Angular에서는 HostListener를 사용하여 쉽게 뒤로가기 이벤트를 감지할수있다.

Angular가 아닌 자바스크립트에서는

//product-detail component

window.onpageshow = function(e){
	if(e.persisted){
    	//뒤로가기 눌렀을때 로직실행
    	sessionStorage.setItem('isMainBack',JSON.stringify(true));
      }
 }

이런식으로 사용하면 될것같다.

기존에 react의 spa를 이용해서 페이지를 구현하던터라 페이징을 할 시에는 데이터를 새로 가져와서 렌더링이 되어 뒤로가기를 하면 이전의 다른 페이지가 렌더링 될 수 밖에 없던 상태였다.

내가 개발 중인 시스템은 뒤로가기를 하면 이전 번호의 페이지를 보여주도록 구현해야해서 조금 손을 봐야했다.

원래는 페이지 번호를 누르거나 이전 페이지 또는 다음 페이지 키를 누르면 데이터만 다시 fetch해와서 재렌더링하는 식이었다.
이렇게하면 이전 페이지 번호로 뒤로가기를 구현하는 것이 불가능하기 때문에,
다른 페이지 번호를 누를 때 데이터를 가져오는 것이 아니라 라우팅해서 새롭게 컴포넌트를 렌더링하는 식으로 구현할 수 있었다.

그러기 위해선 밑과 같이 항상 최상위 컴포넌트는 url로부터 페이지 번호 파라미터를 받아와야한다.
그리고 이를 라우팅을 하는 코드가 담겨있는 컴포넌트에 전달한다.
추가로 뒤로가기 구현을 위해 현재 컴포넌트의 history 객체도 전달하도록 한다.

const Body = (props) => {
  const { pageNum } = props.match.params;

  const [totalPage, setTotalpage] = useState(0);
  const [problemData, setProblemData] = useState([]);

  const fetchProblems = async () => {
    const pos = (pageNum - 1) * size;
    try {
      // fetch로 데이터 받아오기
    } catch (error) {
      console.error(error);
    }
  };

  useEffect(() => {
    fetchProblems(pageNum);
  }, []);

  return <div>
        <div>
        <ProblemList problems={problemData}/>
        </div>
        <StyledContainer>
          <PagenumberList curPage={pageNum} totalPage={totalPage} history={props.history}/>
        </StyledContainer>
        </div>;
};

export default Body;

이렇게만 구현하면 내가 예상했던 대로 동작할 거라 기대했지만, 그렇지 않았다.

url은 변경이 잘 되고 컴포넌트 자체도 수행이 잘 되는 것을 확인했지만 useEffect 훅이 무시되어 데이터의 변경이 일어나지 않았고 때문에 가상 DOM이 변화를 감지하지 못해
리렌더링이 일어나지 않았다.

useEffect의 두 번째 파라미터를 보면 빈 배열임을 확인할 수 있다.
이 파라미터는 optional한데, useEffect 사용의 최적화를 위해 매번 수행되는 것이 아니라 특정 인수의 변화가 일어났을 때만 수행되길 원한다면 그 특정 인수를 배열에 넣을 수 있다.
지금 나는 빈 배열을 넣었는데 이는 컴포넌트가 마운트 될 때 즉 DOM에 올려질 때만 수행하겠다는 얘기다.

때문에 내가 url을 전달해서 컴포넌트를 재호출해도 이미 마운트된 상태이기 때문에 변화가 없는 것이었다.

이를 해결할 수 있는 방법은

1. props변화에 따라 useEffect 실행

  useEffect(() => {
    fetchProblems(pageNum);
  }, [props]);

2. 렌더링 후 항상 실행

  useEffect(() => {
    fetchProblems(pageNum);
  });

로 두 가지 방법이 있다.
2번처럼 간단하게 해결할 수 있지만, 페이징할 때 리렌더링 되야하는 시점은 props의 페이지 번호 파라미터가 변경될 때 밖에 없기 때문에 1번 방법이 더 괜찮은 것 같다. 👍