졸업가능? 프로젝트 리팩토링 - 반응형 웹 뷰 구현하기(2)

배성규·2023년 2월 15일
1

프로젝트

목록 보기
2/10
post-thumbnail

Mission 2 : 반응형 네비게이션 바를 개선해보자 !

개선하고 싶은 부분

기존 헤더바에서 Nav를 출력하는게 마음에 들지 않았다. 따라서, 보여주는 값에 대한 설계를 바꾸고자 했다.

기존 방식

  • Header에서 페이지 사이즈와 토글 상태를 받아 헤더바 자체에서 출력

그러나 Nav 자체에서 반응형으로 변경하고 토글버튼의 상태를 받아오는 것이 Nav 자체에 있는 코드를 활용할 수 있는 방법이라고 생각하여 다음과 같이 개선하고자 했다.

개선 방향성

  • 웹 페이지 사이즈에 맞춰 Nav컴포넌트를 변경
  • 닫기 버튼을 누르거나 햄버거 버튼을 누를 때 변경되는 상태값을 Nav컴포넌트에 값을 보내서 닫히고 열릴 수 있게 하고자 했다.

그러자 아래와 같은 에러가 발생했다.

react-dom.development.js:14906 
Uncaught Error: Invalid hook call. 
Hooks can only be called inside of the body of a function component. 
This could happen for one of the following reasons: 
1. You might have mismatching versions of React and the renderer (such as React DOM) 
2. You might be breaking the Rules of Hooks 
3. You might have more than one copy of React in the same app See https:

해당 에러의 이유를 부모-자식간 컴포넌트가 아니기 때문에 발생한 문제로 추론했다.
HeaderNav컴포넌트의 값에 따라서 동시에 변하게 했어야 하는데, 이를 해결하기 위해서는 2가지 방법이 존재한다고 생각했다.

  1. Header컴포넌트와Nav컴포넌트를 합치기
  2. 전역적으로 상태관리를 할 수 있는 방법을 찾기

2번을 선택하여 프로젝트 개선을 진행하고자 했다.

<선택한 이유>

  • HeaderNav를 추후에 분리해야할 때 다시 구조를 개선해야함
  • 기능을 확장하다보면 필연적으로 상태 관리를 필요로 할 것이라 생각
상태관리 라이브러리
  • 상태관리 라이브러리에는 recoil, redux, react context api 등이 있다.
  • react context api는 우선적으로 후보군에서 제외했다.
    • 리액트 앱 최상단에 Provider를 배치하는데, 이를 통해 context를 구독하는 컴포넌트들에게 변화를 알리는 역할으 한다.
    • 그러나, Provider의 value prop이 바뀔때마다 다시 렌더링된다는 단점이 존재한다 (컨텍스트를 참고하는 모든 컴포넌트가 리렌더링되면 성능상으로 비효율성이 높아진다)
  • reduxrecoil로 후보군이 좁혀졌는데, recoil을 선택하여 프로젝트에 적용하기로 했다.
    • 선택이유 : 기존 사용하던 useState와 비슷하게 적용할 수 있다는 장점과 redux에 비해 가볍고 직관적이라는 장점이 있어서 선택했다.

recoil 적용하기

recoil라이브러리를 세팅하고, 루트파일에 RecoilRoot를 씌워주어 전역적으로 관리할 수 있는 환경을 세팅해주었다.

