오늘의 나는 무엇을 잘했을까?
시간을 잘 맞추어 일어났고, 제 시간에 활동에 참여했다. 또, 새로운 라이브러리를 배우며 나름의 즐거움도 느꼈던 것 같다. 새로운 걸 배우는건 꽤 짜릿하다(물론 이해가 잘 되면).
오늘의 나는 무엇을 배웠을까?
지난 수요일의 프로젝트 kick-off를 시작으로 '보유 가상화폐 가치 계산' 서비스를 제공하는 웹 애플리케이션 제작이 시작되었다. 수~금까지는 회의를 통해 컴포넌트를 어떻게 나눌지 어느 컴포넌트에 어떤 기능이 들어가야 할지를 개략적으로 정하고 토요일에 맡을 컴포넌트를 정한 뒤 구현을 시작하였다.
우리 팀 웹 앱의 구조는 다음과 같다.
App
├─ GNB // 네비게이션 바 컴포넌트
│ ├─ Logo // 로고 및 서비스 이름 컴포넌트
│ ├─ div
│ │ ├─ Button // inputBoard 입력 값 및 계산된 데이터 초기화
│ │ ├─ Select // 화폐 단위 변경
│ │ │ ├─ Option
│ │ │ └─ Option
│ │ └─ Button // 검색 기록 모달 창 띄워주기
│ └─ HistoryModal // 검색 기록 모달 컴포넌트
├─ InputBoard // 사용자의 입력을 받는 컴포넌트
├─ MainBoard // 차트 및 계산 결과를 보여주는 컴포넌트
└─ MarketPriceTable // 가상화폐 시세 테이블 컴포넌트
©generated by Project Tree Generator
나는 이번 프로젝트의 GNB 파트를 담당하게 되었는데, 내가 구현해야 할 기능은 다음과 같다.
먼저, 지난 주말 GNB 컴포넌트의 틀을 잡아 놓았다. GNB 내부에는 앞서 프로젝트 트리에서 볼 수 있듯 Logo, Button 컴포넌트를 따로 만들어 삽입하였고, 화폐 단위를 변경하는 select 태그는 재사용이 필요 없을 것 같아 jsx 그대로 넣어 주었다.
어제 자정에 PR을 올려 오늘 팀원들에게 리뷰를 받았고, 함수나 변수명, 구현 방식 등등에 대한 좋은 의견을 들을 수 있었다.
그래서 오늘은, 리뷰 받은 내용을 바탕으로 리팩토링을 진행하고 검색 기록을 띄워줄 모달창 구현을 시작하였다. 모달창을 어떻게 만들 수 있을까 생각을 해보고 자료를 찾아보다 보다 쉽게 제작할 수 있게 해주는 'react-modal' 이라는 라이브러리가 있어서 간단히 공부해 보았다.
react-modal - npm package info
모달(Modal)은 기존의 브라우저 페이지 위에 새로운 창이 아닌 레이어가 더해지는 것을 말한다. 부모 창 위로 새로운 자식 창이 레이어 형태로 띄워지며, 자식 창이 닫히기 전까지 부모 창과의 상호작용이 불가능하다는 특징을 가지고 있다.
GNB 컴포넌트에는 검색 기록을 모달 창으로 띄워주는 버튼이 있다. 이 버튼을 클릭하면 로컬 스토리지에 저장되어 있던 현재까지의 검색 기록을 모달창에 정리하여 보여준다. 원래 모달은 index.html의 독립적인 요소에 붙어 메인 프로그램과 병렬적으로 작동해야 하는데, 이를 좀 쉽게 구현하는 것을 도와주는 것이 react-modal이고, 사용 방법을 정리해 보려 한다.
먼저, 라이브러리를 프로젝트 디렉토리에 설치해 주어야 한다.
$ npm install --save react-modal
$ yarn add react-modal
이후 라이브러리를 호출하고 Modal 컴포넌트를 사용하면 된다.
import Modal from 'react-modal';
function App() {
return (
<Modal isOpen={isOpen} onRequestClose={()=> setIsOpen(false)}>
모달 창 내부 구조 작성
</Modal>
)
}
Modal은 Children으로 내부 템플릿을 전달하는 형식으로 동작한다. Modal 컴포넌트 태그 사이에 템플릿을 작성해주면 Children prop으로 화면에 렌더링 된다. 보통, Modal을 관리하는 컴포넌트를 따로 만들어 사용하는 것이 권장된다고 한다. 이 컴포넌트에서는 Modal의 디자인, 동작을 정의해주면 된다.
Modal 내부에서 사용하는 prop들이 있는데 그 중 중요한 몇가지를 정리해 보면
isOpen
: 모달이 화면에 보일지 여부에 대한 속성이다. true
일 경우 화면에 보이고 false
일 경우 화면에 보이지 않는다.contentLabel
: 시각장애인용 스크린리더에서 모달 창을 어떻게 부를지 정의한다.onRequestClose
: ESC키 또는 모달 밖의 공간을 클릭했을 때 창을 종료시키는 함수를 정의한다. 콜백 형식 또는 외부에서 함수를 정의하고 함수 이름을 전달하는 방식으로 사용된다.추가로, Modal의 setAppElement
메소드를 통해 모달 렌더링의 기준이 되며, 모달 창이 열렸을 때 사용자가 다른 컴포넌트를 보지 않게 하기 위해 숨겨줄 element를 지정해 줘야 한다. 보통 리액트 앱을 렌더링 해주는 root element를 동일하게 지정해 주면 된다.
이 정도로 간단하게 라이브러리에 대해 알아보고 코드에 바로 적용해 보았다.
코드는 아래와 같다.
import { useCallback } from 'react';
import Modal from 'react-modal';
import Button from './Button';
const modalStyle = {
overlay: {
top: '76px',
right: '52px',
backgroundColor: 'none',
},
content: {
position: 'absolute',
overflow: 'auto',
top: 0,
right: 0,
left: 'auto',
bottom: 'auto',
border: '1px solid #E7E9F0',
borderRadius: '16px',
boxShadow: '0px 4px 15px rgba(11, 14, 27, 0.08)',
zIndex: 10,
width: '520px',
height: '590px',
},
};
function HistoryModal({ isOpen, handleNotClicked, handleModalOpen }) {
const handleModalClose = useCallback(() => {
handleModalOpen(false);
handleNotClicked(false);
}, []);
return (
<Modal
style={modalStyle}
isOpen={isOpen}
onRequestClose={() => handleModalClose()}
>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<h1 style={{ fontSize: '20px', fontWeight: 700 }}>검색 기록</h1>
<Button propStyle={{ border: 'none' }} name="기록 모두 지우기" />
</div>
</Modal>
);
}
export default HistoryModal;
Modal.setAppElement('#root');
일단 지금은 스타일 적용을 대충 해놓았기 때문에 인라인으로 지정해 주었다. 스타일 부분은 무시해도 좋을 것 같다.
HistoryModal 컴포넌트가 삽입될 상위 컴포넌트인 GNB에 isOpen
이라는 state를 만들고, true
일 때 화면에 렌더링 되고, false
일 때 화면에서 사라지도록 구현하였다. 따로 창을 닫는 버튼을 만들지는 않았고, 모달 창 외부를 클릭하거나 escape 키를 눌렀을 때 isOpen
state를 false
로 설정해주는 함수 handleModalClose
를 정의해 주었다. handleNotClicked(가명)
은 검색 기록 버튼의 디자인을 바꿔주기 위해 만들어본 state notClicked(가명)
의 setter 함수이다.
이렇게 모달의 뼈대를 구성해 두었고, 검색 기록이 로컬스토리지에 저장되는 기능을 구현하면 그 데이터를 배열 형식으로 가져와 모달 안에서 map
메소드를 통해 한 줄씩 보여주면 될 것 같다.
아직 확실하지는 않지만, 해보면 알 지 않을까.... 생각한다.
오늘의 나는 어떤 어려움이 있었을까?
뭔가 어려움이 있으면서 없는 것 같은 기분이다. GNB 컴포넌트가 App 쪽에서 다뤄야 할 state나 context를 사용해야 할 것 같아 그게 정해지기 전까지는 컴포넌트 틀을 만드는 것 이외에는 뭔가 더 할 수가 없는게 좀 아쉽긴 하다. 내일 한번 이야기를 해봐야겠다.
내일의 나는 무엇을 해야할까?