Compound Components(컴파운드 컴포넌트란?)

gun·2023년 5월 22일
8

개요

이 포스팅을 통해 언제, 왜 Compound Components를 사용하고 무엇이 이점인지 파악할 수 있습니다.

Compound Components (컴파운드 컴포넌트란?)

Compound Components는 React의 강력한 기능 중 하나입니다. Compound Components를 사용하면 유연하고 재사용 가능한 컴포넌트를 설계할 수 있으며, 코드의 가독성과 유지 보수성을 높일 수 있습니다. 이번 포스팅에서는 Compound Components의 개념과 사용법에 대해 알아보겠습니다.

그래서 왜?

그 기술을 사용하기 전 왜 사용하는지 먼저 알아야 합니다.

Compound Components는 headless component입니다.

즉 props로 내려오는 데이터가 없고, 내부에서 데이터를 처리합니다.

headless하게 작성하는 방법은 많은데 Compound Components를 꼭 사용해야 하는 이유가 무엇일까요? 다음 설명에서 알아보겠습니다.

Form Component

Compound Components는 headless하다고 말씀 드렸습니다.

대표적인 예로 title과 input 그리고 감싸고 있는 wrapper를 만들어 보겠습니다.

	function Input({ type, value, onChange, setIsFocus }) {
      const onChangeInFocus = () => {
      	setIsFocus(true);
      }
      
      const onChangeOutFocus = () => {
      	setIsFocus(false);
      }
      
    	return <input 
      			type={type} 
                value={value} 
				onChange={onChange} 
				onFocus={onChangeInFocus} 
				onBlur={onChangeOutFocus}
      			/>
    }
        
    function Title({ title }) {
    	return <div>{title}</div>
    }
      
    function Wrapper({ isFocus, children }) {
    	return (
          <div ... isFocus를 이용한 wrapper css 변화>
            {children}
          </div>
        )
    }

	function InputComponent({ title, userName, setUserName }) {
      
      const [isFocus, setIsFocus] = useState(false)
      
      const onChange = (e) => {
      	const { value } = e.target;
        
        setUserName(value);
      }
      
      return (
      	<Wrapper isFocus={isFocus}>
          <Input 
            onChange={onChange} 
            value={userName} 
            setIsFocus={setIsFocus}
			type='text'
		  />
          <Title title={title}/>
        </Wrapper>
      )
    }
    
    function IndexPage () {
      const title1 = '난 아이디';
      const [userName, setUserName] = useState('')
      const title2 = '난 비밀번호';
      const [userName, setUserName] = useState('')
      
      return (
        <div>
          <InputComponent 
          title={title1} 
          userName={userName} 
          setUserName={setUserName}/>
          <InputComponent 
          title={title2} 
          userName={userName} 
          setUserName={setUserName}/>
        </div>
      )
    }

간단한 InputComponent를 작성해 보았습니다.
이 코드는 어떤가요?
디자인 시스템에서 onFocus는 우리가 알아야 할까요?

InputComponent를 사용하기 위해서는 코드를 좀더 자세히 봐야 합니다.

우리는 개발을 하며 다양한 상황에 직면합니다.

프론트엔드 개발자👨‍💻 - 개발 완료 했어요😀
디자이너👨‍🎨 - ㅇㅇ님 혹시 이거랑 이것만 input, title위치 변경 가능 할까요?
프론트엔드 개발자👨‍💻 - 가능은 한데,,,, (새로운 inputcompoenent를 만들어야 하나..?)

이러한 상황에서 우리는 조금 더 유연하게 대처 할 수 없을까요?


// ...?
function firstTitleInputComponent({ title, userName, setUserName }) {
      
      const [isFocus, setIsFocus] = useState(false)
      
      const onChange = (e) => {
      	const { value } = e.target;
        
        setUserName(value);
      }
      
      return (
      	<Wrapper isFocus={isFocus}>
          <Input 
            onChange={onChange} 
            value={userName} 
            setIsFocus={setIsFocus}
			type='text'
		  />
          <Title title={title}/>
        </Wrapper>
      )
    }

