Coding (164)

06
10

무엇을 상상하든 그 이상을 보게되는 Javascript

1. lodash?

  • 데이터 처리를 위해 다양한 함수를 가지고 있는 라이브러리이다.
  • 배열을 반복시키거나, 객체 형태의 데이터를 정렬하거나 하는 등의 작업을 쉽게 할 수 있다.

2. 예시

예시1 : gropBy(array | object, 규칙)

groupBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': [4.2], '6': [6.1, 6.3] }
 
groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }

예시 2 : join(array, '사이에 넣어줄 string')

join(['a', 'b', 'c'], '~');
// => 'a~b~c'
  • 이와 같은 다양한 함수들이 존재한다. 그래서 처리하기 괴로운 데이터 형태로 백엔드에서 넘겨받는다고 하더라도 찰떡같이 잘 바꿔 사용하면 된다.

3. 단점

  • 용량이 커서 사용하지 않는 경우가 있다고 한다...! (친구 개발자 프로젝트에서는 사용할 때 그런 이슈가 있었다고 하는데, 잘 모르겠다. 함수 덩어리들이 용량이 커봐야 그리 크지 않을 것 같다.)
  • 나중에 알고리즘 문제 풀때 답답하다. 데이터를 받으면 lodash 함수부터 생각난다. (요즘에는 면접 때도 Ai 한테 물어보고 하는 경우도 있다고 하니 뭐 비슷한 경우가 아닐까? 오히려 이쪽이 더 낫지 않나...? 하는 개인적인 생각)

4. 그래서 어떻게 사용할 것인가?

  • 물론 프로젝트에서는 이렇게 저렇게 잘 사용하면 된다. 하지만...?
직접 lodash 함수를 구현해 본다면...?
  • 실무에서 쓰기 좋은 함수들의 모음 -> 만약, 실무에서 쓰지 못 하는 프로젝트를 만난다면? -> 직접 구현!
  • 실무에서 적용시키기 어렵고 복잡한 알고리즘 문제들보다 더 활용도가 높을 것이다!라는 결론이다.

5. 연습하는 법

  1. https://lodash.com/docs 로 접속한다.
  2. 스크롤을 랜덤으로 쭉 내린다 (순서대로 하는 것은 추천하지 않는다. 데이터 형태별로 순차로 적혀있어서 배열과 객체를 섞어서 하는 게 더 헷갈리고 도움이 된다.)
  3. 아무 함수나 찝고 함수를 하나 만들고, 예시와 똑같이 동작하도록 만든다.
  4. 시간이 남거나 기회가 된다면 실무처럼 똑같은 환경이라고 생각하고 만든다. 예를 들어, for 문을 사용하지 않는다거나, map을 reduce로 바꾸어 최적화시킨다거나, if문이 중첩 될 경우 삼항연산자로 바꾼다거나 하는 등으로 말이다.
COMMENT
 
06
06

전형적인 릴리즈 15분 27초 전 모습.jpge

1. 왜 필요한가?

  • 눈으로 다 확인할 수 있는 프론트 기능들이지만, 모든 부분을 완벽하게 테스트하기는 쉽지 않다.
  • 그렇게 모든 상황을 다 테스트 하더라도, 시간이 너무 오래 걸린다.
  • 프로젝트의 사이즈가 커지면 커질수록, 협업을 하게 되는데, 동료 개발자가 작업했던 부분을 작업하게 되는 경우가 종종 생긴다. 이때, 의도되지 않은 버그가 있다면 그대로 배포될지도 모른다. 이때, 테스트를 통해 검사할 수 있다. => 실무에서는 이전버전에서 이미 배포된 기능의 코드를 이번 버전에서 고칠 경우가 생기는데 이미 이전 버전에서 Qa가 끝난 부분이기 때문에 자세히 보지 않는 경우가 있다. 이럴 경우 치명적인 버그가 배포될 경우가 생기기 쉽다.
  • 가장 중요한 것은, 릴리즈 당일, 팀원들과 가족 같은 분위기를 형성하게 될 수 있는 상황을 미연에 방지한다.

2. 유닛 테스트

  • 작은 단위(컴포넌트 정도)의 기능동작을 확인하는 테스트
    • 컴포넌트가 잘 렌더링 되는가?
    • 함수는 올바르게 동작하는가?
    • state를 올바르게 사용하고, 올바르게 setState 하는가?

3. 통합 테스트

  • 프로젝트가 전체적으로 올바르게 동작하는지를 확인하는 테스트
    • 수많은 컴포넌트들이 서로 잘 동작하며 각각의 역할을 올바르게 하여 정상적으로 동작하는가?
    • DOM 이벤트가 올바르게 화면을 변화시켜 주는가?

