리액트의 고급 패턴인 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의 재사용성과 확장성을 높여봅시다✨