728x90

기본적으로 FullCalendar는 리액트전용라이브러리가 아니다 보니, 정해진 기능 외의 스타일을 줄 때 어렵다.

현재 제가 구상하는 것은 이벤트 있으면 달력에  cursor : not-allowed 효과를 주려고 한다. 

하지만 라이브러리 자체적으로  간간의 칸에서 이벤트 여부를 확인하는 기능이 없다.  이벤트 여부에 따른 이벤트 스타일링은 있어도 해당 날짜색을 미리 주는게 없다...

 

차선으로 있는 방법은 event나 date 클릭해서 직접 확인하는 방법 뿐,,https://fullcalendar.io/docs/dateClick

 

dateClick - Docs | FullCalendar

Triggered when the user clicks on a date or a time. function( dateClickInfo ) { } In order for this callback to fire, you must load the interaction plugin. If you are using an ES6 build system: import { Calendar } from '@fullcalendar/core'; import interact

fullcalendar.io

이를 해결하기 위해서 dom 요소에 직접적으로 접근해서 자식 노드에 이벤트가 존재한다면 스타일을 주려고 한다.

Ref에서 확인할 수 있나 싶어서 확인해봤지만 Calendar.ref에도 각각 날짜의 이벤트 여부가 기록되어 있진 않았다.

 

1. 우선 개발자도구를 통해서 각각의 div의 className을 확인했습니다.

그 결과 날짜별 컴퍼넌트의 className은 fc-daygrid-day-frame 이었고 calendar-unable 이라는 className을 추가해서 스타일을 주려고 합니다.

 

2. 이를 달력 컴퍼넌트가 렌더링 될 때, 자식에 event가 존재하면 calendar-unable 을 추가하려고 합니다

(현재는 커스텀훅으로 분리했습니다)

// useCalendarStyle.ts

import { useEffect } from 'react';
import { userType } from '../types';

const useCalendarStyle = (type: userType) => {
	useEffect(() => {
		if (type === 'owner') {
			const parentDiv = document.getElementsByClassName('fc-daygrid-day-frame');
			for (let i = 0; i < parentDiv.length; i++) {
				if (parentDiv[i].children[1].children.length > 1) {
					parentDiv[i].classList.add('calendar-unable');
				}
			}
		}
	}, [type]);
};

export default useCalendarStyle;

 

parentDiv는 동일한 className을 가진 div가 많기 때문에 배열로 들어옵니다.

이후 각각의 div를 반복문을 통해서 event가 있는지 호가인합니다.

parentDiv[i] = 날짜 객체

parentDiv[i].children  ==> header 와 events, bg가 들어있습니다.

				console.log(parentDiv[i]);

그리고 events 안에 
만약에 이벤트가 존재한다면  아래와 같이 fc-daygrid-event-harness  가 className 인 div가 추가됩니다.

따라서 childeren의 갯수를 통해서 이벤트여부를 판단하였습니다.  그리고 length가 1보다 크면 (이벤트 존재하면 ) class를 add하여서 style을 부여했습니다.

저희 프로젝트에서는 사실 반대로 예약가능한 날짜가 아닐떄 (등록가능한 이벤트가 없을 때 ) style을 줘야하기에 여기까지 develop 했지만 

 

위와 같이 코드를 짜면  시작일과 끝일이 다른 경우에 색이 부여되지 않는 점 유의해주세요..

서버로 부터 값을 받으면 위의 방법을 응용해서  style주고 싶은   날짜를 추상화해서 코딩을 하는 방법이 좋을 거 같아요.

 

728x90

https://velog.io/@tosspayments/React%EB%A1%9C-%EA%B2%B0%EC%A0%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0-ft.-%EA%B2%B0%EC%A0%9C%EC%9C%84%EC%A0%AF

 

React로 결제 페이지 개발하기 (ft. 결제위젯)

결제 페이지 개발하기 포스트에서 받은 뜨거운 반응에 힘을 입어 React 버전으로 돌아왔어요. 이번에도 많은 관심 부탁드려요. 🤗

velog.io

