โ๏ธ ๋ฆฌ์กํธ๋ก ๊ฐ๋ฐํ๋ค ๋ณด๋ฉด state ๊ด๋ฆฌ๋ฅผ ์ํ๊ธฐ ์ํด ์ฃผ์๋ฅผ ๋ง์ด ํ๊ณค ํ๋ค.
๐๏ธ ์ปดํฌ๋ํธ๋ค์ ํ๊ณ ๋ด๋ ค ๊ฐ๋ฉฐ ์ฌ์ฉ์ฒ๊น์ง ํ๋กญ์ค๋ก ์ ๋ฌํด์ผ ํ๋ค ๋ณด๋ฉด context api๋ ๋ฆฌ๋์ค ๋ฑ์ ๋๊ตฌ๋ค๋ ์ข ์ข ํ์ฉํ๊ธฐ๋ ํ๋ค. ํ์ง๋ง ์ด๋ฐ ๋ฐฉ๋ฒ๋ค์ ์ฝ๋๋ฅผ ๋ณต์กํ๊ฒ ํ๊ฑฐ๋ ์ข ๊ณผ๋ํ ์ ๊ทผ์ผ ๋๊ฐ ์๋ค.
๐ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ์ง ์๊ณ ๋ฆฌ์กํธ ์์ฒด์ ๊ธฐ๋ฅ์ผ๋ก๋ ํ๋กญ์ค ์ ๋ฌ์ ํจ์จ์ ์ผ๋ก ํ ์ ์๋ ๋ฐฉ๋ฒ๋ค์ด ์๋๋ค. ๋จผ์ ์ปดํ์ด๋ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ ๋งํ ๋ฌธ์ ์ํฉ์ธ prop drilling๊ณผ ๋ฆฌ์กํธ ๋ด๋ถ์ ์ผ๋ก ํด๊ฒฐํ ์ ์๋ ๋ฐฉ๋ฒ๋ค์ธ context api์ ์ปดํ์ด๋ ์ปดํฌ๋ํธ์ ๋ํด ์ดํด๋ณด์.
๐ช Prop drilling
"drilling"์ด๋ผ๋ ๋ง์์ ์ ์ถํ ์ ์๋ฏ์ด ๋๋ฆด๋ก ๊ตฌ๋ฉ์ ๋ซ๋ ๊ฒ์ฒ๋ผ, ์๋ฌด๋ฐ ๊ด๊ณ๋ ์๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์ฒ๊น์ง ์ ๋ฌํ๊ธฐ ์ํด ์ฌ์ด์ ์ปดํฌ๋ํธ๋ค์ ๋ซ๊ณ ๊ฐ ๊ฒฝ์ฐ๋ฅผ prop drilling์ด๋ผ๊ณ ํ๋ค. ์ฆ, prop drilling ๋ ์ค์ฒฉ๋ ์ปดํฌ๋ํธ ๋ ์ด์ด๋ค ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ณ์ ์ ๋ฌํ์ฌ, ๊น์ด ์ค์ฒฉ๋ ์ฌ์ฉ์ฒ ์ปดํฌ๋ํธ๊น์ง ์ ๋ฌํ๋ ๊ฒฝ์ฐ๋ฅผ ์ผ์ปซ๋๋ค. ์ด๋ ๊ฒ ์ ๋ณด๋ฅผ ์ ๋ฌํ๋ค ๋ณด๋ฉด ์ฌ์ฉ์ฒ์ ๋๋ฌํ๊ธฐ๊น์ง ๋ถํ์ํ ์ค๊ฐ ๋จ๊ณ ์ปดํฌ๋ํธ๋ค์ ๊ฑฐ์ณ๊ฐ์ผ ํ๋ค.
์ข ์ข ์ค์ฒฉ๋ ์ปดํฌ๋ํธ๋ค์ ๋ค๋ฃจ๋ค ๋ณด๋ฉด prop drilling์ ๊ฒฝํํด ๋ดค์ ์ ์๋ค.
์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ drop down ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ ๋ค๊ณ ํด๋ณด์.
Solution 1 : Context API
Context API๋ context provider๋ก ๊ฐ์ผ ์ฌ๋ฌ ์ปดํฌ๋ํธ๋ค ๊ฐ์ state๊ณผ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ ์ ์๋๋ก ํด์ค๋ค.
useContext ํ ์ ํตํด ํด๋น state๋ context์ ์ ๊ทผํ ์ ์๋๋ก ํ๋ค.
์์ drop down ์ปดํฌ๋ํธ๋ฅผ context api๋ก ๊ฐ๋จํ๊ฒ ํ๋ฒ ๋ง๋ค์ด๋ณด์.
- DropDownContext.jsx : ๋๋กญ๋ค์ด ์ด๋ฆผ/๋ซํ state ๊ด๋ฆฌ
import React, { createContext, useContext, useState } from 'react';
// Create the context
const DropdownContext = createContext();
// Create a custom hook to use the DropdownContext
const useDropdown = () => {
return useContext(DropdownContext);
};
// Create the provider component
const DropdownProvider = ({ children }) => {
const [isOpen, setIsOpen] = useState(false);
const toggleDropdown = () => {
setIsOpen(prevState => !prevState);
};
const value = {
isOpen,
toggleDropdown,
};
return (
<DropdownContext.Provider value={value}>
{children}
</DropdownContext.Provider>
);
};
export { DropdownProvider, useDropdown };
- DropDown.jsx : ๋๋กญ๋ค์ด ์ ์ฒด๋ฅผ ๊ฐ์ ๋ฉ์ธ ์ปดํฌ๋ํธ
import React from 'react';
const Dropdown = ({ children }) => {
return (
<div className="dropdown">
{children}
</div>
);
};
export default Dropdown;
- DropdownItems.jsx : ๋๋กญ๋ค์ด ์ ํ ํญ๋ชฉ๋ค
import React from 'react';
const DropdownItems = () => {
const items = ['Item 1', 'Item 2', 'Item 3'];
return items.map(item => <p key={item}>{item}</p>);
};
export default DropdownItems;
- DropdownMenu.jsx : ๋๋กญ๋ค์ด ์ด๋ฆผ/๋ซํ ์ ๋ฐ๋ผ ๋ณด์ผ ๋ฉ๋ด
import React from 'react';
import { useDropdown } from './DropdownContext';
const DropdownMenu = ({ children }) => {
const { isOpen } = useDropdown();
return (
isOpen ? (
<div className="dropdown-menu">
{children}
</div>
) : null
);
};
export default DropdownMenu;
- DropdownToggle.jsx : ๋๋กญ๋ค์ด ์ด๋ฆผ/๋ซํ ๋ฒํผ
import React from 'react';
import { useDropdown } from './DropdownContext';
const DropdownToggle = ({ children }) => {
const { toggleDropdown } = useDropdown();
return (
<button className="dropdown-toggle" onClick={toggleDropdown}>
{children}
</button>
);
};
export default DropdownToggle;
- App.jsx : ์ฌ์ฉ
import React from 'react';
import { DropdownProvider } from './DropdownContext';
import Dropdown from './Dropdown';
import DropdownToggle from './DropdownToggle';
import DropdownMenu from './DropdownMenu';
import DropdownItems from './DropdownItems';
const App = () => {
return (
<div className='App'>
<DropdownProvider>
<Dropdown>
<DropdownToggle>Toggle Dropdown</DropdownToggle>
<DropdownMenu>
<DropdownItems />
</DropdownMenu>
</Dropdown>
</DropdownProvider>
</div>
);
};
export default App;
contextAPI๋ฅผ ํตํด์ dropdown ์ฌ๋ซ์ ์ํ๋ฅผ ๊ณต์ ํ ์ ์๊ฒ ๋๋ค. Prop drilling ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
๐ react playground : https://playcode.io/1961630
ํ์ง๋ง contextAPI๋ก ์์ฑํ์ ๋ ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ์ ๋ค์ด ์์ ์ ์๋ค.
1. ์ฑ๋ฅ ์ด์ : context value๋ฅผ ์ ๋ฐ์ดํธํ ๊ฒฝ์ฐ, ๋ถํ์ํ ๋ฆฌ๋๋๋ง์ด ๋ฐ์ํ ์ ์๋ค.
๋ฆฌ์กํธ์์๋ ์์ ์ปดํฌ๋ํธ์์ state ๋ณํ๊ฐ ๋ฐ์ํ๋ฉด ๋ฌด์กฐ๊ฑด ๋ฆฌ๋๋๋ง์ด ๋ฐ์ํ๋ค. ์ด๋ ๊ฒ ๊ณต์ ํ context๊ฐ app ์ ์ฒด์ ์ฐ์ผ ๊ฒฝ์ฐ, ์ฌ๊ธฐ์ ๊ธฐ์ ์์น ์์ ๋ฆฌ๋๋๋ง์ด ๋ฐ์ํ ์ ์๋ค๋ ๋ฌธ์ ๊ฐ ์๋ค.
2. ํ ์คํธ ๋ณต์ก๋ ์ฆ๊ฐ : ํ๋กญ์ค ๊ธฐ๋ฐ์ผ๋ก state ๊ด๋ฆฌ๋ฅผ ํ๋ ๊ฒฝ์ฐ๋ณด๋ค ํ ์คํธ๋ฅผ ํ๊ธฐ๊ฐ ๊น๋ค๋กญ๋ค. mock๋ ์ ์ ํ context value๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด ์ถ๊ฐ์ ์ผ๋ก ์ธํ ์ ํด์ผ ํ๊ณ ์ ๋ ํ ์คํธ ๊ณผ์ ์ ๋ ๋ณต์กํ๊ฒ ๋ง๋ค ์ ์๋ค.
3. ๋ฌด๋ถ๋ณํ ๋จ์ฉ ๊ฐ๋ฅ์ฑ ์ฆ๊ฐ : state๊ด๋ฆฌ๊ฐ ๋๋ฌด ์ฌ์์ง๋ค๋ ์๊ฐ์ ๋ชจ๋ ์ปดํฌ๋ํธ์ state๋ฅผ context API๋ก ๊ณต์ ํ๋ฉด ๋์ง ์๋ ์ถ์ ์ ์๋ค. ํ์ง๋ง ์ปดํฌ๋ํธ ๊ฐ์ ๊ฒฐํฉ๋๊ฐ ๋๋ฌด ๋์์ ธ์ ์ฝ๋๋ฅผ ์ดํดํ๊ธฐ๋ ์ ์งํ๊ธฐ๋ ์ด๋ ค์์ง๋ค.
์์ ๊ฒฝ์ฐ์์๋ ํ๋์ ๊ธฐ๋ฅ์ ์ํด ์ฝ๋ ์ฌ๊ธฐ์ ๊ธฐ๋ฅผ ๋๋๋ค์ด์ผ ํ๋ค๋ ๋ถํธํจ์ ๋๊ผ์ ์๋ ์๋ค.
4. ํ์ ์์ ์ฑ ๊ฒฐ์ฌ : Context value๋ค์ ๋ํดํธ๋ก ํ์ ์ฒดํฌ๊ฐ ๋์ง ์์์ ์ปดํ์ผ๋ฌ๋ ๊ฐ๋ฐ ๋๊ตฌ๋ค์ด context value์ ํ์ ์ด ๋ถ์ ์ ํ๊ฒ ๋ณ๊ฒฝ๋๊ฑฐ๋ ์ฌ์ฉ๋์ด๋ ๊ฐ์งํ์ง ์์ ์๋ ์๋ค.
5. ํ์ฅ์ฑ ๊ฒฐ์ฌ : ๊ท๋ชจ๊ฐ ์๋ ์๋น์ค์ ๊ฒฝ์ฐ context api ๊ด๋ฆฌ๊ฐ ์ด๋ ค์ธ ์ ์๋ค. ์ฌ๋ฌ ์์กด์ฑ๊ณผ context๋ฅผ ์ ๋ฐ์ดํธํ๋ ๊ณผ์ ์์ ์ค๋ณต๊ณผ ์ผ๊ด์ฑ์ ํค์น๋ ์ผ์ด ๋ฐ์ํ ์ ์๋ค.
6. ๊ฐ๋ฐ ๋๊ตฌ์ ๋ฏธ๋ค์จ์ด ๋ถ์ฌ : ๋ฆฌ๋์ค์ ๊ฐ์ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋๊ตฌ๋ค๊ณผ๋ ๋ฌ๋ฆฌ context api ์์ฒด์ ์ผ๋ก ๋๋ฒ๊น ์ด๋ ํ ์คํธ ๋ฑ์ ๋์์ค ๋๊ตฌ๋ค์ด ์๋ค๋ ์ ์์ ์์ฝ๋ค.
Solution 2 : Compound Components
Compound Component๋ ์ด๊ฑฐ ์ปดํฌ๋ํธ๋ค์ด ์๋ฌต์ ์ธ state๋ฅผ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ณต์ ํ๋๋ก ์ค๊ณํ๋ ๋ฆฌ์กํธ ํจํด์ด๋ค.
์ฌ๋ฌ ์ปดํฌ๋ํธ๋ค ๊ฐ์ ๊ฐ์ state๊ณผ ๋ก์ง์ ๊ณต์ ํด์ผ ํ ๊ฒฝ์ฐ ์ด ํจํด์ ํ์ฉํ ์ ์๋๋ฐ,
์ด์ contextAPI๋ก ์์ฑํ๋ ๋๋กญ๋ค์ด ์ฝ๋๋ฅผ Compound Components๋ก ๊ตฌํํด ๋ณด์.
- Dropdown.jsx : ๋๋กญ๋ค์ด ๊ด๋ จ Compound Components
import React, { useState } from 'react';
// Dropdown component
const Dropdown = ({ children }) => {
const [isOpen, setIsOpen] = useState(false);
const toggleDropdown = () => {
setIsOpen(prevState => !prevState);
};
return (
<div className="dropdown">
{children.map((child,index) => React.cloneElement(child,{ isOpen, toggleDropdown })
)}
</div>
);
};
// DropdownToggle component
const DropdownToggle = ({ children, toggleDropdown }) => {
return (
<button className="dropdown-toggle" onClick={toggleDropdown}>
{children}
</button>
);
};
// DropdownMenu component
const DropdownMenu = ({ children, isOpen }) => {
return isOpen ? <div className="dropdown-menu">{children}</div> : null;
};
const DropdownItems = () => {
const items = ['Item 1', 'Item 2', 'Item 3'];
return items.map(item => <p key={item}>{item}</p>);
};
Dropdown.Toggle = DropdownToggle;
Dropdown.Menu = DropdownMenu;
Dropdown.Items = DropdownItems;
export default Dropdown;
โ ๏ธ ์ฌ์ค React.cloneElement ์ฌ์ฉ์ ๊ถ์ฅํ์ง ์๋๋ค. ๋ค์ ์๊ฐ์๋ ๋์ฒดํ์ฌ ๊ตฌํํ ์ ์๋ ๋ฐฉ๋ฒ๋ค์ ์ดํด๋ณด์.
<div className="dropdown"> {children.map((child,index) => React.cloneElement(child,{ isOpen, toggleDropdown }) )} </div>โ
- App.jsx : ์ฌ์ฉ
import React from 'react';
import Dropdown from './Dropdown';
export function App() {
return (
<div className='App'>
<Dropdown>
<Dropdown.Toggle>Toggle Dropdown</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Items/>
</Dropdown.Menu>
</Dropdown>
</div>
);
}
๐ react playground : https://1961651.playcode.io
์ด์ contextAPI๋ฅผ ์ฌ์ฉํ์ ๋๋ณด๋ค ๊น๋ํ๊ณ ์ง๊ด์ ์ด๋ค. ๊ทธ๋ฆฌ๊ณ Dropdown.Toggle์ ํ ๊ธ ๋ฒํผ ๊ธฐ๋ฅ๋ง, Dropdown.Menu๋ ์ด๋ ธ์ ๋๋ง ๋ฉ๋ด๋ค์ ๋ณด์ฌ์ฃผ๋ ๊ธฐ๋ฅ๋ง, Dropdown.Items๋ ์์ดํ ๋์ด ๊ธฐ๋ฅ๋ง ์๊ธฐ ๋๋ฌธ์ ๊ด์ฌ์ฌ ๋ถ๋ฆฌ๊ฐ ์๋๋ค.
Compound Components๋ฅผ ์ฌ์ฉ ์ ์ฅ์ ๋ค๋ก ์ ๋ฆฌํ ์ ์๋ค.
1. ์ฌ์ฌ์ฉ์ฑ ์ฆ๊ฐ : Compound component ๋ด ๊ฐ๋ณ ์ปดํฌ๋ํธ๋ฅผ ์ฌ๋ฌ ๊ตฐ๋ฐ ์ฌ์ฌ์ฉํ ์ ์์ด์ ์ฝ๋ ๋ณต์ก๋๋ฅผ ์ค์ด๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ฝ๋ค.
2. ์ ์ฐ์ฑ ์ฆ๊ฐ : ํ๋กญ์ค ์ ๋ฌ ๊ฑฑ์ ์ ์ค์ด๊ณ ์ปค์คํ ํํ๊ณ ์ฌ์ฌ์ฉํ๊ธฐ ์ฝ๊ฒ ์ปดํฌ๋ํธ๋ค์ ๊ตฌ์ฑํ ์ ์๋ค.
3. ๊ด์ฌ์ฌ ๋ถ๋ฆฌ : Compound component์ ๊ฐ ์์ ์ปดํฌ๋ํธ๋ ํ๋์ ๊ธฐ๋ฅ์ ๋ด๋นํ๊ธฐ ๋๋ฌธ์ ์ ์ฒด ์ฝ๋์ ์ํฅ์ ์ค ๊ฑฑ์ ์์ด ์ ์ง๋ณด์๋ฅผ ํ ์ ์๋ค.
4. ์ง๊ด์ API : ๊ฐ ๊ด๋ จ ์์ ์ปดํฌ๋ํธ๋ค์ ๋ถ๋ชจ ์ปดํฌ๋ํธ๋ก ๊ฐ์ธ์ฃผ๊ธฐ ๋๋ฌธ์ ๋๋ฃ ๊ฐ๋ฐ์๋ค ๋ํ ์ดํดํ๊ธฐ ์ฌ์ด ํจํด์ด๋ค.
5. ์ปค์คํฐ๋ง์ด์ง์ ์ฉ์ด : Compound component ํจํด์ ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ณ๊ฒฝํ๊ฑฐ๋ ํ์ฅํ๊ธฐ ์ฝ๋๋ก ํ๋ค. ์๋ฅผ ๋ค๋ฉด, ์ด์ ์ Item์ ๋ฐ๊พธ๊ณ ์ถ์ ๋ Dropdown.Items๋ง ๋ณ๊ฒฝํด ์ฃผ๋ฉด ๋๋ค.
6. Prop Drilling ๊ฐ์ : props๋ฅผ ํตํด state๋ฅผ ์ ๋ฌํ๋ ๋์ ์๋ฆฌ๋จผํธ๋ค์ ์์์ผ๋ก ์ ๋ฌํ๋ค.
7. ์ ์ง๋ณด์ ์ฉ์ด : props๋ฅผ ์ถ์ ํ๋ ๊ณผ์ ๋ ์ค์ด๊ณ , state ๊ณต์ ๋ฐฉ์์ด ๋ ๋จ์ํ๊ธฐ ๋๋ฌธ์ ๋๋ฒ๊น ์ด ์ฌ์์ง๋ค.
๐ ๋ง์น๋ฉด์
์ค๋์ prop drilling์ด ์ด๋ค ๊ฑด์ง, ๊ทธ๋ฆฌ๊ณ ๋ฆฌ์กํธ ๋ด๋ถ์ ์ธ ๊ธฐ๋ฅ๋ค๋ก ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ๋ค ์ค contextAPI์ compound components pattern์ ๋ํด ์ดํด๋ดค๋ค.
๋ค๋ฆ์ด ์๋๋ผ vinylify ํ๋ก์ ํธ๋ฅผ ํ๋ฉด์ compound components ๋ฐฉ์์ผ๋ก ๊ตฌํํ ์ ์๋ค๋ ๊ฑธ ์ฒ์ ์๊ฒ ๋์๋ค.
์์ธ๋ก ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ค์ฌ์จ๋ค๋๊ฐ ๋ณต์กํ ๋ฌด์ธ๊ฐ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ๋ณด๋ค๋ ์ ์ ํ ๋ด๋ถ ๊ธฐ๋ฅ์ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์๊ฐ๋ ์๊ฐ์ด์๋ค.
vinylify์์์ compound components ํ์ฉ ๋ํด์๋ ํ๊ณ ๊ฒธ ๋ค์ ์๊ฐ์ ๋ค๋ค๋ณด๋๋ก ํ๊ฒ ๋ค.
๐ References
https://react.dev/reference/react/useContext
https://medium.com/@vitorbritto/react-design-patterns-compound-component-pattern-ec247f491294