// App.js
// code.. 
import { RecoilRoot } from 'recoil';
const App = () => {
  return (
    //code.. 
    <RecoilRoot>
    	<Header/>
        <Nav/>
        // code.. 
    </RecoilRoot>
  );

atom.js파일을 만들어 atom의 값을 읽는 컴포넌트들에게 값 변경이 있을 시 재 렌더링하도록 설정해주었다.

  • Atom : 상태를 말하고, 어떤 컴포넌트에 의해 씌여지고 읽혀지는 것이 가능하다.
// atom.js
import { atom } from 'recoil';

/*
토글의 상태변화를 받는 값. 
key는 유니크한 값을 가지고 default에서 값을 설정해줄 수 있다. 
*/
let toggleState = atom({
    key: 'toggle',
    default: false,
});

export default toggleState;

atom을 읽고 쓰게 하기 위해서는 useRecoilState()를 호출하여 사용한다.

// Header.jsx
import toggleState from '../atom';
import { useRecoilState } from 'recoil';

//code.. 
const [isToggled, setIsToggled] = useRecoilState(toggleState);
    const handleToggleSide = () => {
        setIsToggled(!isToggled);
    };
    return (
       // code.. 
       {isToggled ? (
                    <div className="header__sidebar">
                        <img
                            alt="reject"
                            src="imgs/reject.png"
                            className="header__sidebar-icon"
                            onClick={handleToggleSide}
                        ></img>
                    </div>
                ) : (
                    <div className="header__sidebar">
                        <img
                            className="header__sidebar-icon"
                            src="imgs/sidebar.png"
                            alt="sidebar"
                            onClick={handleToggleSide}
                        ></img>
                    </div>
                )}
     //code..
    );

Nav컴포넌트에도 동일하게 적용하여 상태 값이 변하면 적용되도록 처리하였다.

// Nav.jsx 
import toggleState from '../atom';
import { useRecoilState } from 'recoil';

function Nav() {
    const [checkToggled, setCheckToggled] = useRecoilState(toggleState);

    return (
        <>
            {checkToggled ? (
                <nav className="navbar">
                    <Link to="Notice" className="navbar__nav">
                        공지사항
                    </Link>
                    <Link to="KyRecommend" className="navbar__nav">
                        인기교양추천
                    </Link>
                    <Link to="Graduate" className="navbar__nav">
                        졸업요건확인
                    </Link>
                    <Link to="Board" className="navbar__nav">
                        정보공유게시판
                    </Link>
                    <Link to="Feedback" className="navbar__nav">
                        피드백하기
                    </Link>
                </nav>
            ) : (
                <></>
            )}
        </>
    );
}

1차 개선 결과

(기본 상태)

(햄버거 버튼을 눌렀을 시)

제대로 값은 먹혀서 닫히고 열리게 완성이 되었다. 하지만 다른 페이지로 이동하면 알아서 nav가 다시 닫히지 않았다.

따라서 네비게이션에 있는 버튼을 클릭하면 false값을 재할당하여 default형태로 되돌렸다.

false값을 재할당하여 발생한 문제

  • false이 할당된 상태에서 pc뷰 크기로 늘리면 nav상태가 출력되지 않는 문제 발생

window.innerWidth값을 가져와 pc뷰일때의 조건을 추가하여 pc뷰 상태일때는 무조건적으로 nav가 보이도록 설정했다.

function Nav() {
    const [checkToggled, setCheckToggled] = useRecoilState(toggleState);
    const [checkWidth, setCheckWidth] = useState(window.innerWidth);

    const clickLink = () => {
        setCheckToggled(false);
    };
    const handleResize = () => {
        setCheckWidth(window.innerWidth);
    };
    useEffect(() => {
        window.addEventListener('resize', handleResize);
    });
    return (
        <>
            {checkToggled || checkWidth >= 768 ? (
                <nav className="navbar">
                    <Link
                        to="Notice"
                        className="navbar__nav"
                        onClick={clickLink}
                    >
                        공지사항
                    </Link>
                    <Link
                        to="KyRecommend"
                        className="navbar__nav"
                        onClick={clickLink}
                    >
                        인기교양추천
                    </Link>
                    <Link
                        to="Graduate"
                        className="navbar__nav"
                        onClick={clickLink}
                    >
                        졸업요건확인
                    </Link>
                    <Link
                        to="Board"
                        className="navbar__nav"
                        onClick={clickLink}
                    >
                        정보공유게시판
                    </Link>
                    <Link
                        to="Feedback"
                        className="navbar__nav"
                        onClick={clickLink}
                    >
                        피드백하기
                    </Link>
                </nav>
            ) : (
                <></>
            )}
        </>
    );
}

최종 개선 결과

마무리

전역적으로 상태 관리하는 라이브러리에 대해 경험이 부족했는데, recoil을 사용해보는 경험을 할 수 있었다. 추가로 리팩토링을 할 때에도 전역적으로 관리해야하는 부분이 필요하다면 적극적으로 도입해서 사용해봐야겠다.

profile
FE 유망주🧑‍💻

0개의 댓글