컴포넌트를 잘만든다 -> 유지보수를 용이하게 한다.
컴포넌트를 잘 설계하는 방법
<해결하고자 하는 문제>
<목표>
<컴포넌트의 역할>
이름을 입력받는 Input컴포넌트를 작성해보았다.
const SomeInput = () => {
const [value, setValue] = useState('')
const handleChangeValue = e => {
setValue(e.target.value)
}
return (
<div className="input-container">
<label htmlFor="1">NAME</label>
<input id="1" type="text" value={value} onChange={handleChangeValue} />
</div>
)
}
이 컴포넌트를 위에서 작성한 컴포넌트의 역할에 따라 나눠 보자면
const [value, setValue] = useState('')
내부데이터인 상태 <label htmlFor="1">NAME</label>
<input id="1" type="text" value={value} onChange={handleChangeValue} />
</div>
const handleChangeValue = e => {
setValue(e.target.value)
}
요구사항에 따라 스타일링도 하고, 잘 동작하는 인풋컴포넌트를 하나 만들었다고 치자.
그런데 다음날 기획자가 변경된 ui디자인을 요구한다면 어떻게 될까?
아까 만든 Someinput컴포넌트를 재사용하기는 어려울것 같다. 그러면
만약 데이터를 다루는 로직과 UI로직을 분리한다면 새로운 UI가 와도 이전에 만든 컴포넌트를 재사용해서 만들수 있지 않을까?
UI -> Head : 컨텐츠를 보여주는 방법
데이터 -> Body: 컨텐츠
데이터만 남긴 컴포넌트를 headless 컴포넌트라고 한다.
< 남길 부분 >
< 없앨 부분 >
import { useState, createContext, useContext } from 'react'
const InputContext = createContext({
id: '',
value: '',
type: 'text',
onChange: () => {},
})
//context API를 통해서 컴포넌트 내부에서 공유될 상태(데이터)를 정의한다.
const InputWrapper = ({ id, value, type, onChange, children }) => {
const contextValue = { id, value, type, onChange }
return (
<InputContext.Provider value={contextValue}>
{children}
</InputContext.Provider>
)
}
//부모 컴포넌트를 생성하여 위의 contextAPI를 공유한다.
const Input = ({ ...props }) => {
const { id, value, type, onChange } = useContext(InputContext)
return (
<input id={id} value={value} type={type} onChange={onChange} {...props} />
)
}
const Label = ({ children, ...props }) => {
const { id } = useContext(InputContext)
return (
<label htmlFor={id} {...props}>
{children}
</label>
)
}
//자식 컴포넌트들을 만든다. 이때 자식 컴포넌트 들은 contextAPI를 통해 필요한 상태를 공유받는다.
InputWrapper.Input = Input
InputWrapper.Label = Label
//(선택) 자식 컴포넌트들을 부모 컴포넌트의 프로퍼티로 등록한다.
//명시적으로 InputWrapper와 Input, Label이 부모자식관계라고 나타낼수 있다.
App.js에서 사용시
//APP.js에서 사용시
function App(){
const [name, setName] = useState('');
const handleChangeName = (event) => {
setName(event.target.value);
}
return(
<div>
<InputWrapper
id='name'
value={name}
type="text"
onChange = {handleChangeName}
>
<InputWrapper.Input />
<InputWrapper.Label >Name</InputWrapper.Label>
</InputWrapper>
</div>
)
}
사용처에서는 InputWrapper의 하위 컴포넌트들을 자유롭게 볼 수 있고, 위치를 수정하여 마크업을 수정할 수 도 있다.
const App = () => {
const {value: name, onChange: onChangeName} = useInput();
return (
<div className="input-container">
<label htmlFor="1">NAME</label>
<input id="1" type="text" value={name} onChange={onChangeName} />
</div>
)
}
자식에 어떤것이 들어올지 모른다고 가정한다.
InputHeadless 컴포넌트에는 데이터 로직만 같는다. 해당 데이터 로직을 자식 함수에 주입한다.
const InputHeadless = ({children}) => {
const [value, setValue] = useState('');
const handleChangeValue = (event) => {
setValue(event.target.value);
}
return children({
value,
onChange: handleChangeValue,
})
}
export default InputHeadless;
그러면 사용처에서는 매개변수로 받는 데이터 로직을 마크업에 따라 마음껏 사용할 수 있다.
function App() {
return (
<div>
<InputHeadless>
{({ value, onChange }) => {
return (
<div className="input-container">
<label htmlFor="1">NAME</label>
<input id="1" type="text" value={value} onChange={onChange} />
</div>
)
}}
</InputHeadless>
</div>
)
}
<참고한 영상>
토스ㅣSLASH 22 - Effective Component 지속 가능한 성장과 컴포넌트
https://www.youtube.com/watchv=aAs36UeLnTg&t=440s