728x90

React 위주의 프로젝트를 진행하다보니, 바닐라 자바스크립트로 프로젝트를 구현한 경험이 매우 적었다. 이를 보완하기 위해서 + 동적인 웹앱을  만들어 보기 위해서 테트리스 만들기를 클론 코딩하게 되었다.

 

참고영상은 아래와 같고, 단순히 따라만 만들 경우 머리속에 얼마 남지 않는다고 느껴서, TypeScript로 디벨롭하기로 생각했다. (최근 우아한 타입스크립트 책을 보면서 배운 내용을 체득할 겸..)

https://www.youtube.com/watch?v=_xGETajBA98

테트리스

 

 

완성 레포지토리 주소는 아래와 같다.

https://github.com/suhong99/StudyRepo/tree/main/tetrisclone

 

 

프로젝트 코드 리뷰

개발환경은 Vite를 사용하였고, Phaser를 npm으로 install하였다.

폴더 구조는 아래와 같다.

┣ 📂public
┃ ┣ 📂image
┃ ┃ ┣ 📜back.png
┃ ┃ ┗ 📜block.png
┃ ┗ 📜vite.svg
┣ 📂src
┃ ┣ 📂config
┃ ┃ ┣ 📜gameconfig.ts
┃ ┃ ┗ 📜type.ts
┃ ┣ 📂manager
┃ ┃ ┗ 📜timermanager.ts
┃ ┣ 📂obj
┃ ┃ ┣ 📂tetrisblock
┃ ┃ ┃ ┣ 📜base.ts
┃ ┃ ┃ ┣ 📜helper.ts
┃ ┃ ┃ ┣ 📜iblock.ts
┃ ┃ ┃ ┣ 📜jblock.ts
┃ ┃ ┃ ┣ 📜lblock.ts
┃ ┃ ┃ ┣ 📜oblock.ts
┃ ┃ ┃ ┣ 📜sblock.ts
┃ ┃ ┃ ┣ 📜shape.ts
┃ ┃ ┃ ┣ 📜tblock.ts
┃ ┃ ┃ ┗ 📜zblock.ts
┃ ┃ ┣ 📜gameBoard.ts
┃ ┃ ┗ 📜tetrisblockfactory.ts
┃ ┣ 📂scene
┃ ┃ ┗ 📜mainScene.ts
┃ ┗ 📜main.ts
┣ 📜.gitignore
┣ 📜index.html
┣ 📜package.json
┣ 📜README.md
┗ 📜tsconfig.json

  <body>
    <div
      id="app"
      style="
        display: flex;
        justify-content: center;
        align-items: center;
        margin-top: 20px;
      "
    ></div>
    <script type="module" src="/src/main.ts"></script>

script를 통해서 Phaser를 사용할 수 있는데, main.ts에서 config설정을 했다.

//main.ts
import Phaser from 'phaser';

import MainScene from './scene/mainScene';

const config = {
  type: Phaser.AUTO,
  parent: 'app',
  width: 400,
  height: 800,
  scene: [MainScene],
};

export default new Phaser.Game(config);

 

이때 공식문서에서는 physic를 통해서 중력도 적용하고 다양한 효과를 줬지만, 테트리스 이기에 기본적인 scene만 사용하였다.

 

MainScene 클라스에서 메소드를 활용하였다.

import Phaser from 'phaser';
import GameBoard from '../obj/gameBoard';
import GameConfig from '../config/gameconfig';
import TimerManager from '../manager/timermanager';

export default class MainScene extends Phaser.Scene {
  private gameBoard: GameBoard;
  private timerManager: TimerManager; // timeManager 속성 선언

  constructor() {
    super('MainScene');
    this.gameBoard = new GameBoard(this);
    // this.timeManager = new TimerManager(); // timeManager 인스턴스 생성
    //private라 인스턴스로 생성
    this.timerManager = TimerManager.getInstance();
  }

