HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🧚
[1기]최종 프로젝트 데브코스
/
[팀7] 뿡치와 삼촌들 - Devnity
[팀7] 뿡치와 삼촌들 - Devnity
/
📘
프론트엔드 공간
/
🧑‍🎓
Today We Learned
/
🧳
민철 참고자료
🧳

민철 참고자료

생성일
Dec 8, 2021 04:43 AM
기록자
Jay Mincheol Cho
해결 여부
속성
카테고리
전체 컨셉절대 경로 자동 완성리액트 아이콘 라우팅이모션 클래스 네임배포CSS in JSHTTPS디자인Git테스트HTTP API 모킹리액트리코일리액트 쿼리Axios 미디어 쿼리 2021로띠웹 접근성 a11yvscode카카오 지도맵각코기능 개발타입스크립트JestESLintYarn코드 리뷰아이디어
💡
프로젝트를 진행하면서 알게 되거나 참고한 자료들을 모아둡니다.
test@gmail.com / 1q@W3e$R
 
 

전체 컨셉

  • 드리블 주황색 대시보드 - [링크]

절대 경로 자동 완성

  • https://github.com/prgrms-fe-devcourse/FEDC1_Mogakco_Dali2/pull/125/files: jsconfig

리액트 아이콘

  • 리액트 아이콘을 css의 background url로 넣는 방법
  • svg를 background ur로 넣는 방법

라우팅

  • 화면 리렌더링 하지 않고 url 바꾸기 - window.history.replaceState

이모션 클래스 네임

  • 한글 블로그 설명
  • ts-loader와 babel-loader 함께 사용하기
    • 예제 코드
    • ts-loader에서 위 예제 코드를 추천해준 문서
  • 이모션 공식문서 label 설명

배포

  • Github Actions + React + S3 자동 빌드 및 배포

CSS in JS

  • JS에 :hover 스타일링 넣는 방법
  • svg와 span 정렬 codepen

HTTPS

  • webpack https
  • let's encrypt 인증서 발급받기

디자인

  • material design - tooltip
  • how to choose primary color
  • 유저 카드
  • 유저 프로필 이미지 원 겹치기
  • contextual color
  • 색상 의미

Git

  • 파일 이름을 대문자에서 소문자료 변경해도 git은 인식하지 못한다. 이를 인식하게 하려면
    • [core] ignorecase = false

테스트

  • 컴포넌트 내부적으로 axios를 사용하는 코드를 테스트할 때 axios onCreate 에러가 날 경우 - [설명] [Github]
  • 검색하다가 찾은 2019년 NHN Toast 테스트 자료 - [링크]
  • 부스트캠프 2019 Cypress 코드
  • Cypress
    • 설치
      • 타입스크립트
    • 커맨드
      • viewport
      • cypress-command 패키지
      • basic selecting elements
    • 예제
      • 로컬 스토리지
      • 카카오 개발자 블로그에 담긴 예제 코드
    • Jest에서 cypress 테스트 코드 무시하기
      • stackoverflow

HTTP API 모킹

  • 생성 완료
    • /api/v1/users/suggest
  • 현실적인 더미 데이터로 모킹 HTTP API 만드는 방법
  • 대한민국 위도 경도 범위

리액트

  • setState의 batch update 취소하고 순서대로 업데이트 하도록 만들기
  • input 컴포넌트 값을 브라우저가 자동완성 옵션 보여주는 것 끄기: autocomplete="off"

리코일

  • cheo
  • JWT 리코일 example
  • 테스팅
    • RecoilRoot에 초기값을 넣어주면 된다
    • 나한테 적용한 코드:
      function renderMainPage() { return render( <QueryClientProvider client={queryClient}> <RecoilRoot initializeState={(snap) => snap.set(currentUserState, randomUserInfo()) } > <MemoryRouter> <ThemeProvider theme={theme}> <MainPage /> </ThemeProvider> </MemoryRouter> </RecoilRoot> </QueryClientProvider> ); }

