포켓몬 도감 사이트) TroubleShooting 2 : $ prefix와 Context API🎈

밍갱·2025년 2월 6일
0

PROJECTS

목록 보기
8/20

1. 문제 발생🤯

01. Button.jsx의 width/height 값 속성으로 넣어주는 로직

Styled-Components를 리팩토링하는 과정에서 Button를 재사용할 때, 상황에 맞게끔 width/height 값이 바뀌도록 수정해야했다.

Button이 속성값에 맞게 잘 적용되었는데, console창에 warning이 떴다. 영어 울렁증이 순간 올라왔지만 찬찬히 읽어보니 Styled-Components에서 사용자 정의 prop (HTML 속성이 아닌 prop)이 DOM 요소로 전달되었다는 뜻이었다.

해결방법은 아래 정리해두었다.

02. PokemonDetail.jsxADD/DELETE버튼 추가

어찌저찌 Styled-Components 리팩토링과 warning을 해결하고, PokemonDetailADD/DELETE 버튼을 추가하는 작업을 해주었다. dexList(localStorage)에 some메서드를 사용해 포함된 포켓몬의 디테일 페이지에서는 DELETE 버튼이 뜨고, 포함되지 않은 포켓몬의 디테일 페이지에서는 ADD 버튼이 뜨도록 로직을 세웠다.

우선 dexList에 포함되어있는지 여부를 판단하기 위한 함수 isCatched를 세운 뒤, 그 반환값에 맞는 Button을 렌더링하도록 했다.
로직이 한번에 잘 작동하는 것을 보며 뿌듯함을 느꼈다. 설레는 마음에 버튼을 클릭해보았더니...

아뿔싸... addList/removeList(추가/삭제) 함수를 Dex.jsx에 세워서 Detail.jsx로 넘겨줄 수 없다. 넘겨주려면 최상위 컴포넌트인 App.jsx에 로직을 다시 세워야한다.
솔직히 고백하자면, Context API가 두려워서 흐린 눈으로 prop-drilling 과제부터 진행하고 있었는데 이젠 더이상 미룰 수 없이 되어버렸다. prop-drilling의 문제점을 몸소 느껴봤으니, 이제 Context API로 넘어가보자!

2. 개념 정리🧐

01. Styled-Component의 $prefix

Styled-Components도 컴포넌트이기 때문에 props를 전달받을 수 있다. 하지만, 스타일 지정을 위한 props만 있는 경우가 아니라면(HTML 속성, 이벤트 등) DOM에서 혼동이 오기 때문에 warning을 띄운다.
Styled-Components만의 props를 지정하기 위해서는 $ prefix를 사용하면 된다. 즉, $ prefix를 사용하면 props가 실제 DOM 요소에 전달되는 것을 막는다.

prefix 참고 사이트 1
prefix 참고 사이트 2

02. Context API

i ) Context API란?
Context API는 부모 컴포넌트에서 props를 사용하지 않아도, 필요한 데이터(state 등)을 쉽게 공유할 수 있도록 한다. 즉, prop-drilling을 하지 않아도, App의 모든 컴포넌트에서 데이터에 접근할 때 사용한다.

*prop-drilling이란? : 컴포넌트 트리에서 데이터를 하위 컴포넌트로 전달하기 위해 중간 컴포넌트를 통해 props를 내려주는 것

ii ) prop-drilling의 단점
예시로 App.jsx (최상위 컴포넌트)에서 전달한 state를 User3.jsx에서 뿌려준다고 가정해보자. 그렇다면 User1.jsxUser2.jsx는 state를 직접적으로 사용하지 않지만, 단순히 전달을 위해서 state를 받고 내려줘야한다.

//App.jsx(최상위 컴포넌트)
	return(
    	<div>
        	<User1 state={state} />
        </div>
    )

//User1.jsx (하위 컴포넌트)
	return(
    	<div>
        	<User2 state={state} />
        </div>
    )

//User2.jsx (하위 컴포넌트)
	return(
    	<div>
        	<User3 state={state} />
        </div>
    )
    
//User3.jsx (하위 컴포넌트)
	return(
    	<div>
        	<h1>{state}</h1>
        </div>
    )
  • 중간 컴포넌트에 불필요한 프로퍼티 전달하여, 불필요한 복잡성을 초래할 수 있다.
  • 프로퍼티 이름이 변경되면 해당 값을 추적하고 업데이트하는 것이 복잡해진다.
  • 프로퍼티가 필요로 하는 컴포넌트에 전달되지 않은 상황을 인지하기 어려워 잠재적인 문제를 발견하기 어려울 수 있다.
  • 프로퍼티의 데이터 형식을 변경해야 하는 경우, 컴포넌트 계층 전체에서 업데이트 해야한다.

