React 12장 - Modal 버튼
포스트
취소

React 12장 - Modal 버튼

React

  • 클릭 시 알림창을 생성하는 Modal 버튼 만들기

컴포넌트 짜기

  • 컴포넌트를 나눈다.
  • ModalContainer : 전체를 포함하고 있는 컴포넌트 하나
  • ModalBtn : 모달 기능을 구현할 버튼 하나
  • ModalBackDrop : 검은색 배경을 구현할 컴포넌트 하나
  • ModalView : 알림창을 나타낼 컴포넌트 하나
1
2
3
4
5
6
7
8
return (
  <ModalContainer>
    <ModalBtn />
    <ModalBackDrop>
      <ModalView />
    </ModalBackDrop>
  </ModalContainer>
);

Styled-Components

  • ModalContainer

    • Modal 컴포넌트 안에 위치할 하위 컴포넌트들을 display:flexjustify-content:center,align-items:center을 통해 가운데 정렬을 한다.
    • position 값은 하위 컴포넌트의 position의 기준이 되기위해 사용한다.
  • ModalBtn

    • ModalContainer 안에 위치할 button인 ModalBtn 컴포넌트이다.
    • display,justify-content,align-items 동일
  • ModalBackDrop

    • ModalBtn을 눌렀을 시 BackDrop과 View를 보여줄 컴포넌트이다.
    • display,justify-content,align-items 동일
    • 상위 컴포넌트인 ModalConatiner의 position을 기준으로 절대적 위치값을 설정한다.
    • position:absolute를 사용하지 않을 시, ModalBtn과 같은 하위 컴포넌트이기 때문에 ModalBtn에 옆에 위치하게 된다.
    • 따라서, z-index 값을 부여하여 ModalBtn보다 앞에 위치하게 하며 전체 화면을 덮을 수 있도록 ModalContainer의 넓이값과 높이값을 모두 받아온다.
  • ModalView

    • ModalBackDrop 안에 위치할 알림창 컴포넌트이다.
    • ModalBackDrop의 하위 컴포넌트이기 때문에, ModalBackDrop의 justify-content,align-items에 의해 중앙 정렬된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const ModalContainer = styled.div`
  background-color: lightblue;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 20rem;
  position: relative;
`;

const ModalBtn = styled.div`
  background-color: blue;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 10px;
  cursor: grab;
`;

const ModalBackDrop = styled.div`
    background-color: rgba(0,0,0,0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%
    height: 100%
    position: absolute;
    left: 0;
    top: 0;
    z-index: 10;
`;

const ModalView = styled.div.attrs((props) => ({
  role: "dialog",
}))`
  background: white;
  display: felx;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 200px;
  height: 100px;
  > . closeBtn {
    cursor: pointer;
  }
`;

기능 구현

상태값 작성

  • ModalBtn을 눌렀을 때의 상태에 따라 ModalBackDrop과 하위 컴포넌트인 ModalView 컴포넌트가 나타났다,사라졌다를 반복한다.
  • ModalBtn이 클릭됐을 때 상태값이에 따라 전원을 키고 끄듯이 상태값이 달라져야하며, 상태값을 바꿔주기 위한 함수 onHandle을 작성해준다.
1
2
3
4
5
const [isModal, setModal] = useState(false);

const onHandle = () => {
  setModal(!isModal);
};

컴포넌트에 이벤트 핸들러 부여

  • ModalBtn을 클릭했을 때 onHandle함수를 실행시켜 isModal의 값이 바뀌게 한다.
  • 바뀐 값에 따라 ModalBackDrop과 ModalView가 나타나게 하는 조건을 건다.
1
2
3
4
5
6
7
8
9
10
11
12
13
return (
  <ModalContainer>
    <ModalBtn onClick={onHandle}>{!isModal ? "Open Modal" : "Opend"}</ModalBtn>
    {!isModal ? null : (
      <ModalBackDrop onClick={onHandle}>
        <ModalView>
          <div className="closeBtn">X</div>
          <div>알림창이 열렸습니다.</div>
        </ModalView>
      </ModalBackDrop>
    )}
  </ModalContainer>
);

다듬기

  • 위의 내용만 갖고도 컴포넌트와 기능 구현은 끝났다.
  • 문제점은 ModalBackDrop 컴포넌트를 클릭했을 때 실행되는 onHandle함수를, 하위 컴포넌트인 ModalView 컴포넌트가 그대로 상속받아 ModalView의 어느 영역을 클릭해도 onHandle함수가 실행된다는 것이다.
    • 이를 위해 stopPropagation메서드를 사용한다.
    • event.preventDefault()와 비슷한 의미를 가진 메서드로, 클릭이벤트가 발생했을 때, 이벤트가 전파되는 것을 막는 기능을 한다.
    • 사용자가 웹페이지 내의 버튼을 클릭했을 때, 버튼을 감싸고 있는 부모 태그들 또한 클릭 이벤트에 반응하게 되는데, 이것을 Bubble Up이라고 한다.
    • event.stopPropagation()을 사용하여 Bubble Up현상을 막는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
return (
  <ModalContainer>
    <ModalBtn onClick={onHandle}>{!isModal ? "Open Modal" : "Opend"}</ModalBtn>
    {!isModal ? null : (
      <ModalBackDrop onClick={onHandle}>
        <ModalView>
          <div
            className="closeBtn"
            onClick={(event) => {
              event.stopPropagation();
            }}
          >
            X
          </div>
          <div>알림창이 열렸습니다.</div>
        </ModalView>
      </ModalBackDrop>
    )}
  </ModalContainer>
);

stories 작성

  • 컴포넌트명과 stories.js 를 작성하면 컴포넌트의 stories로 인식한다.
  • Modal 컴포넌트를 불러온다.
  • 기본값의 titleExample의 폴더 안에 Modal 컴포넌트를 의미한다.
  • 기본값의 component는 Modal 컴포넌트를 지칭한다.
  • stories는 storybook
  • stories 설명보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from "react";
import { Modal } from "./components/Modal.js";

export default {
  title: "Example/Modal",
  component: Modal,
};

const Template = (args) => <Modal {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: "Modal",
};

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import { useState } from "react";
import styled from "styled-components";

const ModalContainer = styled.div`
  background-color: lightblue;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 20rem;
  position: relative;
`;

const ModalBtn = styled.div`
  background-color: blue;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 10px;
  cursor: grab;
`;

const ModalBackDrop = styled.div`
    background-color: rgba(0,0,0,0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%
    height: 100%
    position: absolute;
    left: 0;
    top: 0;
    z-index: 10;
`;

const ModalView = styled.div.attrs((props) => ({
  role: "dialog",
}))`
  background: white;
  display: felx;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 200px;
  height: 100px;
  > . closeBtn {
    cursor: pointer;
  }
`;

const [isModal, setModal] = useState(false);

const onHandle = () => {
  setModal(!isModal);
};

return (
  <ModalContainer>
    <ModalBtn onClick={onHandle}>{!isModal ? "Open Modal" : "Opend"}</ModalBtn>
    {!isModal ? null : (
      <ModalBackDrop onClick={onHandle}>
        <ModalView>
          <div
            className="closeBtn"
            onClick={(event) => {
              event.stopPropagation();
            }}
          >
            X
          </div>
          <div>알림창이 열렸습니다.</div>
        </ModalView>
      </ModalBackDrop>
    )}
  </ModalContainer>
);
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.