  preload() {
    this.load.spritesheet(
      GameConfig.MainScene.RENDER_TILE_SPRITE_SHEET_KEY,
      'image/block.png',
      {
        //이미지를 64*64으로 쪼갬
        frameWidth: 64,
        frameHeight: 64,
      }
    );
    this.load.spritesheet(
      GameConfig.MainScene.BACKGROUND_TILE_SPRITE_SHEET_KEY,
      'image/back.png',
      {
        //이미지를 64*64으로 쪼갬
        frameWidth: GameConfig.MainScene.RENDER_TILE_SPRITE_ORIGIN_SIZE,
        frameHeight: GameConfig.MainScene.RENDER_TILE_SPRITE_ORIGIN_SIZE,
      }
    );
  }
  create(): void {
    this.gameBoard.init();
    this.gameBoard.spawnRandomBlock(
      GameConfig.MainScene.GAME_BOARD_WIDTH_CNT / 2,
      0
    );
    this.gameBoard.render();
  }

  update(time: number, delta: number): void {
    this.timerManager.update(delta);
    this.gameBoard.update(time, delta);
    this.gameBoard.render();
  }
}

preload : 에셋 로드하기 --> 여러개의 이미지가 합쳐진 하나의 이미지를 64픽셀로 쪼개서 인덱스를 통해 사용함

create  :  Scene 만들기 -> 초기 줄금있는 보드판 만들기 , 첫 블록 생성

update : 업데이트 하기 ->블록이 자동으로 내려가기, 줄 없애기, 블록 이동, 게임 종료 등등

 

이때 세부적인 기능은 GameBoard, TimeManager 클라스를 만들어 따로 관리하였다.

