Styled-Components를 리팩토링하는 과정에서 Button
를 재사용할 때, 상황에 맞게끔 width/height 값이 바뀌도록 수정해야했다.
Button
이 속성값에 맞게 잘 적용되었는데, console창에 warning이 떴다. 영어 울렁증이 순간 올라왔지만 찬찬히 읽어보니 Styled-Components에서 사용자 정의 prop (HTML 속성이 아닌 prop)이 DOM 요소로 전달되었다는 뜻이었다.
해결방법은 아래 정리해두었다.
어찌저찌 Styled-Components 리팩토링과 warning을 해결하고, PokemonDetail
에 ADD
/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로 넘어가보자!
Styled-Components도 컴포넌트이기 때문에 props를 전달받을 수 있다. 하지만, 스타일 지정을 위한 props만 있는 경우가 아니라면(HTML 속성, 이벤트 등) DOM에서 혼동이 오기 때문에 warning을 띄운다.
Styled-Components만의 props를 지정하기 위해서는 $ prefix
를 사용하면 된다. 즉, $ prefix
를 사용하면 props가 실제 DOM 요소에 전달되는 것을 막는다.
prefix 참고 사이트 1
prefix 참고 사이트 2
i ) Context API란?
Context API는 부모 컴포넌트에서 props를 사용하지 않아도, 필요한 데이터(state 등)을 쉽게 공유할 수 있도록 한다. 즉, prop-drilling을 하지 않아도, App의 모든 컴포넌트에서 데이터에 접근할 때 사용한다.
*prop-drilling이란? : 컴포넌트 트리에서 데이터를 하위 컴포넌트로 전달하기 위해 중간 컴포넌트를 통해 props를 내려주는 것
ii ) prop-drilling의 단점
예시로 App.jsx
(최상위 컴포넌트)에서 전달한 state를 User3.jsx
에서 뿌려준다고 가정해보자. 그렇다면 User1.jsx
와 User2.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 값을 사용.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로 더 자세히 다룰 예정이다.
Button
의 Styled-Components 속성값들 앞에 $ prefix
를 붙여 수정해주었다.
buttonWidth ▶️ $buttonWidth
과 buttonHeight ▶️ $buttonHeight
로 수정한 후 적용해주었더니 warning이 해결되었다.
우선 context
폴더와 DexContext.jsx
파일을 생성하고, 공유해야하는 변수와 로직들을 DexContext
에 리팩토링 해주었다.
DexContext
에서 생성한 Provider로 Router
를 감싸주었다.
Context가 필요한 코드에서 useContext(DexContext)
로 불러와 사용하였다.
이론으로 배울 때는 복잡하고 어려울 것 같았는데, 실제로 적용해보니 생각보다 간단해서 놀랐다. 역시 배운 것을 직접 해봐야 내 것이 된다.
어제 짠 로직에서 Button
까지 전해지는 onClick 프로퍼티가 DashBoard
/ PokemonList
에서 PokemonCard
에 무의미하게 전달되는 느낌이 들어서 Context API를 활용해 리팩토링을 해주었다.
Button
의 children(텍스트)와 onClick시 실행되는 로직을 isCatched
함수 반환값에 의해 결정되도록 작성했더니, DashBoard
와 PokemonList
버튼이 동기화 되어버렸다.
사실 UX 면에서는 이게 더 좋은 것 같지만...🤔 프로젝트에서 주어진 예외처리 구현 과제(중복 선택시 알람 띄워 줘야함)가 있었기 때문에 다시 수정을 해야했다.
생각보다 쉽게 문제가 해결되었다! 로직을 어떻게 세우면 좋을지 튜터님께 도움을 받으러 갔다가 얼떨결에 라이브 코딩으로 해결을 스스로 해버렸다😂
DashBoard
와 PokemonList
에서 렌더링되는 Card를 구분짓기 위해, PokemonList
의 Card에게 isInList={true}
속성을 넘겨주었다. 그리고 Card에서 Button
을 isInList의 값을 통해 결정되도록 로직을 세웠더니 잘 구현되었다.
문제해결을 위해 어렵게 로직을 생각하다 꼬여버린 것이다. 알고리즘을 풀 때에도 이런 경우가 왕왕 있었는데, 오히려 단순하게 생각하는 것이 더 도움이 되는 것 같다!
Context API를 사용한 리팩토링으로, PokemonDetail
에서도 addList
/removeList
함수가 잘 실행된다!
그리고 DashBoard
와 PokemonList
의 버튼이 다르게 설정되는 것도 확인되었다!
DexContext.jsx
PokemonCard.jsx
다른 코드도 확인하고 싶다면 내 GitHub / Pokemon 레포에서 확인할 수 있다.
이왕 들어가는 김에 내 깃헙 좋댓구알