전체 컨셉절대 경로 자동 완성리액트 아이콘 라우팅이모션 클래스 네임배포CSS in JSHTTPS디자인Git테스트HTTP API 모킹리액트리코일리액트 쿼리Axios 미디어 쿼리 2021로띠웹 접근성 a11yvscode카카오 지도맵각코기능 개발타입스크립트JestESLintYarn코드 리뷰아이디어
프로젝트를 진행하면서 알게 되거나 참고한 자료들을 모아둡니다.
test@gmail.com / 1q@W3e$R
전체 컨셉
- 드리블 주황색 대시보드 - [링크]
절대 경로 자동 완성
리액트 아이콘
라우팅
이모션 클래스 네임
- ts-loader와 babel-loader 함께 사용하기
배포
CSS in JS
HTTPS
디자인
Git
[core] ignorecase = false
테스트
- 검색하다가 찾은 2019년 NHN Toast 테스트 자료 - [링크]
- Cypress
- 설치
- 커맨드
- 예제
- Jest에서 cypress 테스트 코드 무시하기
HTTP API 모킹
리액트
- input 컴포넌트 값을 브라우저가 자동완성 옵션 보여주는 것 끄기:
autocomplete="off"
리코일
- 테스팅
- RecoilRoot에 초기값을 넣어주면 된다
나한테 적용한 코드:
function renderMainPage() { return render( <QueryClientProvider client={queryClient}> <RecoilRoot initializeState={(snap) => snap.set(currentUserState, randomUserInfo()) } > <MemoryRouter> <ThemeProvider theme={theme}> <MainPage /> </ThemeProvider> </MemoryRouter> </RecoilRoot> </QueryClientProvider> ); }
리액트 쿼리
Axios
미디어 쿼리 2021
로띠
웹 접근성 a11y
vscode
- 우연히 눌러본 단축키
cmd + F12
카카오 지도
- 카카오 지도 API 문서 - [링크]
- CSS 맵 마커 흔들흔들 - [링크]
- 구글 맵 마커 예시 문서 - [링크]
- velog - 참조할 만한 지도 테스트 코드 - [링크]
- 오프 멘토님이 추천해준 맵 테스트 예시 코드 - [링크]
- 스크립트 태그 onload 사용한 테스트 코드 - [링크]
- 이미지 요소 드래그 안되게 막기:
pointer-events: none
- 맵각코 지도 디버깅 코드
코드
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;
맵각코
기능 개발
- 메인 화면 컨셉
- Mac-ish 글래스모피즘 glassmorphism - [링크]
- 계정 이미지 드롭다운 메뉴
- 자기 소개
타입스크립트
- IntersectionObserver - [링크]
- tsconfig.json 예시 - [링크]
- tsconfig.json 공식문서 - [링크]
- string 타입과 string literal 타입의 차이점 (feat. 인덱스 시그니처) - [링크]
- 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
코드 리뷰
- by 푸름 - 이벤트 함수 네이밍 - [링크]
아이디어
- 회원가입을 완료하면 빵빠레(Hooray, Three.js particles) 애니메이션을 보여준다 (Github 저장소에서 discussion 처음 만드니까 나오더라)

- 맵각코에 유저 표시 예시
