React 컴포넌트 잘 만들기(feat: 관심사의 분리)

sxxng_ju·2023년 1월 6일
0

컴포넌트를 잘 만드는 것은 곧 유지보수가 쉽다는 것을 의미합니다. 컴포넌트를 잘 만들기 위해서는 어떻게 해야할까요?

먼저 컴포넌트는 재사용이 가능하고 계속해서 변경되는 요구사항을 반영하는데 발생하는 부수효과를 최소화 하는 것입니다.

이러한 경우 우리는 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컴포넌트가 요구사항이 바뀌어 스타일이 바뀐다면 어떻게 해야할까요?

1. Compound Component 패턴

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>
    )
}

위 방식으로 컴포넌트를 만들면 사용하는 곳에서 하위 컴포넌트들을 명확하고 자유롭게 볼 수 있습니다.

2. Function as Children Component 패턴

자식에 어떤 것이 들어올지 모른다고 가정하고 데이터 로직 만을 갖는다.
해당 데이터 로직을 자식 함수에 주입한다.

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>
    )
}

3. Custom Hook 패턴

우리가 많이 사용하는 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>
    )
}

0개의 댓글