위 글을 바탕으로 구현해보았습니다. 개인적으로 코드 자체만 보는거는  깃헙을 통해 보는게 더 좋다고 생각해요!(제가 봤을때는 오타가 있었어요)
https://github.com/tosspayments/payment-widget-sample/blob/main/react/src/pages/Checkout.tsx

위 게시글은 테스트용이기 떄문에 이를 활용해서 실제 결제까지 진행해보려고 합니다.!
사업자 번호가 필요하네요,,?

프로젝트는 vite를 통해서 생성하였고, ts-swc로 하였습니다.
 우선 결제에 필요한 결제위젯 sdk와 router를 설치합니다.

npm i @tosspayments/payment-widget-sdk react-router-dom

일단,, 추후 보완해보겠습니다.
테스트 버전에서는 카카오페이는 지원하지 않는데 네이버 페이는 지원되네요,,

결제까지는 예시코드가 너무 잘 작성되어서 어렵지 않고, 추후에 응답관련해서는 아래 링크를 통해서 확인할 수 있따고 하네요.

결제방법 마다 결제금액을 얻는 방법이 다르지만, 조건문 통해서 얻으면 되겠고,,,

test버전이라도 1원 전송 이런거 가능할 줄 알고 설렜었는데, 아쉽네요. 혹시  추가단계를 진행하면 게시글 업데이트 하겠습니다..


https://docs.tosspayments.com/guides/payment-widget/integration#4-%EA%B2%B0%EC%A0%9C-%EC%99%84%EB%A3%8C-%ED%9B%84-%EC%9D%91%EB%8B%B5-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0

 

728x90

패캠을 초격차 패키지를 통해서 docker에 대해서 배웠는데, react 에서 어떻게 사용하는지 기록하기 위해서 게시글을 작성하였습니다.


FROM : 베이스 이미지를 가져옴 --> npm을 사용하기 위해서 node 이미지를 사용

WORKDIR : 컨터이너안에 어떠한 경로에 리액트를 위한 파일이 있는지 설치되어있는지 확인

COPY : package.json 파일을 카피해서 디렉토리에 적용

RUN : package.json을 이용해 npm install을 실행시켜줌 
COPY : ./    ./  한번 더 카피해서 DIR 안에 모든것을 넣어주는것

CMD  :  "npm" ,"run" ,"start"  

#docekerfile

FROM node:16-alpine

WORKDIR /usr/src/app

COPY package.json ./

RUN npm install

COPY ./ ./

CMD ["npm","run","start"]

docker를 적용시킬 프로젝트에 해당 파일을 생성하고
빌드하기 docker build ./
하면 아래와 같은 문구가 적힌다.

writing image sha256:c70afb1293c3d426504f182

이후에  docker run c70a 
하면 실행된다.

하지만 이렇게 실행이 성공이 되도 포트맵핑을 하지 않으면 우린 브라우저로 접속할 수 없다.!
(네트워크 연결이 안되어 있기 때문)

-p를 통해 포트 맵핑을 하면 사용가능하다.

docker run -p 3000:3000 c70





막연하게 패캠강의를 듣고 하다가 좀 더 자세히 알 기회 및 알고 싶어진 계기가 생겨서 추가 게시글을 작성하였습니다.
https://ungumungum.tistory.com/manage/newpost/29?type=post&returnURL=https%3A%2F%2Fungumungum.tistory.com%2F29