4. Jest

자바스크립트 코드 테스트 자동화 Tool 중 대표적인 Jest

  • 가장 단순한 테스트 툴, 기본적인 설정을 해 줄 필요가 없어서 간단하다.
  • 스냅샷 기능을 지원하여, 오브젝트가 변경되면 에러를 보여줄 수 있다. -> 렌더링 된 컴포넌트의 변경도 체크할 수 있다.
  • 테스트 코드는 작업물과 완전히 분리되어 있다.
  • API도 -coverage 옵션을 사용해 커버리지를 확인할 수 있도록 되어 있어, 매우 단순하다.

5. 실전 테스트

  • 먼저 프로젝트를 생성한다. (cra을 해도 테스트해 보기는 괜찮다! 다 깔려있다! 만약, yarn init 했다면 babel도 설치해 주자 yarn add --dev babel-jest @babel/core @babel/preset-env)
  • 생성한 프로젝트에 yarn add -D jest
  • package.json에 script에 test 명령어를 넣어준다.
{
  "name": "jest-test",
  "version": "1.0.0",
  "main": "index.ts",
  "license": "MIT",
  "scripts": {
    "test": "jest"
  },
  "devDependencies": {
    "jest": "^29.5.0"
  }
}
  • index.js에 함수를 만들어 준다. 나는 NaN을 판별해 주는 함수를 만들 것이다.
export const checkNaN = (value) => {
  return !Object.is(value, NaN) ? "참" : "거짓";
};
  • 그리고, index.test.js파일을 test 폴더에 만들어서, jest가 검사할 수 있도록 한다.
  • 그리고 몇 가지 검사를 한다. 방법은 test('로그', 콜백함수)의 형태로 작성한다. 아래는 예시이다.
import { checkNaN } from "../index";

test("1번 : 1은 NaN이 아니다!", () => expect(checkNaN(1)).toBe("참"));
test("2번 : 3 + 6은 NaN이 아니다!", () => expect(checkNaN(3 + 6)).toBe("참"));
test("4번 : 10 / abc은 NaN이 아니다!", () => expect(checkNaN(10 / "abc")).toBe("거짓"));
  • 결과를 보기 위해 yarn test를 입력하면?

야호! 100점

6. toBe

  • 위에서 사용한 toBe 함수는 완전히 같은 요소인지를 검사한다.
  • Object일 경우 같은 메모리 참조가 아니기 때문에 toBe로 검사할 수 없다.
  • 비슷한 toBe시리즈 함수로는 toBeTruthy, toBeFalsy, toBeNull이 있다. 사용법은 똑같으니 생략

7. toEqual

  • Object형태까지 구분이 가능하다.
const getFriend = (name, age) => {
  return {
    name,
    age,
  };
};

test("내 친구의 이름은 hendrix, 나이는 20이야.", () => expect(getFriend("hendrix", 20)).toBe({ name: "hendrix", age: 20 }));
  • 간단하게 친구 정보를 불러오는 함수이다.

땡! 심지어 deep equality하게 검사해야 통과한다고, toStrictEqual을 사용하란다. 친절 그잡채

test("내 친구의 이름은 hendrix, 나이는 20이야.", () => expect(getFriend("hendrix", 20)).toStrictEqual({ name: "hendrix", age: 20 }));

코드를 수정한 이후 성공!

  • 여기서 toEqual과 toStrictEqual의 차이는 toStrictEqual가 더 엄격하게 undefined도 허용하지 않는 함수이다. 