iii ) Context API 사용법

  • createContext : context 생성.
  • Provider : 생성한 context를 대상 컴포넌트에 값을 내려주기 위한 컴포넌트. value에 전달할 데이터를 넣고, 대상 컴포넌트를 감싼다.
//UserContext.jsx
import { createContext } from 'react';

//Context 생성
export const UserContext = createContext();

//Provider 생성
//Provider에 인자로 받는 children은 Provider로 감쌀 하위 컴포넌트를 뜻한다.
export const UserProvider = ({children}) => {
	//해당 컴포넌트가 공유할 변수, 함수 등
    //Context API는 상태뿐만 아니라 함수를 공유하는 데도 유용하다
    
    return (
    	<UserContext.Provider value={{공유할 변수명, 함수명}}>
        	{children}
        </UserContext.Provider>
    )
}

//App.jsx (상위 컴포넌트)
import { UserProvider } from './context/UserContext';

const App = () => {
	return (
    	<UserProvider>
        	<User1 />
        </UserProvider>
    )
}
  • useContext : context 값을 사용.
    *Consumer 컴포넌트를 사용하는 방법도 있지만, 이번 프로젝트에서는 useContext Hook을 사용해 따로 정리하진 않았다.
//User1.jsx와 User2.jsx는 더이상 props를 물려받지 않아도 된다.
//User3.jsx (하위 컴포넌트)
import { useContext } from 'react';
import { UserContext } from '../context/UserContext';

const User3 = () => {
  //context
  const { 공유할 변수명, 함수명 } = useContext(UserContext);
  
  ...이하 생략
}

Context API 참고 사이트 1
*useContext와 Context API관련 글은 추후에 TIL로 더 자세히 다룰 예정이다.

3. 해결 방안😇

01. Styled-Components 속성값에 $prefix 넣기

Button의 Styled-Components 속성값들 앞에 $ prefix를 붙여 수정해주었다.
buttonWidth ▶️ $buttonWidthbuttonHeight ▶️ $buttonHeight로 수정한 후 적용해주었더니 warning이 해결되었다.

02. Context API로 리팩토링해주기

우선 context 폴더와 DexContext.jsx 파일을 생성하고, 공유해야하는 변수와 로직들을 DexContext에 리팩토링 해주었다.

DexContext에서 생성한 Provider로 Router를 감싸주었다.

Context가 필요한 코드에서 useContext(DexContext)로 불러와 사용하였다.

이론으로 배울 때는 복잡하고 어려울 것 같았는데, 실제로 적용해보니 생각보다 간단해서 놀랐다. 역시 배운 것을 직접 해봐야 내 것이 된다.

02-1. 리팩토링 과정에서 Button 문제 발생

어제 짠 로직에서 Button까지 전해지는 onClick 프로퍼티가 DashBoard / PokemonList에서 PokemonCard에 무의미하게 전달되는 느낌이 들어서 Context API를 활용해 리팩토링을 해주었다.
Button의 children(텍스트)와 onClick시 실행되는 로직을 isCatched 함수 반환값에 의해 결정되도록 작성했더니, DashBoardPokemonList버튼이 동기화 되어버렸다.
사실 UX 면에서는 이게 더 좋은 것 같지만...🤔 프로젝트에서 주어진 예외처리 구현 과제(중복 선택시 알람 띄워 줘야함)가 있었기 때문에 다시 수정을 해야했다.

03. isInList 속성 추가

생각보다 쉽게 문제가 해결되었다! 로직을 어떻게 세우면 좋을지 튜터님께 도움을 받으러 갔다가 얼떨결에 라이브 코딩으로 해결을 스스로 해버렸다😂
DashBoardPokemonList에서 렌더링되는 Card를 구분짓기 위해, PokemonList의 Card에게 isInList={true} 속성을 넘겨주었다. 그리고 Card에서 Button을 isInList의 값을 통해 결정되도록 로직을 세웠더니 잘 구현되었다.

문제해결을 위해 어렵게 로직을 생각하다 꼬여버린 것이다. 알고리즘을 풀 때에도 이런 경우가 왕왕 있었는데, 오히려 단순하게 생각하는 것이 더 도움이 되는 것 같다!

4. 결과😎

01. 해결!

Context API를 사용한 리팩토링으로, PokemonDetail에서도 addList/removeList 함수가 잘 실행된다!

그리고 DashBoardPokemonList의 버튼이 다르게 설정되는 것도 확인되었다!

02. 최종 코드

  • DexContext.jsx

  • PokemonCard.jsx

다른 코드도 확인하고 싶다면 내 GitHub / Pokemon 레포에서 확인할 수 있다.
이왕 들어가는 김에 내 깃헙 좋댓구알

profile
미술 전공에서 프론트엔드 개발까지

0개의 댓글