728x90
const AuthInput: React.FC<AuthInputType> = ({ title, password, setValue }) => {
    const [isVisible, setIsVisible] = useState(password);
    const [inputValue, setInputValue] = useState('');

    const toggleIsPassword = () => {
        setIsVisible(!isVisible);
    };

    const onChangeInput = (e: ChangeEvent<HTMLInputElement>) => {
        setValue(e.target.value);
    };

 로그인 및  회원가입에 사용할 인풋창을 위와 같은 로직으로 구현하였다.
문제가 없어 보일 수 있지만, 성능적으론 굉장히 문제가 있는 코드이다.

리익트 리랜더링 조건으로는  

  1. state 변경이 있을 때
  2. 새로운 props이 들어올 때,
  3. 부모 컴퍼넌트가 렌더링 될 때
  4. props가 업데이트 될 때

4가지로 주로 꼽는다.

 

위 코드와 같이 부모 컴퍼넌트의 setter를 받는 컴퍼넌트는 부모 컴퍼넌트의 state를 변경 시킨다.
이는 부모 컴퍼넌트의 리렌더링을 유발하고 이에 의해서, 부모 컴퍼넌트 아래의 모든 컴퍼넌트를 리 랜더링 시킨다.

따라서 리랜더링을 줄이기 위해서 debounce를 사용하여서 제어하려고 한다.
debounce는 지정한 delay시간안에 추가적인 매개변수값의 변화(입력값)가 없는 경우에만 return 하게 하여 
한번에 여러 글자를 일어나는 경우 렌더링 횟수를 줄이기 위해서 사용한다.
https://dev.to/manishkc104/debounce-input-in-react-3726

 

Debounce Input in React

Debouncing an input is a technique used to improve web application performance and user experience....

dev.to

위 게시글을 보면 우선 자식 컴퍼넌트로 입력값을 받고 이 값의 변화를 useEffect로 인지하고, setTimeout과 clearTImeout을 활용하여  제어하려한다.

React.useEffect(() => {
  const delayInputTimeoutId = setTimeout(() => {
    setDebouncedInputValue(inputValue);
  }, 500);
  return () => clearTimeout(delayInputTimeoutId);
}, [inputValue, 500]);

이를 활용하여 내 코드도 리랜더링을 제어하려한다.

하지만 디바운스의 경우 범용성이 높기 떄문에 커스텀훅으로 만들 것 이다.


import { useEffect, useState } from 'react';

export const useDebounce = (value: string, delay: number) => {
	const [debounceValue, setDebounceValue] = useState(value);
	useEffect(() => {
		const handler = setTimeout(() => {
			setDebounceValue(value);
		}, delay);

		return () => {
			clearTimeout(handler);
		};
	}, [value, delay]);

	return debounceValue;
};

이후  input의 값과 딜레이를 훅에 인수로 전달할 것이다.
그리고 useEffect를 통해서 debounceValue가 변할 떄마다 부모 컴퍼넌트의 setter를 통해서 값을 할당하면 된다.

const AuthInput: React.FC<AuthInputType> = ({ title, password, setValue }) => {
	const [inputValue, setInputValue] = useState('');

	const onChangeInput = (e: ChangeEvent<HTMLInputElement>) => {
		setInputValue(e.target.value);
	};
	const debouncedValue = useDebounce(inputValue, 300);

	useEffect(() => {
		if (debouncedValue) {
			setValue(debouncedValue);
		}
	}, [debouncedValue, setValue]);

	return (
		<AuthInputWrapper>
			<AuthInputTitle>{title}</AuthInputTitle>
			<AuthInputContainer>
				<AuthInputSheet type={isVisible ? 'password' : 'text'} onChange={onChangeInput} />



사실 위 커스텀훅을 만들고, onChangeInput 내에서 훅을 사용하려다가  일반 함수내에서 훅을 사용하려다가 훅 규칙을 위반해서 에러도 겪었었지만, 결국 완성,,

728x90
		dispatch(openModal({ modalComponent: 'auth', modalSize: 300 }));

위 코드는 현재 수정된 코드인데 이때 'auth'대신에    <AuthWrap/>  이라는 컴퍼넌트를 넘겼었다.

이럴 경우  

에러가 뜬다. 직렬화 관련 에러인데, store에 리엑트 컴퍼넌트를 저장하면 위와 같이 payload.$$typeof 값이 저장되는 이 값에 의해서 에러가 뜨는 것 ( 코드는 정상작동해서 늦게 알았습니다).

이를 해결하기 위해서 1. 배열안에 넣기 2. 객체 안에 넣기 등의 방법을 시도했는데 다 실패했습니다.

마지막으로 선택한 해결 방법은  컴터넌트를 저장하는 것이 아니라 string 값을 저장하고, CommonModal 컴퍼넌트에서 &&로 확인하는 것 입니다. 잉럴 경우 && 에 의해서 조건이 틀릴 경우 렌더링 되지 않기 때문에, 컴퍼넌트가 늘어놔도 큰 타격이 없다고 생각했습니다( 차선이라고 생각했습니다.)


<Modal
			bodyStyle={{ display: 'flex', justifyContent: 'center' }}
			open={modalState}
			footer={null}
			onCancel={onCloseModal}
			keyboard
			centered
			maskClosable
			width={size}
		>
			{modalComp === 'auth' && <AuthWrap />}
		</Modal>

 

 

	const modalOpen = () => {
		const modalSize = window.innerWidth >= 1000 ? 600 : 450;

		dispatch(openModal({ modalComponent: 'auth', modalSize: modalSize }));
	};



혹시 더 좋은 방법이 있따면 알려주시면 감사합니다..


728x90

모달에 스타일을  주려고 하는데, 초기에는 max-width와 min-width를  활용해서 
최대 크기는  80vw 최소 크기는 400 px로 설정하려고 했다. ( 모달 크기도 화면의 크기에 따라서 조정하고 싶었습니다)
따라서 기존에 작성된 코드에서 width를 매개변수로 전달 받아서 스타일을 주려고 했는데,  모달에 최대사이즈와 최소사이즈를 주는 법을 찾지 못하였습니다.



문제해결 방법 1
우선 Modal antd 홈페이지에서 api를 살펴보았습니다.  (https://ant.design/components/modal)
하지만  width 키워드는 존재했지만, max-width와 min-width는 찾지 못하였습니다.
이를 해결하기 위해서 stackoverflow에도 검색을 하였는데,  className을 활용해라는 막연한 답변만 존재하고 실용가능한 답이 없었습니다.

문제해결 방법 2

두 번째는  키워드에서 style이라는 키워드가 있기에 이를 활용해서 해결해보려고 했습니다.
이중에서 style이라는 키워드가 있어서 이를 활용해서 css를 주려고 했습니다.
하지만 이 키워드에서 width 관련 스타일(max.min)은 적용되지 않았습니다.
(바깥 영역에 빨간색은 보이는 100vh가 제대로 적용되지 않음.

문제해결 방법 3
그 다음엔  해결하기 위해서 className을 줘서   해결하려고 했습니다.
이 방법 또한  실패했습니다. 파란색 영역은 적용되는데 크기는 적용되지 않음.

.CommonModal {
	/* 여기에 원하는 스타일을 작성합니다 */
	display: flex;
	justify-content: center;
	/* min-width: 400px !important;
	max-width: 100vw !important; */
	width: 5000px;
	align-items: center;
	background-color: blue;
	/* 추가적인 스타일을 적용하고 싶다면 이곳에 작성합니다 */
}



이를 해결하기 위해서 개발자 도구를 통해서 className을 찾은 다음에 직접적으로 스타일을 주려고 했는데 크기를 결정하는 컴퍼넌트의 class 이름이 ant-modal css-dev-only-do-not-override로 수정하지 말라고 적혀있더군요..

따라서 컴퍼넌트를 전달할 떄 , 직접 주는 width 값을 변경하기로 결정하였습니다.
useEffect를 통해서 사이즈가 변경될 때 적용을 시키려고 하다가 이럴 경우 렌더링이 많이 일어 날 거 같아서,,
그냥 모바일 크기 vs 웹 크기에서 모달 크기를 결정해놓고 size를 주었습니다.

	const modalOpen = () => {
		const modalSize = window.innerWidth >= 1000 ? 600 : 450;

		dispatch(openModal({ modalComponent: 'auth', modalSize: modalSize }));
	};



728x90

antd라이브러리로 모달 구현하기

전역 변수로 관리하기 위해서 먼저 redux-toolkit와 react redux를 설치합니다.

npm i @reduxjs/toolkit react-redux

이후 store와 Provider를 등록합니다 (BrowserRouter는 페이지 이동을 하기 위해 작성한 react-router 관련 코드입니다.)

import { store } from './store/redux';
import { Provider } from 'react-redux';


root.render(
	
		<Provider store={store}>
			<BrowserRouter>
				<App />
			</BrowserRouter>
		</Provider>
	
);


이후  모달의 기능을 구현할 slice를 등록합니다.

import { createSlice } from '@reduxjs/toolkit';
import { ReactNode } from 'react';
import { RootState } from '.';

export interface ModalState {
	isModalOpen: boolean;
	modalComponent: ReactNode | null;
}

const initialState: ModalState = {
	isModalOpen: false,
	modalComponent: null,
};

export const modalSlice = createSlice({
	name: 'modal',
	initialState,
	reducers: {
		openModal: (state, action) => {
			state.isModalOpen = true;
			state.modalComponent = action.payload;
		},
		closeModal: (state) => {
			state.isModalOpen = false;
			state.modalComponent = null;
		},
	},
});

export const { openModal, closeModal } = modalSlice.actions;

export const isModalOpen = (state: RootState) => state.modal.isModalOpen;
export const modalComponent = (state: RootState) => state.modal.modalComponent;

export default modalSlice.reducer;


타입스크립트가 적용된 modalSlice입니다. 

혹시 redux-toolkit에 대해서 간단하게 학습하고 싶다면 

npx create-react-app my-app --template redux-typescript

을 통해서 간단한 counter를 활용한 예시코드를 확인할 수 있습니다. (my-app 대신 자기가 만들고 싶은 프로젝트 이름)

api 요청 없는 전역변수이기에 extrareducer와 thunk는 사용하지 않았습니다.

이후 동일한 폴더내 있는 index.ts에 configureStore를 만들었습니다. 이를 통해서 index.tsx 에 등록한 store에 modalReducer를 등록했습니다.

import { configureStore } from '@reduxjs/toolkit';
import modalReducer from './modalSlice';

export const store = configureStore({
	reducer: {
		modal: modalReducer,
	},
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;


usesDispatch와 useSelector 관련 코드는  hooks 폴더에서 작성 했습니다.

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from '../store/redux';

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

최종적으로 modalSlice에서 export했던   모달상태(isModalOpen)과 모달 내에 작성할 컴퍼넌트(modalComponent)를 모달에 등록합니다.

import { Modal } from 'antd';
import { useAppDispatch, useAppSelector } from '../../hooks/useReduxToolkit';
import { closeModal, isModalOpen, modalComponent } from '../../store/redux/modalSlice';

const CommonModal = () => {
	const modalState = useAppSelector(isModalOpen);
	const modalComp = useAppSelector(modalComponent);

	const dispatch = useAppDispatch();
	const onCloseModal = () => dispatch(closeModal());
	return (
		<div>
			<Modal open={modalState} footer={null} onCancel={onCloseModal} keyboard centered maskClosable>
				{modalComp}
			</Modal>
		</div>
	);
};

export default CommonModal;


이후에  모달을 사용할 컴퍼넌트에서 useAppDispatch와 openModal 함수를 통해서  모달을 열면 됩니다.

import { useAppDispatch } from '../hooks/useReduxToolkit';
import { openModal } from '../store/redux/modalSlice';
import Login from '../components/auth/Login';

const Header = () => {
	const dispatch = useAppDispatch();

	const modalOpen = () => {
		dispatch(openModal(<Login />));
	};
	return (
		<HeaderContainer>
			<div>logo</div>
			<div onClick={modalOpen}>로그인</div>
		</HeaderContainer>
	);
};

export default Header;

이렇게 하면 전역변수로 관리했기 때문에, 모달이 state 변경에 의해서 주변 컴퍼넌트를 리랜더링 시키지 않습니다.


아직 모달에 스타일 적용은 하지 않았습니다

 

 

++++
뒤늦게 알았는데 이럴 경우 병렬화 에러가 떴습니다. 해결방법은  https://ungumungum.tistory.com/25

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