2024. 6. 12. 05:22ㆍ리액트 (React)/리액트 레시피 (React Recipt)
Theme
7. State 보존하기 글에서 만들었던 친구 목록에 테마를 추가해봅니다.
이는 오른쪽 상단의 테마 버튼을 눌러서 전환할 수 있도록 합니다.
먼저 List에 theme state를 추가하고 div의 className에 적용합니다.
export default function List(...) {
const [theme, setTheme] = useState<Theme>("light");
return (
<div className="layout">
<div className={`box ${theme}`}> // theme에 따라 className이 변경
<Header />
{names.map((name) => (
<div className="list-item" onClick={() => onClickListItem(name)}>
<span className="name">{name}</span>
</div>
))}
</div>
</div>
);
}
그리고 Header에도 테마를 적용할 뿐만 아니라,
내부 버튼으로 테마를 제어할 계획이므로 props 및 관련 로직을 추가해줍니다.
import "./Header.scss";
import { Theme } from "./List";
export default function Header({
theme,
onClickTheme,
}: {
theme: Theme;
onClickTheme: () => void;
}) {
function renderIcon(theme: Theme) {
if (theme === "light") {
return <i className="fa-solid fa-sun" />;
}
return <i className="fa-solid fa-moon" />;
}
return (
<div className={`friend-header ${theme}`}>
<span className="title">Chat Friends</span>
<button className="theme" onClick={() => onClickTheme()}>
{renderIcon(theme)}
</button>
</div>
);
}
결과는 다음과 같습니다.
이제 친구 목록 뿐만 아니라 채팅창에도 마찬가지로 적용하려고 합니다.
따라서 theme을 Main으로 옮겨옵니다.
export default function Main() {
const [theme, setTheme] = useState<Theme>("light");
...
}
그러나 props로 모든 컴포넌트들에게 theme을 넘겨주어야 하니, 고난의 작업이 예상될 것으로 보이네요.
특히 컴포넌트를 만들 때도 조금 더러워지는 걸 생각해야됩니다.
<div>
{selectedName !== "" ? (
<>
{previousName && (
<ChangeButton
name={previousName}
direction="left"
onClick={() => setSelectedName(previousName)}
theme={theme} // give theme
/>
)}
<Box
key={selectedName}
name={selectedName}
onClickList={() => setSelectedName("")}
theme={theme} // give theme
/>
{nextName && (
<ChangeButton
name={nextName}
direction="right"
onClick={() => setSelectedName(nextName)}
theme={theme} // give theme
/>
)}
</>
) : (
<List
names={friendNames}
onClickListItem={(name) => {
setSelectedName(name);
}}
onClickTheme={() => handleClickTheme()}
theme={theme} // give theme
/>
)}
</div>
Context
context를 사용하면 props으로 전달하지 않아도, 모든 컴포넌트가 각자 사용할 수 있도록 합니다.
가령 테마(theme)를 모든 컴포넌트가 사용해서 각자 본인의 색상을 바꾼다고 하면,
context를 사용하면 모든 컴포넌트가 props로 theme을 만들지 않아도 되죠.
context를 사용하는 방법은 다음과 같습니다.
1. context를 만듭니다.
import { createContext } from "react";
export type Theme = "light" | "dark";
export const ThemeContext = createContext<Theme>("light");
2. 만든 context로 Provider를 생성합니다.
import { Theme, ThemeContext } from "./ThemeContext";
...
export default function Main() {
const [theme, setTheme] = useState<Theme>("light");
...
return (
<div>
<ThemeContext.Provider value={theme}>
{...} // now, every components can use theme!
</ThemeContext.Provider>
</div>
);
}
3. Provider 내부의 컴포넌트들은 useContext 훅을 사용해 theme을 사용할 수 있습니다.
const theme = useContext(ThemeContext);
예를 들어 Header는 다음과 같이 context을 사용합니다.
import { useContext } from "react";
import { Theme, ThemeContext } from "../ThemeContext";
import "./Header.scss";
export default function Header({ onClickTheme }: { onClickTheme: () => void }) {
const theme = useContext(ThemeContext);
function renderIcon(theme: Theme) {
if (theme === "light") {
return <i className="fa-solid fa-sun" />;
}
return <i className="fa-solid fa-moon" />;
}
return (
<div className={`friend-header ${theme}`}>
<span className="title">Chat Friends</span>
<button className="theme" onClick={() => onClickTheme()}>
{renderIcon(theme)}
</button>
</div>
);
}
이렇게 context를 이용해 만든 결과는 다음과 같습니다.
Alternative
context는 props를 만들 필요 없이 컴포넌트 내에서 알아서 사용할 수 있으니 매우 편리하죠.
하지만 일반적으로는 props를 통해 명시적으로 노출해주는 것이 좋습니다.
일단 context는 보이지 않기 때문에, 이미 context로 정의되어 있는지 아닌 지 확인하기가 어렵습니다.
따라서 일일히 context가 정의되어 있는 곳을 찾아야 할 뿐 아니라 Provider가 설정되어 있는 지도 확인해야 하죠.
이런 불편함 뿐 아니라, 잠깐 놓쳐도 중복으로 props와 context를 만들 수 있습니다.
특히 context를 사용하는 건 컴포넌트 자체의 선택이기 때문에, 컴포넌트를 사용하는 입장에서 해당 정보가 필수인 지 모릅니다.
따라서 올바르게 context를 사용하려면 왜 context를 사용하는 것이 더 좋은 지 설명할 수 있어야 합니다.
- 사전에 모두 약속하고 정했습니다.
- props로 표현하기에는 가독성과 유지보수가 더 떨어집니다.
- 대다수의 하위 컴포넌트가 해당 정보를 사용합니다.
- ...
그렇게만 한다면 context는 충분히 좋은 대안이 될 수 있죠!
References
'리액트 (React) > 리액트 레시피 (React Recipt)' 카테고리의 다른 글
9. Ref로 제어하기 (0) | 2024.06.12 |
---|---|
7. State 보존하기 (0) | 2024.06.11 |
6. 인터렉티브한 Input 만들기 (0) | 2024.06.10 |
5. State로 리렌더링하기 (0) | 2024.06.10 |
4. CSS 입히기 (0) | 2024.06.10 |