항해 16기/Week I Learned

[항해 28일차] WIL_React 심화주차: 모달, 버튼을 포함한 웹 페이지

해갈 2023. 9. 7. 13:39

들어가며

08/25부터 3주간에 걸쳐 진행될

[Chapter 3] 주특기 주차가 시작되어

Lv.1 ~ 5 까지 페어 프로그래밍으로 과제가 진행되고 있습니다.

이번주는 Lv.3 페어과제로 모달과 버튼을 포함한 웹 페이지를 페어를 이루어

주어진 요구사항에 맞게 만들어 보는 과제를 화요일, 수요일(09/05, 09/06) 진행했습니다.

웹 페이지에서는 과제를 수행하기 위해

Redux-Toolkit, Styled-Components 의 props children, position(css) 의 개념을 이용했습니다.

 

과제 요구사항

  • Modal 2개를 구현합니다.
    • overlay 를 클릭했을 때 모달이 닫히는 형태와 그렇지 않은 형태
  • Buuton 6개를 구현합니다.
    • styled-components 를 이용해 props 를 적절하게 활용합니다.
    • label 에 아이콘을 추가합니다.
  • Input 2개를 구현합니다.
    • 화페 단위, 콤마 , 가 찍히는 input
  • Select 를 2개 구현합니다.
    • select 를 클릭했을 때 부모 요소에 의해 가려지지 않는 select 와 그렇지 않은 형태
  • 예시 : https://hh99-react-lv3.vercel.app/
 

React App

 

hh99-react-lv3.vercel.app

 

과제를 수행하며

Lv.3 모달 웹페이지를 구현하면서 className 을 일일이 수동으로 관리할 필요 없이 React 의 props 나 전역 속성을 기반으로 컴포넌트에 스타일 속성을 부여하기 때문에 Styled-components 가 간단하고 직관적인 강점을 갖고 있다는 점을 알게 되었습니다.

또한, 제가 생각하는 Redux-Toolkit 의 장점으로는

Redux 의 단점으로 꼽히는 보일러플레이트 코드가 줄어든다는 것입니다.

보일러플레이트 코드가 많으면, 코드의 예측가능성 측면에서 떨어지고, 코드해석이 어려워져, 휴먼에럴나 실수를 유발시킬 수 있습니다.

 그리고, 패키지의 의존성을 줄여줍니다.

리덕스를 사용하다보면, redux devtool, immer, reselect 등 여러가지 라이브러리들을 설치하게 됩니다. 하지만, Redux-Toolkit 에는 많은 라이브러리들이 내장되어 있어 다른 라이브러리들의 의존성을 줄일 수 있다고 생각합니다.

https://github.com/yngjnhykk/react_Lv.3-css

 

GitHub - yngjnhykk/react_Lv.3-css

Contribute to yngjnhykk/react_Lv.3-css development by creating an account on GitHub.

github.com

https://react-lv-3-css.vercel.app/

 

React App

 

react-lv-3-css.vercel.app

 

react_Lv.3-css info

수행한 프로젝트의 기능들을 폴더별로 설명해보겠습니다.

  • components
    • LeftModal : 닫기 버튼과, 확인 버튼이 포함된 모달창으로, 모달의 외부영역을 클릭해도 모달창이 닫히지 않습니다.
    • RightModal : 어떤 버튼 없이, 외부 영역을 클릭하면 닫히게 되는 모달창입니다.
    • LeftSelect : select 의 option 창이 부모 요소로 보이는 박스 바깥으로 보이는 select 입니다.
    • RightSelect : select 의 option 창이 부모 요소로 보이는 박스 바깥으로는 보이지 않는 select 입니다.
  • modules
    • reduxer.js : select 에서 클릭한 값을 initialState 로 설정하고, option 창에서 클릭한 값을 전역 변수로 관리하는 곳입니다.
  • App.js : 메인 페이지입니다.

 

LeftModal 

closeModal

닫기 버튼을 클릭했을 때, 모달을 닫는 기능을 하는 함수로 App.js 에서 false 를 초기값으로 useState 로 관리했던 leftModalOpen 변수를 LeftModal 컴포넌트의 props 로 받아와 모달을 켜기 위해 App.js 에서 true 로 바꾼 leftModalOpen 을 모달을 닫기 위해  다시 false 로 바꿉니다.

// 왼쪽 모달의 초기값 상태 관리
  const [leftModalOpen, setLeftModalOpen] = useState(false);

// 모달을 띄우기 위해 상태를 true 로 바꾼 함수, showLeftModal
 const showLeftModal = () => {
    setLeftModalOpen(true);
  };
  
// 모달 끄기
  const closeModal = () => {
    setLeftModalOpen(false); 
  };

 

RightModal

stopPropagation