리액트 쿼리

  • cheo
  • RQ - Quick start
  • RQ - important defaults
  • TkDodo - Practical React Query
  • state가 변하면 query 또한 같이 실행하기

Axios

  • get 메서드에는 body를 넣을 수 없다

미디어 쿼리 2021

  • Media Queries Breakpoints For Responsive Design In 2021

로띠

  • 리액트에서 사용하기
    • How to use lottie in react
    • How to add user control

웹 접근성 a11y

  • 컬러 대비 체커 WebAIM
    • 예시
      notion image
  • eslint - no-noninteractive-element-interactions

vscode

  • 우연히 눌러본 단축키 cmd + F12

카카오 지도

  • 카카오 지도 API 문서 - [링크]
  • CSS 맵 마커 흔들흔들 - [링크]
  • 구글 맵 마커 예시 문서 - [링크]
  • velog - 참조할 만한 지도 테스트 코드 - [링크]
  • 오프 멘토님이 추천해준 맵 테스트 예시 코드 - [링크]
  • 스크립트 태그 onload 사용한 테스트 코드 - [링크]
  • 이미지 요소 드래그 안되게 막기: pointer-events: none
  • 서울 좌표값 알아내기
  • 영역 변경 이벤트(지도 좌표의 가장자리 좌표 알아내기)
  • 지도 객체를 설정한다. 이미 설정되어 있는 지도는 setMap(null) 로 해제 가능하다
  • 카카오 로컬 검색 API 문서
  • 카카오 로컬 API - 키워드로 장소 검색하기
  • 맵각코 지도 디버깅 코드
    • 코드
      import { useCallback, useRef, useState } from "react"; import { MapMarker } from "react-kakao-maps-sdk"; import theme from "../../assets/theme"; import { Position } from "../../types/commonTypes"; import { isEqualPosition } from "../../utils/map"; import Button from "../base/Button"; import Text from "../base/Text"; import Mapbox from "../Mapbox/Mapbox"; import PlaceSearchForm from "./PlaceSearchForm"; import { ButtonContainer, Container, Guide, MapFloatContainer, PlaceSearchFormWrapper, SearchContainer, } from "./styles"; interface Props { initialCenter: Position; } const MapgakcoMap = ({ initialCenter }: Props) => { const memoCenter = useRef(initialCenter); const [center, setCenter] = useState({ lat: initialCenter.lat, lng: initialCenter.lng, }); const [currentCenter, setCurrentCenter] = useState({ lat: initialCenter.lat, lng: initialCenter.lng, }); const [target, setTarget] = useState({ y: null, x: null, }); const handleKeywordSubmit = useCallback((place) => { setTarget((prev) => ({ ...prev, ...place, })); setCenter({ lat: place.y, lng: place.x, }); }, []); const handleMyPositionClick = useCallback(() => { if (isEqualPosition(currentCenter, memoCenter.current)) { return; } Promise.resolve().then(() => { setCenter(() => ({ lat: currentCenter.lat, lng: currentCenter.lng, })); setCenter(() => ({ lat: memoCenter.current.lat, lng: memoCenter.current.lng, })); }); }, [currentCenter]); const handleCenterChange = useCallback(({ lat, lng }) => { setCurrentCenter({ lat, lng, }); }, []); const buttonStyle = { padding: "8px", backgroundColor: theme.colors.white, minWidth: "80px", display: "flex", justifyContent: "center", borderRadius: "8px", boxShadow: theme.boxShadows.primary, }; const guideTextStyle = { background: theme.colors.white, padding: "8px", borderRadius: "8px", fontSize: "12px", color: "#91979a", }; return ( <Container> <MapFloatContainer> <SearchContainer> <PlaceSearchFormWrapper> <PlaceSearchForm onSubmit={handleKeywordSubmit} /> </PlaceSearchFormWrapper> <ButtonContainer> <Button style={buttonStyle} onClick={handleMyPositionClick}> 나의 위치 </Button> <Button style={buttonStyle} onClick={() => ({})}> 데둥이 위치 </Button> <Button style={buttonStyle} onClick={() => ({})}> 모각코 </Button> <Button style={buttonStyle} onClick={() => ({})}> 등록 </Button> </ButtonContainer> </SearchContainer> <Guide> <Text style={guideTextStyle}> 위치를 지정한 후 등록 버튼을 눌러주세요. </Text> </Guide> </MapFloatContainer> <Mapbox center={{ lat: center.lat, lng: center.lng }} isPanto hasControl={false} onCenterChanged={handleCenterChange} > {target.x && target.y && ( <MapMarker position={{ lat: target.y, lng: target.x, }} /> )} </Mapbox> {JSON.stringify(currentCenter)} </Container> ); }; export default MapgakcoMap;
