recoen.

[리액트 고급패턴] props getters pattern

리액트의 고급 패턴인 Props Getters Pattern에 대해서 알아보겠습니다. 해당 패턴을 사용하면 custom hook을 사용할 때, 훨씬 더 높은 재사용성과 확장성을 가져갈 수 있습니다. 바로 예제부터 살펴보겠습니다.

Props Getters 패턴을 사용하지 않았을 때

먼저 Props Getters 패턴을 사용하지 않은 예시부터 살펴보겠습니다.

import React, { useState } from "react";

const useToggle = initialValue => {
  const [value, setValue] = useState(initialValue);
  const toggleValue = () => setValue(!value);
  return [value, toggleValue];
};

function Toggle() {
  const [value, toggleValue] = useToggle(false);
  return (
    <div>
      <button onClick={toggleValue}>Toggle me</button>
      <p>{value ? "ON" : "OFF"}</p>
    </div>
  );
}

export { Toggle };

이렇게 Toggle을 만드는 컴포넌트를 작성하고 있다고 가정하겠습니다. 여기서 저희는 useToggle이라는 custom훅을 사용중입니다. 무슨 문제가 있나요? 사실 딱히 문제라고 할 것은 없습니다. custom 훅을 이용해서 Toggle이라는 컴포넌트로부터 로직을 분리하고, useToggle훅은 다른곳에서도 이용할 수 있게 되었습니다. 그런데 말입니다. 만약에 다른 곳에서 사용할 때는 로직을 약간 수정해서 사용하고 싶다면 어떨까요? 기존의 로직은 그래도 가져가되 약간만 수정해서 사용하고 싶다면요?


Props Getters 패턴을 사용했을 때

그럼 이제 Props Getters 패턴을 사용했을 때의 예시를 살펴보겠습니다.

import React, { useState } from "react";

const useToggle = initialValue => {
  const [value, setValue] = useState(initialValue);
  const toggleValue = () => setValue(!value);
  const getToggleProps = customProps => ({
    "aria-pressed": value,
    onClick: toggleValue,
    ...customProps
  });
  return { value, getToggleProps };
};

function Toggler() {
  const { value, getToggleProps } = useToggle(false);
  return (
    <div>
      <button {...getToggleProps()}>Toggle me</button>
      <p>{value ? "ON" : "OFF"}</p>
    </div>
  );
}

export { Toggler };

useToggle 부분이 조금 수정된 것을 확인할 수 있습니다. 우선 getToggleProps라는 함수가 생겼습니다. 이 함수는 customProps를 인자로 받고, 그 안에서는 특정 객체를 반환합니다. 그런데, 이 객체에는 저희가 기본적으로 사용하고 싶은 props를 넣어둡니다.

그리고 만약에 customProps가 들어오면 해당 props가 오버라이딩될 것입니다.(만약 새로운 props라면 그대로 추가되겠죠?) 이제 useToggle 훅이 훨씬 더 확장성 있게 되었습니다.


장점 :

그래서 이 패턴을 사용해서 얻을 수 있는 장점이 무엇일까요? 앞에서 언급한 바와 같이 이 패턴을 사용하면 custom hook을 더욱 확장성 있게 사용할 수 있습니다. 기본적으로 사용하고 싶은 props들을 미리 정의해놓고, 이 훅을 사용하는 곳에서 동작을 약간 수정하고 싶더거나, 기능을 추가하고 싶은 경우에는 customProps에 전달해주면 됩니다. 놀랍지 않나요?

단점 :

장점만 알아볼 수는 없죠. 이 패턴의 단점은 없을까요? 주의해야 할 점은요? 약간의 단점이 있다면, 코드의 복잡성이 증가한다는 점일 것 같습니다. 그 외에는 테스트할 때의 난이도가 조금 올라간다는 것..?!


또 다른 예시는 없나요? :

있습니다. 모달에 적용한 예시를 소개해보겠습니다.

import React, { useState } from "react";
import styled from "styled-components";

const useModal = initialValue => {
  const [isOpen, setIsOpen] = useState(initialValue);
  const closeModal = () => setIsOpen(false);
  const openModal = () => setIsOpen(true);
  const getModalProps = customProps => ({
    isOpen,
    onClose: closeModal,
    onOpen: openModal,
    ...customProps
  });
  return { isOpen, getModalProps };
};

function Modal({ children, ...props }) {
  const { isOpen, getModalProps } = useModal(false);
  return (
    <ModalWrapper {...getModalProps(props)}>
      <ModalInner>{children}</ModalInner>
    </ModalWrapper>
  );
}

export { Modal };

const ModalWrapper = styled.div`
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  visibility: ${props => (props.isOpen ? "visible" : "hidden")};
  opacity: ${props => (props.isOpen ? 1 : 0)};
  transition: all 0.3s;
`;

const ModalInner = styled.div`
  background: white;
  padding: 20px;
  border-radius: 10px;
  box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
`;

이렇게 Modal 컴포넌트를 정의하고, 아래와 같이 사용합니다.

function App() {
  const handleOpenModal = () => {
    console.log("Open modal");
  };

  return (
    <div>
      <button onClick={handleOpenModal}>Open modal</button>
      <Modal isOpen={false}>This is the content of the modal</Modal>
    </div>
  );
}

export default App;

결론 :

props getters 패턴을 이용해 custom hook의 재사용성과 확장성을 높여봅시다✨