ThemeContext.tsx - Context 생성
import { createContext, useState } from "react";
// Context API 기본 + 동적으로 사용하기
//
// <사용 절차>
// 1. createContext를 통해 초기 value 설정
// - 여기서의 초기 value는 Provider 없을 때 사용 됨
// 2. 원하는 사용 범위를 MyContext.Provider + value를 통해 감싸기 (Provider 없어도 사용은 가능)
// - 동적인 Context 사용시 Provider 컴포넌트를 커스텀으로 제작
// 3. Context value 실제 사용 방법
// 1) Consumer 사용 -> 함수 children(render props 패턴)을 통해
// 2) useContext 사용 -> FC에서만
// 3) static contextType 사용 -> Class 컴포넌트에서만, this.context가 자동으로 value로 할당 됨
export interface ITheme {
state: { bgColor: string; borderColor: string };
actions: {
setBgColor: React.Dispatch<React.SetStateAction<string>>;
setBorderColor: React.Dispatch<React.SetStateAction<string>>;
};
}
interface IProps {
children: React.ReactNode;
}
// createContext(initialValue)
// - 초기 value는 객체일 필요 X, 아래 예시는 동적 활용을 위해 제작
// - 동적 활용을 할 때에는 state와 actions로 나누어 주는 것이 사용에 용이
const ThemeContext = createContext<ITheme>({
state: {
bgColor: "#ffffff",
borderColor: "#000000",
},
actions: {
setBgColor: () => {},
setBorderColor: () => {},
},
});
// 동적 Context 사용을 위한 커스텀 Provider
// actions 함수의 호출마다 state가 변경되어 Provider의 value가 변경되도록 설계
const ThemeContextProvider = ({ children }: IProps) => {
const [bgColor, setBgColor] = useState("#ffffff");
const [borderColor, setBorderColor] = useState("#000000");
const value: ITheme = {
state: { bgColor, borderColor },
actions: { setBgColor, setBorderColor },
};
return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
};
const { Consumer: ThemeContextConsumer } = ThemeContext;
export { ThemeContextProvider, ThemeContextConsumer };
export default ThemeContext;
ContextTheme.tsx - class component에서 Context의 사용 예시
import React from "react";
import { ThemeContextConsumer } from "../../shared/components/ThemeContext";
import ContextThemeUsingConsumer from "./ContextThemeUsingConsumer";
import ContextThemeUsingStatic from "./ContextThemeUsingStatic";
// Context의 value 구독 및 하위 컴포넌트 변경 예시
// - 해당 코드는 useContext를 제외한 나머지 두 방법을 다룸
// 1) Consumer 사용
// 2) static contextType 사용
class ContextTheme extends React.Component {
render(): React.ReactNode {
return (
<div>
<h1>Context Theme w/ Class Component</h1>
<div style={{ display: "flex", gap: "30px" }}>
{/* 방법 1. Consumer 사용 */}
<ThemeContextConsumer>
{(value) => <ContextThemeUsingConsumer {...value} />}
</ThemeContextConsumer>
{/* 방법 2. static contextType 사용 */}
<ContextThemeUsingStatic />
</div>
</div>
);
}
}
export default ContextTheme;
ContextThemeUsingConsumer.tsx - Consumer를 이용한 Context 사용
import React from "react";
import { ITheme } from "../../shared/components/ThemeContext";
interface IProps extends ITheme {}
// Consumer를 이용한 Context 사용
// - 부모 컴포넌트에서 Consumer의 render props를 통해 props로 context value를 전달 받음
class ContextThemeUsingConsumer extends React.Component<IProps> {
handleChange = (
e: React.ChangeEvent<HTMLInputElement>,
setColor: React.Dispatch<React.SetStateAction<string>>
) => {
setColor(e.target.value);
};
render(): React.ReactNode {
const { state, actions } = this.props;
return (
<div>
<h2>#1. Using Consumer</h2>
<div
style={{
width: "100px",
height: "100px",
backgroundColor: state.bgColor,
border: "5px solid",
borderColor: state.borderColor,
}}
/>
<label htmlFor="bgColorChange">bgColor: </label>
<input
type="color"
id="bgColorChange"
value={state.bgColor}
onChange={(e) => this.handleChange(e, actions.setBgColor)}
/>
<label htmlFor="borderColorChange">borderColor: </label>
<input
type="color"
id="borderColorChange"
value={state.borderColor}
onChange={(e) => this.handleChange(e, actions.setBorderColor)}
/>
</div>
);
}
}
export default ContextThemeUsingConsumer;
ContextThemeUsingStatic.tsx - static contextType을 이용한 Context 사용
import React from "react";
import ThemeContext, { ITheme } from "../../shared/components/ThemeContext";
// static contextType을 이용한 Context 사용
// - static contextType을 Context 자체로 설정하면 this.context가 value로 할당 됨
class ContextThemeUsingStatic extends React.Component {
static contextType?: React.Context<ITheme> | undefined = ThemeContext;
context!: React.ContextType<typeof ThemeContext>;
handleChange = (
e: React.ChangeEvent<HTMLInputElement>,
setColor: React.Dispatch<React.SetStateAction<string>>
) => {
setColor(e.target.value);
};
render(): React.ReactNode {
const { state, actions } = this.context;
return (
<div>
<h2>#2. Using static contextType</h2>
<div
style={{
width: "100px",
height: "100px",
backgroundColor: state.bgColor,
border: "5px solid",
borderColor: state.borderColor,
}}
/>
<label htmlFor="bgColorChange">bgColor: </label>
<input
type="color"
id="bgColorChange"
value={state.bgColor}
onChange={(e) => this.handleChange(e, actions.setBgColor)}
/>
<label htmlFor="borderColorChange">borderColor: </label>
<input
type="color"
id="borderColorChange"
value={state.borderColor}
onChange={(e) => this.handleChange(e, actions.setBorderColor)}
/>
</div>
);
}
}
export default ContextThemeUsingStatic;
UseContextTheme.tsx - useContext 훅을 이용한 Functional component에서의 사용법
import { useContext } from "react";
import ThemeContext from "../../shared/components/ThemeContext";
// useContext: Context API를 Consumer 없이 사용 가능
// - Consumer 없이 사용 가능하지만, 동적 사용을 위해서는 Provider로 감싸줘야 함!
// - 매개변수로 해당 Context 그 자체를 넘겨야 함
const UseContextTheme = () => {
const { state, actions } = useContext(ThemeContext);
const handleChange = (
e: React.ChangeEvent<HTMLInputElement>,
setColor: React.Dispatch<React.SetStateAction<string>>
) => {
setColor(e.target.value);
};
return (
<div>
<h2>useContext Theme</h2>
<div
style={{
width: "100px",
height: "100px",
backgroundColor: state.bgColor,
border: "5px solid",
borderColor: state.borderColor,
}}
/>
<label htmlFor="bgColorChange">bgColor: </label>
<input
type="color"
id="bgColorChange"
value={state.bgColor}
onChange={(e) => handleChange(e, actions.setBgColor)}
/>
<label htmlFor="borderColorChange">borderColor: </label>
<input
type="color"
id="borderColorChange"
value={state.borderColor}
onChange={(e) => handleChange(e, actions.setBorderColor)}
/>
</div>
);
};
export default UseContextTheme;