8. 여담

  • 부트캠프를 진행하면서 보았던 문제 형식의 스프린트는 이런 식으로 만드는 구나를 깨달았다. (그럼 계산을 위한 코드도 어딘가에 공식처럼 적혀 있다는 것인데... 그거 뜯어보면 전부 간단하게 해결할 수 있다는 것인데... 그걸 찾는다면 이미 개발 실력이 늘어날 듯하다)
  • 한 발자국 더 나아가면, 저런 테스트 코드를 캡슐화하여 잘 숨겨놓고, 더 어렵게, 그리고 더 엄격하게 검사하는 코드도 넣으면 참 좋은 스프린트를 만들 수 있지 않을까?라는 생각도 들지만 이미 부트캠프를 나온 지 1년 6개월은 넘어서 의미가 없다 흑흑,
  • 실무에서는 테스트 코드를 작성할 수 있을까?라고 한다면 스타트업의 경우 거의 불가능... 하지 않을까 싶다. 규모가 그리 크지 않고, 프론트 개발팀 구성원이 몇 명 없는 경우, 항상 개발시간에 쫓겨가다 보면, 데드라인 맞추기도 급급해서 테스트 코드까지 작성할 시간이 항상 부족하다. 조금만 여유가 있다면, 오히려 Qa 시간도 줄이고, 탄탄한 코드로 이후 유지보수가 뛰어나다는 생각을 운영진도 해 줬으면...!이라고 생각만 하고 있다 흑흑.
  • 하지만 짬날 때마다 테스트 코드를 작성해 놓으면 충분히 도움이 될 것이다. 지금 업무로 진행하는 프로젝트의 특성(기획이 한 번에 픽스되어 나오지 않고, 조금씩 쌓이고 쌓이고 하는 구조) 덕분에 같은 작업자들끼리 기능적으로 꼬이는 부분이 있었는데, 테스트 코드가 있다면 충분히 커버될 것이라고 생각했다.
COMMENT
 
05
31

이제 없이 코딩하라고 하면 마치 왼손만 쓰면서 밥을 먹는것 같은 느낌이 듭니다. 그만큼 강합니다.

1. tailwind css?

  • https://tailwindcss.com/
  • 기가 막힌 css-in-js를 가능하게 하는 라이브러리이다.
  • bootstrap과 비슷하게 미리 정해진 속성을 className안에 부여해서 css파일을 따로 두지 않아도 인라인으로 코드를 짤 수 있다.
  • 일반적인 css와 쓰는 방법이 조금 다르지만, 빠르게 적응할 수 있고 다양한 설정도 가능해 매우 편하다. 
	.container {
		height: 16px;
        width: 16px;
        border: 1px solid;
        border-radius: 16px
	}
  • css로 작성하면 이렇게 되지만,
<div className='w-4 h-4 border-1 rounded-2xl'/>
  • tailwind는 이렇게 한 줄이 끝이다.

2. 장점 (feat. 왜 나는 환장하는가?)

  • tsx파일과 css파일을 오고 가고 할 필요 없이 한 번에 적어서 시간절약에 탁월하다.
export const box = `flex flex-col items-center w-6 h-6 border-1 border-white bg-red-200`

<div>
	<span className={box}>안녕</span>
    <span className={box}>잘가</span>
</div>
  • className을 고민하지 않아도 된다. 반복해서 사용해야 하는 스타일 일 경우 string으로 스타일 자체를 설정해서 여기저기 파일에서 import 해서 사용할 수 도 있다. 위의 코드처럼 사용하며, 추가로 다른 스타일을 넣어 줄 수도 있다.
  • 자체 스타일 가이드를 제공하여 동일한 사이즈와 컬러로 통일시켜서 디자인 할 수 있다.

3. 단점

  • 가독성이 나쁘다.
    • 스타일 자체를 변수로 잘 선언해서 재사용한다면 어느정도 커버가 가능하다.
    • config 파일을 고쳐서 일정한 커스텀한 스타일을 추가로 넣어줄 수도 있다. ex) text-14 text-white => text(14 white)
  • 어디에 어떤 속성을 넣었는지 혹은, 관심사 분리가 떨어져 유지보수가 곤란한 경우가 생긴다.
    • 작업자들 간의 사용을 문서화하고, 잘 협의한다면 크게 문제가 되지 않는다. (사실 config 파일을 잘 살펴보면 모두 일정한 규칙이 있을 것이므로 크게 어렵진 않다. 그리고 디자이너와 협업하는 경우라면 스타일 가이드를 만들어 주기 때문에 그 스타일 대로 공용 컴포넌트 등을 작업해 놓으면 통일시켜서 사용할 수 있다)

4. 한발짝 더 나가기 (feat. twind)

tailwind + emotion + styled componets, 진국의 짬뽕탕

  • https://twind.dev/
  • tailwind처럼 css-in-js와 컴포넌트화 및 추가 스타일 설정등 거의 무적의 css 라이브러리 끝판왕이다.
  • 이미 이전에 tailwind와 emotion을 사용한 적이 있는 나의 경우 러닝 커브는 없었다. 다양한 라이브러리를 알 고 있으면 알고 있을수록 유리하다.
import { tw, css, apply } from '@/twind';

const tempBox = apply`w-4 h-4 border-1 border-white bg-red-100`;

