컴포넌트를 잘 만드는 것은 곧 유지보수가 쉽다는 것을 의미합니다. 컴포넌트를 잘 만들기 위해서는 어떻게 해야할까요?
먼저 컴포넌트는 재사용이 가능하고 계속해서 변경되는 요구사항을 반영하는데 발생하는 부수효과를 최소화 하는 것입니다.
이러한 경우 우리는 Headless를 알아볼 필요가 있습니다. Headless는 UI와 데이터 중 UI부분이 없어진 것이라고 생각하면 이해하기 쉽습니다. 즉 컨텐츠만 남아있는 것이죠. Input 컴포넌트를 예로 들어봅시다.
const CustomInput = () => {
const [input, setInput] = useState("")
const handleChange = (e) => {
setInput(e.target.value)
}
return (
<div className="input-container">
<label>CustomInput</label>
<input type="text" value={input} onChange={handleChange} />
</div>
)
}
위의 코드는 평범하게 Input 컴포넌트를 만들어 본 것입니다. 하지만 위 Input컴포넌트가 요구사항이 바뀌어 스타일이 바뀐다면 어떻게 해야할까요?
Context API를 통해 컴포넌트 내부에서 공유될 데이터를 정의합니다.
const InputContext = React.createContext({
id: "",
value: "",
type: "text",
onChange: () => {},
})
const InputWrapper = ({id, value, type, onChange, children}) => {
const contextValue = {id, value, type, onChange};
return (
<InputContext.Provider value={contextValue}>
{children}
</ InputContext.Provider>
)
}
const Input = ({...props}) => {
const {id, value, onChange, type } = React.useContext(InputContext)
return (
<input id={id} value={value} type={type} onChange={onChange} {...props} />
)
}
const Label = ({children, ...props}) => {
const {id} = React.useContext(InputContext)
return (
<label htmlFor={id} {...props}>
{children}
</label>
)
}
InputWrapper.Input = Input
InputWrapper.Label = Label
const test = () => {
...
const [name, setName] = useState("")
const handleChange = (e) => {
setName(e.target.value)
}
return (
<div>
<InputWrapper>
<InputWrapper.Input />
<InputWrapper.Label>Name</ InputWrapper.Label>
</InputWrapper>
</div>
)
}
위 방식으로 컴포넌트를 만들면 사용하는 곳에서 하위 컴포넌트들을 명확하고 자유롭게 볼 수 있습니다.
자식에 어떤 것이 들어올지 모른다고 가정하고 데이터 로직 만을 갖는다.
해당 데이터 로직을 자식 함수에 주입한다.
const InputHeadless = ({ chidren }) => {
const [value, setValue] = useState("");
const handleChange = (e) => {
setValue(e.target.value)
}
return children({
value,
onChange: handleChange,
})
}
export default InputHeadless;
const test = () => {
...
return (
<div>
<InputHeadless>
{({ value, onChange }) => {
return (
<div>
<label htmlFor="1">Name</label>
<input type="text" id="1" value={value} onChange={onChange} />
</div>
)
}}
</InputHeadless>
</div>
)
}
우리가 많이 사용하는 Custom Hook 패턴도 Headless이다.
const test = () => {
const {value: name, onChange: handleChange} = useInput();
return (
<div className="input-container">
<label htmlFor="1">Name</label>
<input type="text" id="1" value={name} onChange={handleChange} />
</div>
)
}