이미지 마커 동그라미
import { useCallback, useRef, useState } from "react"; import { BsArrowRightCircle, BsEye } from "react-icons/bs"; import { MapMarker } from "react-kakao-maps-sdk"; import theme from "../../assets/theme"; import useMapClick from "../../hooks/useMapClick"; import { Position } from "../../types/commonTypes"; import { ImageMarkerOverlay } from "../../types/mapTypes"; import { isEqualPosition } from "../../utils/map"; import Button from "../base/Button"; import Text from "../base/Text"; import Mapbox from "../Mapbox/Mapbox"; import PlaceSearchForm from "./PlaceSearchForm"; import { ButtonContainer, Container, Guide, MapFloatContainer, PlaceSearchFormWrapper, SearchContainer, } from "./styles"; interface Props { initialCenter: Position; userMarkerOverlays: ImageMarkerOverlay[]; } const MapgakcoMap = ({ initialCenter, userMarkerOverlays }: Props) => { const memoCenter = useRef(initialCenter); const [userClickPosition, click, initializeClick] = useMapClick(); const [center, setCenter] = useState({ lat: initialCenter.lat, lng: initialCenter.lng, }); const [currentCenter, setCurrentCenter] = useState({ lat: initialCenter.lat, lng: initialCenter.lng, }); const [targetPlace, setTargetPlace] = useState({ y: null, x: null, }); const handleKeywordSubmit = useCallback( (place) => { setTargetPlace((prev) => ({ ...prev, ...place, })); setCenter({ lat: place.y, lng: place.x, }); initializeClick(); }, [initializeClick] ); const handleMyPositionClick = useCallback(() => { if (isEqualPosition(currentCenter, memoCenter.current)) { return; } Promise.resolve().then(() => { setCenter(() => ({ lat: currentCenter.lat, lng: currentCenter.lng, })); setCenter(() => ({ lat: memoCenter.current.lat, lng: memoCenter.current.lng, })); }); }, [currentCenter]); const handleCenterChange = useCallback(({ lat, lng }) => { setCurrentCenter({ lat, lng, }); }, []); const buttonStyle = { padding: "8px", backgroundColor: theme.colors.white, minWidth: "80px", display: "flex", justifyContent: "center", borderRadius: "8px", boxShadow: theme.boxShadows.primary, }; const guideTextStyle = { background: theme.colors.white, padding: "8px", borderRadius: "8px", fontSize: "12px", color: "#91979a", }; return ( <Container> <MapFloatContainer> <SearchContainer> <PlaceSearchFormWrapper> <PlaceSearchForm onSubmit={handleKeywordSubmit} /> </PlaceSearchFormWrapper> <ButtonContainer> <Button style={buttonStyle} onClick={handleMyPositionClick}> <BsArrowRightCircle style={{ marginRight: 4 }} /> 나의 위치 </Button> <Button style={buttonStyle} onClick={() => ({})}> <BsEye style={{ marginRight: 4 }} /> 데둥이 </Button> <Button style={buttonStyle} onClick={() => ({})}> <BsEye style={{ marginRight: 4 }} /> 모각코 </Button> <Button style={buttonStyle} onClick={() => ({})}> 등록 </Button> </ButtonContainer> </SearchContainer> <Guide> <Text style={guideTextStyle}> 위치를 지정한 후 등록 버튼을 눌러주세요. </Text> </Guide> </MapFloatContainer> <Mapbox center={{ lat: center.lat, lng: center.lng }} isPanto hasControl={false} onCenterChanged={handleCenterChange} onClick={click} > {userMarkerOverlays.map(({ position, imageUrl }) => ( <MapMarker key={`${position.lat}-${position.lng}`} position={{ lat: position.lat, lng: position.lng, }} image={{ src: imageUrl, size: { width: 40, height: 40, }, options: { shape: "circle", }, }} /> ))} {userClickPosition.lat && userClickPosition.lng ? ( <MapMarker position={{ lat: userClickPosition.lat, lng: userClickPosition.lng, }} /> ) : targetPlace.x && targetPlace.y ? ( <MapMarker position={{ lat: targetPlace.y, lng: targetPlace.x, }} /> ) : null} </Mapbox> </Container> ); }; export default MapgakcoMap;