const App = () => {
	return (
    	<>
			<div className={tw`w-4 h-4 border-1 border-white bg-red-100`}>하이?</div>
            <div className={tw(tempBox)}>하이?</div>
            <div className={tw(tempBox, css`
            	${apply`w-10`}
                posision: absolute;
                top: 0;
                left: 50%;
            `)}>반가워</div>
        </>
    )
}
  • 위와 같이 혼용해서 사용가능하다. 가독성 및 재사용이 개선될 수 있다.
COMMENT
 
05
31

진짜 docs 100번 이상은 정독했다. 씁쓸하다.

1. 문제의 발단

  • 평소에 매우 자주 사용하는 지도 API다. (... 였다) 전투적으로 약 8달 이상은 사용했던 네이버 지도에서 쉽고도 어렵게 사용했던 Marker 시스템을 정리해 보려고 한다.

2. 기본적인 사용법

const loadScript = () => {
  return new Promise((resolve) => {
    if (document && document.querySelectorAll('#naver-map-sdk').length === 0) {
      const script = document.createElement('script');
      script.id = 'naver-map-sdk';
      script.src = `https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${NCP_CLIENT_ID}&submodules=geocoder,panorama`;
      script.onload = () => {
        resolve(true);
      };
      document.head.appendChild(script);
    } else {
      resolve(true);
      console.log('script already loaded');
    }
  });
};
  • 위와 같이 script를 심어준다. await로 지도 init시 호출하게 된다.
 const initMap = async (containerId: string, isActiveDistance: number) => {
	// 스크립트 로드
	await loadScript();

    // 지도 생성시 설정해 줄 옵션이다.
    const initialMapOptions = {
      center: [point.longitude, point.latitude],
      zoom: 16,
      minZoom: 12,
      maxZoom: 22,
      zoomOrigin: [point.longitude, point.latitude],
      pinchZoom: false,
      mapDataControl: false,
      disableDoubleClickZoom: false,
      disableDoubleTapZoom: false,
      logoControlOptions: {
        position: naver?.maps?.Position?.BOTTOM_LEFT,
      },
      mapDataControlOptions: {
        position: naver?.maps?.Position?.BOTTOM_CENTER,
      },
      scaleControlOptions: {
        position: naver?.maps?.Position?.BOTTOM_LEFT,
      },
    };

    const map = new naver.maps.Map(containerId, initialMapOptions) as any;

    new naver.maps.Circle({
      map: map,
      center: [point.longitude, point.latitude],
      radius: isActiveDistance + 100,
      fillColor: '#ffffff',
      fillOpacity: 0.2,
      strokeOpacity: 0,
    });

    // 마커생성은 함수로 만들어서 쓰고 있다.
    const pinMarker = makeIconMarker(map, [point.longitude, point.latitude], 'pin');

    // 지도 옵션 (마커나 지도에 커서를 바꿔줌)
    pinMarker.setCursor('default');
    map.setCursor('default');

    return map;
  };

  useEffect(() => {
    initMap('mainMap');
  }, []);
  • 이런 식으로 지도를 생성해 주고,  map이 들어갈 div에 id 값을 알맞게 준다.
  • 각종 지도 옵션 및 마커, 도형 등의 옵션은 docs를 참고한다. (https://navermaps.github.io/maps.js.ncp/docs/)

3. Marker에 대한 고찰

  • 지도에서 특정 마커를 지우거나 선택등을 하려면 그 특정 마커 요소 자체를 정확히 알고 가지고 있어야 한다.
  • 이때, state로 마커를 가지도 다닐 수는 없다.
  • 예를 들어, maker.setMap(null)로 특정 마커를 지울 수 있다. 이때, state에 마커를 넣어놓고 지우려고 하면 올바르게 동작하지 않는다.
  • 이때는 useRef를 사용한다.
const markerRef = useRef<naver.maps.Marker>();

    const marker = makeMarker(map, point, {
      value: 100
    });

markerRef.current = marker;

useEffect(() => {
    markerRef.current?.setMap(null);
    
    const marker = makeMarker(map, point, {
      value: 200
    });
    
    marker.setMap(mapRef.current);
    markerRef.current = marker;
    }
  }, [isActive]);
  • isActive가 변경될 때마다, 기존의 value 100의 마커는 사라지고, value 200을 가진 마커가 생성된다. (isActive가 단 한 번만 바뀐다는 전제 하에 이렇게 만들 수 있지만 급조한 코드라 어색하다)
  • 마찬가지로 마커 뿐 아니라 map 객체 자체나 도형들과 같은 지도 위 요소들도 다 useRef로 관리가 가능하고, 이를 list형태로 저장하여 사용할 수 도 있다.
COMMENT
 
1 2 3 4 5 6 7 ··· 41