728x90

https://ant.design/components/modal

 

Modal - Ant Design

An enterprise-class UI design language and React UI library with a set of high-quality React components, one of best React UI library for enterprises

ant.design

 

antd라이브러리에는 자체적인 Modal 컴퍼넌트를 제공합니다.

라이브러리가 굉장히 친절해서 아래 사진의 노란 버튼을 누르면 소스코드도 보여줍니다.

해당 url의 api부분에서 할당가능한 attribute 목록을 확인할 수 있습니다.

이 중에서 저는 footer( 아래에 있는 버튼2개)을 제거하고 esc와 외부 클릭으로 나갈 수 있는 모달을 만들려고 했습니다.


만들어진 모양은 아래와 같습니다.

작성코드

import { Modal } from 'antd';
import { ReactNode } from 'react';
// import { styled } from 'styled-components';

interface CommonModalProps {
	isModalOpen: boolean;
	children: ReactNode;
	setIsModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
}

const CommonModal: React.FC<CommonModalProps> = ({ setIsModalOpen, isModalOpen, children }) => {
	return (
		<div>
			<Modal open={isModalOpen} footer={null} onCancel={() => setIsModalOpen(false)} keyboard centered maskClosable>
				{children}
			</Modal>
		</div>
	);
};

export default CommonModal;

이때 할당된 각각의 attribute를 설명하면 아래와 같습니다

open : true가 할당되면 모달이 열립니다.

footer 하단의 버튼 2개 (cancle 와 ok)를 보여줄 지 선택합니다.

onCancel : 닫기 기능을 수행할 함수를 할당합니다. 이떄 저는 isModalOpen 변수로 열기 떄문에 이를 false로 만드는 함수를 할당했습니다)

keyboard : esc 누를 시 onCancel 함수 실행

centerd : 모달 가운데 정렬

maskClosable : 화면 밖의 영역 누를 시   onCancel 함수 실행

 

api읽을 때는 maskClosable이 true이면 외부 영역 클릭시 닫힌다고 적혀 있어서 시간을 좀 썼습니다... onCancel이 중요한데,,


이후 UserHome 컴퍼넌트 내부의 header 컴퍼넌트에 있는 버튼을 통해서 모달을 여닫습니다.

const UserHome = () => {
	const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
	const [modalChildren, setModalChildren] = useState<React.ReactNode>(<div></div>);
	return (
		<LayoutWrapper>
			<Header setIsModalOpen={setIsModalOpen} setModalChildren={setModalChildren} />
			<Nav />
			<Outlet />
			<CommonModal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen}>
				{modalChildren}
			</CommonModal>
			<Footer />
		</LayoutWrapper>
	);
};

modalChildren 을 통해서 하위에 컴퍼넌트를 받으려 합니다 (이를 통해서 로그인 회원가입 컴퍼넌트를 교체하려고 합니다.)

하지만 이 코드에선 큰 문제점이 있죠.

 

바로 isModalOpen 의 상태를 통해서 모달을 여닫기 떄문에 다른 컴퍼넌트의 리랜더링 문제가 생깁니다.

따라서 이를 해결하기 위해서 isModalOpen 상태를 전역변수로 관리하기로 했습니다.

                                                                                                                                           (2부에서 계속)

 

모달 컴퍼넌트 랜더링 관리하기

728x90

브라우저마다 다른 css가 적용되어 있는 경우도 있고, 혹시 모를 경우를 방지하기 위해서 기본 브라우저의 css를 없애줄 필요가 있다. 이 작업을 실행하지 않는다면, 각 브라우저마다 내가 작업한 웹 페이지의 css가 조금씩 차이가 날 수 있다!

https://meyerweb.com/eric/tools/css/reset/
방법은  결국  모든 태그들에 대해서 기본css를 지정하는 것 이다. 해당 코드들은 위의 링크를 통해서 확인할 수 있다.

사이트마다 초기세팅이 어떻게 되었는지 정확히 알 수 없기 떄문에, 모든 태그에 대해서 등록하는것!

 

이를 쉽게 하기 위해서  styled-reset 라이브러리를 사용하는 방법도 있지만, 저는 제 프로젝트에서 styled-components 라이브러리를 사용하기 때문에 해당 라이브러리의 createGloablStyle을 활용하였다.

 

위의 링내용을 GLobalStyle에 등록한다

import { createGlobalStyle } from 'styled-components';

/// rest css 용
const GlobalStyle = createGlobalStyle`
html, body, div, span, applet, object, iframe,
  h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  a, abbr, acronym, address, big, cite, code,
  del, dfn, em, img, ins, kbd, q, s, samp,
  small, strike, strong, sub, sup, tt, var,
  b, u, i, center,
  dl, dt, dd, menu, ol, ul, li,
  fieldset, form, label, legend,
  table, caption, tbody, tfoot, thead, tr, th, td,
  article, aside, canvas, details, embed,
  figure, figcaption, footer, header, hgroup,
  main, menu, nav, output, ruby, section, summary,
  time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
  }

  /* HTML5 display-role reset for older browsers */
  article, aside, details, figcaption, figure,
  footer, header, hgroup, main, menu, nav, section {
    display: block;
  }

  /* HTML5 hidden-attribute fix for newer browsers */
  *[hidden] {
    display: none;
  }

  body {
    line-height: 1;
  }

  menu, ol, ul {
    list-style: none;
  }

  blockquote, q {
    quotes: none;
  }

  blockquote:before, blockquote:after,
  q:before, q:after {
    content: '';
    content: none;
  }

  table {
    border-collapse: collapse;
    border-spacing: 0;
  }

  //style custom
  * {
    box-sizing: border-box;
  }

  body {
    font-family: 'Source Sans Pro', sans-serif;
    background-color: ${(props) => props.theme.bgColor};
    color: ${(props) => props.theme.textColor}
  }

  a {
    text-decoration: none;
  }
	`;