맵각코

  • 카드 슬라이더
  • meetup card
  • 카카오맵 빨간색 이미지 마커
  • 대한민국 극점

기능 개발

  • 메인 화면 컨셉
    • Mac-ish 글래스모피즘 glassmorphism - [링크]
  • 계정 이미지 드롭다운 메뉴
    • 사이드바 - [링크]
    • 투명도 애니메이션 및 마크업&CSS 영상 - [링크]
    • 슬라이드 다운 애니메이션 - [링크]
      • 직접 구현 - [링크]
    • CSS 툴팁 - [링크]
    • 구글 계정 대시보드 - [링크]
  • 자기 소개
    • 스켈레톤 UI - [링크]
    • 스크롤 다운 UI - [링크]
    • 마우스 휠로 가로 스크롤 하는 방법 - [링크]
    • 리액트 캐루셀 - [링크]
    • 나루토 프로필 카드 - [링크]
    • 개발자 프로필 카드 - [링크]
    • 노션 백엔드 자기소개 페이지 - [링크]
  • constants 관리
    • 리액트 디렉토리 구조 - [링크]
    • 자바스크립트 상수 디렉토리 구조 - [링크]

타입스크립트

  • IntersectionObserver - [링크]
  • tsconfig.json 예시 - [링크]
  • tsconfig.json 공식문서 - [링크]
  • string 타입과 string literal 타입의 차이점 (feat. 인덱스 시그니처) - [링크]
  • 타입 파일 이름 컨벤션 camelCase
  • 리액트 타입스크립트 cheatsheet
  • onSubmit 타입스크립트
  • React.CSSProperties에서 제공하지 않는 타입 해결하기 - [링크]

Jest

  • import index.scss unexpected token 해결하는 방법 - [링크]
    • jest.config.js 파일에 moduleNameMapper 추가

ESLint

  • ESLint 설정파일 설명 - [링크]
  • ESLint TypeScript 공식문서 - [링크]
  • airbnb no-unused-vars error 설정 - [링크]
  • airbnb-typescript 규칙 코드 - [링크]
  • devDependencies를 import 할 경우에 no-extraneous 에러 해결 하기 - [링크]
    • 테스트는 devDependency를 허용하도록 rule을 추가한다
    • 구체적 예시: jest-mock-axios - [링크]를 사용해야 할 때 아래 규칙을 추가하면 린트 에러가 발생하지 않는다
    • "import/no-extraneous-dependencies": [ "error", { devDependencies: [ "**/*.test.js", "**/*.test.jsx", "**/*.test.ts", "**/*.test.tsx", "__mocks__/*.ts", ], }, ],

Yarn

  • NPM 사용하지 못하도록 막기 - [링크] [링크]

코드 리뷰

  • by 푸름 - 이벤트 함수 네이밍 - [링크]
 
 

아이디어

  • 회원가입을 완료하면 빵빠레(Hooray, Three.js particles) 애니메이션을 보여준다 (Github 저장소에서 discussion 처음 만드니까 나오더라)
notion image
  • 맵각코에 유저 표시 예시
    • notion image