gameBoard 에서는 게임과 관련된 함수들 ( 블록 

timeManager에서는 시간과 관련된 함수를 넣었다. (블록 떨어진 시간 측정)

구현방식은  움직이는 블록과 보드 상태를 더해서 랜더링되는 보드로 보여주게 설정하였다.

 

 

프로젝트 피드백

자바스크립트 코드를 타입스크립트 코드로 바꾸면서 캡슐화에 신경썼다. 코드가 기본적으로 class형으로 사용되었는데, 자바스크립트에는 private 같은 키워드 없이도 class를 사용할 수 있다. 이를 private 키워드를 사용하여 각각의 클라스의 은닉화를 시도하였다.

Timer를 예시로 외부에서 사용할 init, update 등의코드는 public으로 하였고 나머지는 private으로 설정하였다.

import GameConfig from '../config/gameconfig';

export default class TimerManager {
  private static instance: TimerManager;
  private fallingBlockTimer: number = 0;

  // 하나의 Timer만 인스턴스로 생성
  private constructor() {
    if (!TimerManager.instance) {
      TimerManager.instance = this;
    }
    return TimerManager.instance;
  }

  public static getInstance(): TimerManager {
    return TimerManager.instance || new TimerManager();
  }

  public init(): void {
    this.fallingBlockTimer = 0;
  }

  public update(delta: number): void {
    this.fallingBlockTimer += delta;
  }

  public checkBlockDropTime(): boolean {
    if (
      this.fallingBlockTimer > GameConfig.MainScene.TIMER_INTERVAL_BLOCK_DOWN
    ) {
      this.fallingBlockTimer = 0;
      return true;
    }
    return false;
  }
}

 

또한 각각의 타입을 보여하여서 런타임 에러를 예방하고자 하였다

 

좋았던 점

클래스에 대해서 사실 쓸 일이 많지도 않고, 익숙치도 않다. (자바에서 얕게 배운게 끝..)

이번 프로젝트를 통해서 클래스를 사용해본 경험도 좋았고, 타입스크립트에 조금은 더 익숙해져서 좋았다.

다만 사이즈가 작았고, model이 많지는 않아서 type을 많이 사용할 일은 없었다.

JS를 TS로 마이그레이션 하게 되는 경험을 하는것도 좋았다.

아쉬운점

다만 아직 게임보드의 update의 경우에 사용하지 않는 변수가 존재한다(time,delta, isClear ) 이를 개선하고

 public update(time: number, delta: number): void {
    if (this.gameEnd) return;

    const { isClear, line } = this.checkForClearableLines();
    if (line !== undefined) {
      this.clearLines(line);
      this.lineDown(line);
    } else {
      if (this.currentTetrisBlock === undefined) {
        const block = this.spawnRandomBlock(
          GameConfig.MainScene.GAME_BOARD_WIDTH_CNT / 2,
          0
        );
        if (this.canSpawnBlock(block)) {
          this.currentTetrisBlock = block;
        } else {
          this.currentTetrisBlock = this.setLastBlockPos(block);
          this.gameEnd = true;
          this.scene.cameras.main.shake(500);
        }
      } else {
        if (this.timerManger.checkBlockDropTime()) {
          if (this.canMoveBlock(0, 1)) {
            this.currentTetrisBlock.move(0, 1);
          } else {
            this.placeBlock();
          }
        }
      }
    }
  }

 

 

또한 줄삭제가 되는 로직을 업데이트할 여지가 있어 보인다.

해당 부분은

https://ungumungum.tistory.com/96

728x90

https://ungumungum.tistory.com/48#comment18876777

 

React에서 화면 크기를 구해주는 커스텀 훅 만들기 (feat. UI라이브러리에 반응형 크기 주기)

화면 크기를 구해주는 커스텀 훅을 만들게 된 이유는 UI 라이브러리의 경우 간혹 max-width나 100% 같은 반응형 크기가 먹히지 않는 경우가 있습니다. 제가 사용하는 calendar , cropImage, antd 라이브러리

ungumungum.tistory.com

 

현재 커스텀훅을 통하여서 windowWidth를 구하는데, 이에 대한 링크는 위에 남겨두겠습니다.

 

 

자바스크립트가 아닌 타입스크립트를 쓰는 시점에서 ,   *1 과 같이 암묵적 타입 변환을 하려고 하면 아래와 같은 에러가 뜹니다.

산술 연산의 왼쪽은 'any', 'number', 'bigint' 또는 열거형 형식이어야 합니다.ts(2362) 

 

따라서 string을  int 로 변환하는 단계가 필요한데, 앞서서 debounce 훅의 경우에는  string 값이 주로 활용되기 떄문에, 그냥 useCalWindowWidth 훅에서도 string을 반환하려고 했습니다. 하지만 string으로 반환을 하다보니 중간로직에 계쏙 불필요한 타입변화 로직이 필요했습니다. 결국 최종적으로 라이브러리에 스타일을 줄 때는 숫자값+"단위" 형식의 string을 줘야하지만, 크기를 변환한다던가, 넓이 기반으로 높이를 구할 때는 number 타입인게 훨씬 편합니다.

 

따라서 string으로 반환했던 windowWidth로 짜였던 코드들을 number로 리팩토링 하게 되었습니다.

 

우선 대략적인 로직은 아래와 같습니다.

const windowWidth = useCalWindowWidth();
const [uploaderSize, setUploaderSize] = useState({ width: '28rem', height: '21rem' });
useEffect(() => {
	const width = pxToRemWithResizer(windowWidth, 3 / 10);
	const height = ratioConverter(width, 3 / 4, 'rem');
	setUploaderSize({ width, height });
}, [windowWidth]);

윈도우 넓이 구하기,, 라이브러리에 크기를 줄 uploaderSize 선언

useEffect로  윈도우 넓이가 변할때마다 크기를 셋해주기

하지만 이때 px기반의 넓이를 Rem 단위로 바꾸고, 비율에 맞게 높이 설정하기

(width의 경우 조건문을 통해서 반응형을 구현해야 하지만 그전에 리팩토링을 하려고 합니다.)

 

그리고 그 과정을 진행해줄 유틸함수는

const pxToRemWithResizer = (px: string, ratio: number) => {
	const rootFontNum = parseFloat(rootFontSize.slice(0, -2)); // rootFontSize를 숫자로 변환

	const returnNum = (ratio * parseFloat(px)) / rootFontNum;
	if (isNaN(returnNum)) {
		alert('숫자로 된 값만 입력됩니다. windowWidth 그대로 단위없이 넣어주세요');
		return '16rem';
	} else {
		return returnNum + 'rem';
	}
};

const ratioConverter = (origin: string, ratio: number, unit: string) => {
	const ratioOriginNum = parseFloat(origin.slice(0, -unit.length)); // rootFontSize를 숫자로 변환
	return ratioOriginNum * ratio + unit;
};

문자값을 입력받고~ 그걸 단위 분리해주고, 비율입력받고,, 너무 불편한 구조입니다.

만약 width가 number 타입이었고, 그걸 곱한다음에 최종적으로 set할때만 단위를 더하면 되는 로직을 만들었다면!!...

 

따라서 리팩토링을 진행했습니다.

 

불필요하다 느낀 함수 ratioConverter,  효율성을 위해서 화면 넓이는 number 타입으로 반환. 그리고 이에 맞게 나머지 함수들을 수정

 

1. 화면 넓이 number 타입으로 반환하기

import { useEffect, useState } from 'react';
import { useDebounce } from '.';

export const useCalWindowWidth = () => {
	const [windowSize, setWindowSize] = useState(window.innerWidth);
	const debouncedValue = useDebounce(window.innerWidth + '', 300);
	useEffect(() => {
		const handleResize = () => {
			setWindowSize(parseFloat(debouncedValue));
		};

		window.addEventListener('resize', handleResize);

		return () => {
			window.removeEventListener('resize', handleResize);
		};
	}, [debouncedValue]);

	return windowSize;
};

debounce 훅에서는 타입변환을 통해서 여전히 string 값을 넘겨줍니다.

하지만 debouncedValue의 반환값이 숫자로 암묵적타입변환이 가능한 값이기에 parseFloat을 통해서  숫자형으로 변환한 후에 windowSize에 상태를 저장합니다.

 

2. px단위의 값을 root 폰트 사이즈를 통해서 나누는 함수 리팩토링

const pxToRemWithResizer = (pxUnitNum: number) => {
	const rootFontNum = parseFloat(rootFontSize.slice(0, -2)); // rootFontSize를 숫자로 변환
	const returnNum = pxUnitNum / rootFontNum;
	return returnNum;
};

export {  pxToRemWithResizer };

스타일 리팩토링을 용이하게 하기 위해서  팀에선 px보다 rem 단위를 가급적이면 쓰기로 하였습니다.

하지만 window.innerWidth 를 통해서 구하는 값의 단위는 px이죠.. 따라서 이를 변환하기 좋은 util함수가 있으면 팀에 도움이 될 것 같습니다. 하지만 불필요한 string타입으로 값을 받아서 복잡했떤 코드를 위와 같이 좀 더 간단하게 리팩토링했습니다.

 

기존에 역할하던 resize기능은 굳이 위 함수를 통해 할 필요가 없으니 함수명도  pxToRem 으로 바꾸면 좋겠네요!

 

 

최종적으로 반응형 반응한 코드는 다음과 같습니다.

 

	const windowWidth = useCalWindowWidth();
	const [uploaderSize, setUploaderSize] = useState({ width: '28rem', height: '21rem' });

	useEffect(() => {
		let widthNumber: number;
		const ratio = 3 / 4;

		switch (true) {
			case windowWidth < 427:
				widthNumber = 16;
				break;
			case windowWidth >= 427 && windowWidth < 747:
				widthNumber = pxToRem(windowWidth) * 0.6;
				console.log(widthNumber);
				break;
			default:
				widthNumber = 28;
		}
		const width = widthNumber + 'rem';
		const height = widthNumber * ratio + 'rem';
		setUploaderSize({ width, height });
	}, [windowWidth]);

하단에 있는 이미지와 크기를 맞추기 위해서 위와 같이 분기처리하였습니다.

const HouseImg = styled.img`
	justify-self: center;
	max-width: 28rem;
	margin-bottom: 0.5rem;
	object-fit: contain;
	border-radius: 0.5rem;
	width: 60vw;
	min-width: 16rem;
`;

 

728x90

백엔드가 구축한 기능이 로컬에서는 swagger에서 정상 작동하는 것을 확인할 수 있으나, 배포된 서버에서는 500번 에러가 뜨고 있다. 하지만 디버깅이 되고 있지 않는 상황이었다.

 

우리 앱은 dockerfile을 통하여서 container를 구축한 후 ec2에 배포된 상황이기에, ssh를 통하여 ec2에 접속한 후 docker 컨테이너에 접근하여서 log를 확인하면 된다.

아직 ssh와 docker가 익숙하지 않은 단계에서 구글링을 통해 각각의 키워드를 검색하면서 에러를 해결해 나갔다.

(사실 ssh 말고 aws에서 인스턴스에 연결하면 굳이 ssh를 쓸 필요는 없긴하다..)

 

ssh를 하기 위해서 우선 기존에 사용하던 pem파일을 생성하였다.

해당 파일의 경로를 찾은 후 

ssh -i "경로\키파일.pem" 사용자@서버주소 를 통해서 접속하려고 했다.

하지만 pem파일에 권한을 부여하지 않았더니, 경고를 알려주면서  막았따

permissions for c:\\test\\server.pem are too open. it is required that your private key files are not accessible by others. this private key will be ignored. load key c:\\test\\server.pem: bad permissions

 

해당 원인은 아래 블로그를 통해서 확인하였는데, ssh 키는 중요한 파일이기에 다른 사람이 읽을 수 있으면 안된다.

https://www.lesstif.com/lpt/ssh-unprotected-private-key-file-80249001.html

 

SSH 접속시 UNPROTECTED PRIVATE KEY FILE! 에러 해결

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: UNPROTECTED PRIVATE KEY FILE! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Permissions 0644 for 'ubuntu@example.com/id_rsa' are too open. It is required that your priva

www.lesstif.com

따라서 권한을 부여할 필요가 있었다. 리눅스 환경에서는 chmod를 통하여 권한을 설정할 수 있다.

하지만 나는 윈도우 환경이기에 다른 명령어를 사용할 필요가 있었다.

리눅스 환경이라면 chmod 0400 ~/.ssh/ubuntu@example.com/id_rsa 해당 파일을 읽기 권한을 줄 수 있다

0400 :  해당 파일을 소유자에 대해서 읽기권한만 부여함.

~/.ssh/ubuntu@example.com/id_rsa  : 파일 경로와 파일

ubuntu@example.com : 사용자 이름

 

하지만 해당 명령어를 윈도우 환경에서는 아래의 명령어로 실행해야 한다.

icacls "C:\test\server.pem" /inheritance:r /grant:r "%username%:R"

내 경우에는 username이 인식되지 않아서 

[System.Security.Principal.WindowsIdentity]::GetCurrent().Name

을 통해서 사용자이름을 구한후 username 대신에 작성하였다.

 

이후 ssh -i "경로\키파일.pem" 사용자@서버주소 을 통하여 서버에 접속하였다.

 

cicd 파이프라인을 구축하여 배포는 완료한 상황에서 , docker container는 올바르게 실행되고 있었다

따라서 docker  ps 로 해당 컨테이너의 이름을 친후 docker logs 를 입력하여 에러 메시지를 확인하였다.

 

에러를 확인하니 table이 생성되어 있지 않았었다.

728x90

https://www.youtube.com/watch?v=NwLWX2RNVcw 

위 영상을 보고 언젠가 데이터를 관리할 때, 퍼널형식으로 보여주면 효율적인  랜더링이 가능하지 않을까 생각했다.

 

 

 

HOUS-SG 프로젝트에서는 숙소등록하는 부분이 있다.
해당 페이지에서 우선 유저는 이미지 제출을 통해 사업자 등록증을 검사를 받는다.

이후 숙소 위치를 다음 우편번호 기능을 통해서 제출한다.

마지막으로 숙소와 관련된 추가정보를 제출한다.

 

결국 숙소 등록 마지막에 사업자 정보, 주소, 숙소정보를 제출하는데 중간중간 기능이 너무 많다. 이를 한페이지에서 관리하려다 보니 복잡하다고 생각했고, 퍼널형식으로 나누는 방식을 도입하려고 했다.

 

토스 라이브러리에 있는 useFunnel 을 사용하면 조금 더 쉬웠겠지만, 유사 기능을 직접 구현해보고자 하였다.

 

 

현재 구성은 아래와 같다.

이를 다음과 같은 상태로 바꾸려 한다.

const OwnerHouseRegister = () => {
	const navigate = useNavigate();

	useWindowWarning();

	const location = useLocation();
	const funnelState = location.state;

	const step = funnelState ? funnelState.step : 0;
	const goStep = (step: number, newState?: { [key: string]: string | number }) => {
		navigate('', { state: { ...funnelState, step, ...newState } });
	};

	return (
		<RegisterWrapper>
			{step === 0 && <BusinessRegi goStep={goStep} step={step} funnelState={funnelState} />}
			{step === 1 && <AddressFinder goStep={goStep} step={step} funnelState={funnelState} />}
			{step === 2 && <HouseImageRegi goStep={goStep} step={step} funnelState={funnelState} />}
			{step === 3 && <HouseInfoRegi goStep={goStep} step={step} funnelState={funnelState} />}
			<div onClick={() => goStep(0)}>등록완료</div>
		</RegisterWrapper>
	);
};

export default OwnerHouseRegister;

 

토스측 진유림님이 말했던 데로, 유저가 페이지 이동을 uri 부근의  뒤로 가기 버튼으로도 할 수 있기 때문에, 단순한 state로 관리하는 것이 아니라, navigate를 통해서 관리하려고 했다.

 

이 부분에서 처음에 어려움을 겪었는데

1. 유튜브 영상에 있는 shallow routing은  next.js의 기능이다.

2. 구글링 결과 현재 history 훅은 deprecated 되었기 때문에, navigate 기능을 자의적 해석을 통해 해당 기능을 구현해야 했다.

 

결국 next.js 또한 리액트 기반의 프레임워크 이기에 나도 구현할 수 있지 않을까?라는 의문에 끝까지 시도했다.

 

목표는 토스측에서 설정한 목표에서 아직 훅으로 만드는 부분만 구현하지 못했다.

 

따라서 나의 목표는

 

1. step을 통해서 랜더링 관리

2. 유저가 뒤로가기등 페이지 이동 기능을 사용하여도 state가 사라지지 않게 하기.

3. 그렇다고 데이터를 uri 에 노출시켜서 유저 임의로 수정하지 못하게 하기

 

 

 

이를 navigate의 state를 활용하여 구현하려고 한다.

 

navigate의 두번째 매개변수를 통해서 추가적인 설정을 할 수 있는데 해당 매개변수의 키값으로 state를 줄 경우에 값을 저장할 수 있다. 이를 통해서 페이지 이동할 떄, 값을 주고 받을 수 있는데... 해당 값이 뒤로가기 버튼을 눌렀을 때 사라지는  문제점이 있다.

 

이를 해결하기 위해서 전역변수를 써야 하나 고민을 했지만, 그럴 경우 진유림님(??) 이 말하신 대로 데이터의 추적이 복잡해질 수 있다.

 

따라서 나는 왜 뒤로가기 버튼을 누르면 데이터가 사라질 까 고민했고, 뒤로가기 버튼 클릭시 적용되던 이벤트를 제거하고자 했다.

이를 위해 작성한 훅이 useSaveNavigateState이다. 

import { useEffect } from 'react';
import { removeWindowWarningState, windowWarningState } from '../utils';

const useSaveNavigateState = () => {
	useEffect(() => {
		windowWarningState();

		return () => {
			removeWindowWarningState();
		};
	}, []);
	return null;
};

export default useSaveNavigateState;

 

const windowWarningState = () => {
	window.addEventListener('popstate', popstateHandler);
};

const removeWindowWarningState = () => {
	window.removeEventListener('popstate', popstateHandler);
};

const popstateHandler = (event: PopStateEvent) => {
	event.preventDefault(); // 뒤로가기 이벤트의 기본 동작을 막음
};

export { windowWarningState, removeWindowWarningState };

 

 

이후 location 내에 저장된 state를 가져오고, 페이지 이동할 때는 전개연산자를 통해서 state를 업데이트를 하는 방식으로 페이지 이동을 구현하였다!.

 

추후 훅을 통해 업데이트 해보겠습니다

 

참고

https://reactrouter.com/en/main/hooks/use-navigation

 

useNavigation v6.22.3 | React Router

useNavigation This hook tells you everything you need to know about a page navigation to build pending navigation indicators and optimistic UI on data mutations. Things like: Global loading indicators Disabling forms while a mutation is happening Adding bu

reactrouter.com

 

피드백 (2024.03.25)

그 당시에는 최대한 지역적인 상태로 관리하는게 좋다고 막연하게 생각했었는데, 아쉬웠던 점도 공유하고자 추가로 작성합니다.

 

1. step ===0 ~~~ 이런식으로 비슷한 형식의 코드들인데, 이를  토스 영상처럼 compound패턴을 도입 못한점이 아쉬웠습니다.

2. 결국 저희 로직대로라면 사업자는 한번에 숙소등록을 해야합니다. 2단계 까지 진행하고 브라우저를 종료해도, 다시 1단계 부터 진행해야합니다. 차라리 단계마다 미리 백엔드와 통신을 하여서, 현재 기록된 내용들을 바탕으로 스탭을 구별한다면, ( 사업자 번호가 있다면 2단계 완료)  좀 더 UX가 향상될 것이라고 느꼈습니다.

728x90

1. Nav 컴퍼넌트 styled-component 관련 에러
@react-refresh:160  Warning: Received `true` for a non-boolean attribute `active`.

If you want to write it to the DOM, pass a string instead: active="true" or active={value.toString()}.


Nav 컴퍼넌트에서 styled-components 사용방식이 틀려서 난 에러이다.
props부분에 $를 붙여주면 해결된다. ( 몇 달전에 사용법이 바꼈는데, 기존 방식 써서 뜬 에러)

	font-size: ${({ $active }) => ($active ? '1.3rem' : '1rem')};
	// 기존 font-size: ${({ active }) => (active ? '1.3rem' : '1rem')};


2. redux 직렬화.
리덕스(+툴킷)에서는 직렬화 할 수 없는 값을 저장하려고 하면 에러가 뜸.
이를 해결하기 위해서 특정 케이스에 무시하는 방법도 있찌만, 기본적으로 직렬화 할 수 없는 값 저장시 오류가 생길 수 있으므로 안하는게 좋음.. 

 

3. antd upload

mocky라고 쉽게 api만들 수 있는 웹사이트가 있는데 이를 활용해서 antd가 이미지 미리보기가 구현되어있다. 근데
5개중 2~3개는 실패한다,, 통제할 수 없는 에러라 생각해서 사용하지 않기로 햇다.

4. null+ "

 

암묵적 타입 변환으로 null을 ""으로 바꾸고 싶었는데 "null"로 바뀜

console.log(imgFile);
	console.log(businessImg, '비지니스');
	console.log(!!businessImg, '비지니스');
	console.log('테스트', businessImg ? businessImg : ownerRegiImg);
	console.log('테스트2', ownerRegiImg);

조건문으로 처리함

 

5.  props 로 true false가 들어오는걸  jsx영역에 attribute로 줄 때,

적다보니 1번이랑 같은 문제라서 $를 붙임.. 근데 그냥 pending+"" 로 문자를 암묵적 타입변환해도 되고, error 메시지의 추천방법은 참일떄만 값을 보내기다

 

6.  배열 비교시 에러

   console.log(roomImgFiles, resistImgs, room.imgs, resistImgs == room.imgs);
        console.log(
            roomCategoryValue === room.roomCategory,
            roomCountValue === room.roomAvailability + '',
            roomPriceValue === room.roomPrice + '',
            resistImgs === room.imgs,
            !roomImgFiles,
            checkedList === room.service,
        );

배열을 비교할 떄 ,배열은 다른 것과 다르게 빈값일 때  true가   나옴,, 그리고  배열끼리 비교할 때 주소값이 다르면 같은 값이라도 다르게 나옴..

--> 해결법 어차피 파일 수정시 이미지 유지되는지 비교하는거라서 배열의 길이를 비교하여 구분함

 

7. 날짜 객체비교

const endDate = new Date('2023-10-18');

    const today = new Date();
    today.setHours(9, 0, 0, 0);
    console.log(endDate);
    console.log(today);
    console.log(today === endDate, '오늘 false나와야함');

참조값이라서 다르게 나옴. 근데 >= 은 제대로 먹음 크기비교라서

+ Recent posts