모달의 외부 영역을 가리키는 div 태그에 LeftModal 컴포넌트의 닫기 버튼에 쓰인 방법과 같이 closeModal 함수를 onClick 으로 선언합니다. 하지만, 이렇게 되었을 때 외부 영역을 클릭하면, 모달이 닫히지만, 외부영역뿐 아니라 div 태그 안에 들어 있는 자식 태그의 모든 부분에 onClick = { closeModal } 이 적용됩니다. 이를 막기 위해 부모태그로의 이벤트 전파를 stop 중지하는 자바스크립트 내장 메서드인 stopPropagation 을 사용해 부모 컴포넌트인 div 태그에서 자식 태그인 모달의 div 태그로 이벤트가 전파되는 것을 방지 기능을 구현합니다.

<div className='wrap' onClick={closeModal}>
      <div
        className='container'
        onClick={(e) => {
          e.stopPropagation();
        }}
      >
        <p>우측 모달창입니다.</p>
      </div>
    </div>

 

LeftSelect

onClickSelect

LeftSelect 는 왼쪽 select 의 option 창을 렌더링하는 컴포넌트입니다. 때문에 Option 을 선택했을때, 해당 태그의 value 를 innerText 를 통해 slected 로 변수선언하고, 이를 Redux-Toolkit 으로 선언한 전역 UPDATE1 리듀서 함수를 호출하기 위해 해당 액션타입을 반환하는 update_select1 을 import 해와 dispatch 로 전역함수를 호출합니다.

LeftSelect.jsx

import React from 'react';
import { styled } from 'styled-components';
import { useDispatch } from 'react-redux';
import { update_select1 } from '../redux/modules/reducer';

function LeftSelect({ showNHideLeftSelect }) {
  const dispatch = useDispatch();

  const OnClickSelect = (e) => {
    const selected = e.target.innerText;
    dispatch(update_select1(selected));
    showNHideLeftSelect();
  };
  return (
    <LeftSelectStyle>
      <Option onClick={OnClickSelect}>리액트</Option>
      <Option onClick={OnClickSelect}>자바</Option>
      <Option onClick={OnClickSelect}>스프링</Option>
      <Option onClick={OnClickSelect}>리액트 네이티브</Option>
      <Option onClick={OnClickSelect}>타입스크립트</Option>
      <Option onClick={OnClickSelect}>넥스트 제이에스</Option>
      <Option onClick={OnClickSelect}>노드 제이에스</Option>
    </LeftSelectStyle>
  );
}

 

reducer.js

import { createSlice } from '@reduxjs/toolkit';

export const update_select1 = (selected) => {
  return {
    type: UPDATE1,
    selected: selected,
  };
};

export const update_select2 = (selected) => {
  return {
    type: UPDATE2,
    selected: selected,
  };
};

const initialState = {
  select1: '리액트',
  select2: '리액트',
};

const counterSlice = createSlice({
  name: 'reducer',
  initialState,
  reducers: {
    UPDATE1: (state, action) => {
      state.select1 = action.selected;
    },

    UPDATE2: (state, action) => {
      state.select2 = action.selected;
    },
  },
});

export const { UPDATE1, UPDATE2 } = counterSlice.actions;
export default counterSlice.reducer;

하지만, 여기서 끝이 아닙니다. 중요한 기능이 한 가지 남았는데요. select 의 option 창이 부모태그의 영역으로 보이는 박스의 바깥 부분에서도 보이게 css 코드로 구현해야 합니다. 방법은 의외로 간단했는데요. position 속성을 이용해 LeftSelect 컴포넌트의 부모 태그를 body 로 하게끔 해서 left 와 top 으로 위치를 맞춰 조정해주었습니다. 이를 위해 LeftSelect 컴포넌트는 가장 바깥에서 태그를 붙여주었습니다.

function App() {
  (...)
  return (
    <div>
      <Button>
        (...)
      </Button>
      <h1>Input</h1>
      <Input>
        (...)
      </Input>
      <Modal>
        (...)
      </Modal>
      <Select>
        (...)
      </Select>
      {leftSelectOpen && (
        <LeftSelect showNHideLeftSelect={showNHideLeftSelect} />
      )}
    </div>
  );

 

RightSelect

RightSelect 도 LeftSelect 와 기능적인 면으로는 마찬가지입니다. 하지만, overflow: hidden 이라는 css 코드를 이용하기 위해 position 속성을 이용해 부모태그를 Select 태그로 갖기 위해 Select 태그 안에서 선언했습니다.

function App() {
  (...)

  return (
    <div>
      <Button>
        (...)
      </Button>
      <h1>Input</h1>
      <Input>
        (...)
      </Input>
      <Modal>
        (...)
      </Modal>
      <Select>
        (...)
        {rightSelectOpen && (
          <RightSelect showNHideRightSelect={showNHideRightSelect} />
        )}
      </Select>
      {leftSelectOpen && (
        <LeftSelect showNHideLeftSelect={showNHideLeftSelect} />
      )}
    </div>
  );