export default GlobalStyle;

이때 저는 추후  사이트의 테마 색 까지 고려하여서 themeprovider도 같이 적용하였습니다.

 

import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';

import { basicTheme } from './assets/styles/theme';
import App from './App';
import GlobalStyle from './assets/styles/GlobalStyle';

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
	<ThemeProvider theme={basicTheme}>
		{/* <QueryClientProvider client={client}> */}
		<BrowserRouter>
			<GlobalStyle />
			<App />
		</BrowserRouter>
		{/* </QueryClientProvider> */}
	</ThemeProvider>,
);

index.tsx 폴더에 ThemProvide를 등록하고 <GlobalStyle/>을  호출하면 적용할 수 있습니다.

    background-color: ${(props) => props.theme.bgColor};
    color: ${(props) => props.theme.textColor}

GlobalStyle의 위 코드는 테마 적용시 기본색을 바꾸기 위해서 props 형식으로 내려받도록 하였습니다.

우선 저희 프로젝트에서는 색을 아래와 같이 지정하였습니다.

import { DefaultTheme } from 'styled-components';

// colors  TODO: 변수명 수정, 추후 테마 변경 적용하려면 코드 추가
export const color = {
	color1: '#845EC2',
	color2: '#A178DF',
	color3: '#BE93FD',
	color4: '#DCB0FF',
	color5: '#FACCFF',
};

// 추후 테마색 결정
export const basicTheme: DefaultTheme = {
	bgColor: 'white',
	textColor: 'black',
};

추후 테마색을 적용하기 위해선 color를 수정해야겠습니다.


728x90

axios에서는 api 호출시에 중복되는 코드나 기능을 생략할 수 있는 axiosinstance라는 기능이 있습니다.
이를 활용하면 코드의 유지보수성과 확장성을 얻을 수 있습니다.

axios 공식 홈페이지가 설명이 굉장히 친절하게 되어 있어서 직접 확인 하는 것도 좋습니다.
https://axios-http.com/kr/docs/intro

 

import axios, { InternalAxiosRequestConfig } from 'axios';

const axiosInstance = axios.create({
  baseURL: 'http://localhost:3000/',
  timeout: 100000,
  headers: {
    'content-type': 'application/json;charset=UTF-8',
    accept: 'application/json,',
  },
});

axiosInstance.interceptors.request.use(
  (config: InternalAxiosRequestConfig<{ headers: string }>) => {
    const access_token = sessionStorage.getItem('authorization');

    // config.headers 초기화
    config.headers = config.headers || {};
    if (access_token !== null) {
      config.headers['Authorization'] = `${access_token}`;
    }
    return config;
  }
);

export default axiosInstance;

위 코드는  서버에 api호출시에 로그인을 했다면 저장된 토큰을 자동으로 전송하기 위해서 작성한 코드입니다.
이는 api호출시 request단계에서 매번 토큰을 불러오는 과정을 직접 입력할 필요가 없어지죠.

사실 axios 자체가 타입 추론이 잘되어서 별다른 코드를 작성하지 않고 타입가드 정도만 해도 정상적으로 작동합니다. 
하지만 이럴 경우 any 타입으로 선언이 되기 때문에 타입스크립트의 장점이 드러나지 않습니다.

따라서 저는 제가 사용하는 토큰 전송시에만 간단하게 타입 처리를 하였습니다.

초기 config는 InternalAxiosRequestConfig 타입에 any값이 제네릭에 들어오는 것을 알 수 있습니다.

저는 이때 config 안에 있는 header를 사용하기 때문에 

axiosInstance.interceptors.request.use(
  (config: InternalAxiosRequestConfig<{ headers: string }>) => {
    const access_token = sessionStorage.getItem('authorization');

    // config.headers 초기화
    config.headers = config.headers || {};
    if (access_token !== null) {
      config.headers['Authorization'] = `${access_token}`;
    }
    return config;
  }
);

로 수정했습니다.

이처럼 타입 추론이 굉장히 잘 되기 때문에, 원하시는 혹은 추가하고 싶은 타입이 있다면  해당 부분에 마우스를 올려서 어떤 값이 any로 들어오고 있는지 확인하고 이를 수정하시면 됩니다.

 


https://velog.io/@bang9dev/axios-interceptor-with-ts 에서 타입을 굉장히 꼼꼼히 쓰신 예시를 볼 수 있는데, 참고하시는 것도 좋을 것 같습니다!

 

Axios 인터셉터 타입스크립트에서 제대로 쓰기

Axios 에는 interceptors 라는 기능이 있다. 이를 통해서 request/response 에 선행, 후행 처리를 커스텀하게 할 수 있다. 하지만 interceptor 를 통해서 변경된 값이 타입에 적용되지는 않는데, 이를 멋지게

velog.io

 

+ Recent posts