위 같은 상황에서 Compound Components를 사용할 수 있습니다.

Compound Components 사용

// CompoundFormInput, CompoundFormLabel
function CompoundFormInput({ onChange }) {
  const { setIsFocus } = useCompoundFormContext();

  const onClickInFoucs = () => {
    setIsFocus(true);
  };
  const onClickOutFocus = () => {
    setIsFocus(false);
  };

  return (
    <input
      onChange={onChange}
      onFocus={onClickInFoucs}
      onBlur={onClickOutFocus}
    />
  );
}


function CompoundFormLabel({ title }: ICompoundFormLabelProps) {
  return <div>{title}</div>;
}


export default CompoundFormInput;


// CompoundForm

function CompoundForm({ children }: ICompoundFormProps) {
  const [isFocus, setIsFocus] = useState(false);

  return (
    <CompoundFormContext.Provider value={{ isFocus, setIsFocus }}>
      <div > //focus를 이용한 css 변화..
        {children}
      </div>
    </CompoundFormContext.Provider>
  );
}

CompoundForm.Input = CompoundFormInput;
CompoundForm.Label = CompoundFormLabel;
export default CompoundForm;

내부 상태 관리를 위한 contextAPI

const CompoundFormContext = createContext(null);

export const useCompoundFormContext = () => {
  const context = useContext(CompoundFormContext);

  if (!context) {
    throw new Error("CompoundForm.* 컴포넌트만 사용 가능합니다.");
  }
  return context;
};

export default CompoundFormContext;

Use

function App() {
  const onChangeCompoundInput = (e) => {
    const { value } = e.target;
    console.log(value);
  };
  
  return (
    <div className="App">
      <CompoundForm>
    // 위치 이동이 자유로움
        <CompoundForm.Label title="테스트" />
        <CompoundForm.Input onChange={onChangeCompoundInput} />

      </CompoundForm>
    </div>
  );
}

export default App;

추상화를 통해 기능을 분리하고 디자인 시스템 내부적으로 동작하는 onFocus효과 같은 경우 내부에서 알아서 작동하도록 수정 했습니다.

우리가 알지 않아도 되는 상태들을 내부에서 관리함으로써 우리는 Form을 굉장히 쉽게 사용할 수 있게 되었습니다.

Input, Label에 Props를 내려주는 이유

다양한 코드를 보며, headless한 컴포넌트를 작성하기 위해 아래 코드처럼 작성해 사용 하시는 분들도 많습니다.

<div className="App" >
      <CompoundForm onChange={onChangeCompoundInput} title="테스트">
        <CompoundForm.Input  />
        <CompoundForm.Label  />
      </CompoundForm>
    </div>

input, label에 props를 내려주는 이유는 Form특성 상 여러 input과 label이 존재 할 수 있기 때문입니다.

<div className="App">
      <CompoundForm>
        <CompoundForm.Input onChange={onChangeUserPhoneNumber} />
        <CompoundForm.Label title="전화번호" />
        // 추가
        <CompoundForm.Input onChange={onChangeUserName} />
        <CompoundForm.Label title="이름" />
      </CompoundForm>
    </div>

마무리 하며

어떤가요? 이제 Compound Components를 사용하여 직접 간단한 컴포넌트를 만들 수 있을것 같나요?

Compound Components는 순서 변경, Css추가, 일관된 디자인, 다양한 상황 대응에 유리한 컴포넌트 입니다.

개발을 하며, 다양한 상황 때문에 디자인, 기획이 변경 되고, 페이지 마다 어울리는 위치, 순서가 있기 때문에 우리는 변경에 유연한 컴포넌트를 작성 해야 할 때 한번 Compound Components를 사용해 작성 해보시길 바랍니다.

전체 코드를 확인 해주세요. style의 변화는 git에서 클론 받아 한번 확인해 보세요.

0개의 댓글