April 1, 2022 • ☕️☕️☕️☕️ 19 min read
꽃가루를 날려~
분 단위를 문자열로 나타낼 수 있는 경우는 ‘00’ ~ ‘59’로 60가지 케이스가 존재한다. 이 60가지를 손수 타이핑하여 type을 지정하기에는 너무나 버겁다.
이럴 때 literal template을 활용하여 간편하게 타이핑할 수 있다. 아래와 같은 코드로 타입을 정의하면 ‘00’~’59’ 까지 받을 수 있는 타입이 완성된다.
type ZeroToFive = "0" | "1" | "2" | "3" | "4" | "5";
type ZeroToNine = ZeroToFive | "6" | "7" | "8" | "9";
type minute = `${ZeroToFive}${ZeroToNine}`;
여러 개의 커밋을 cherry pick 하고 싶을 때, range cherry pick을 하면 된다.
git cherry-pick {cherry pick할 시작 커밋}^..{cherry pick할 마지막 커밋}
cherry pick할 시작 커밋
뒤에 ^
를 빼면, cherry pick할 시작 커밋
의 바로 다음 커밋부터 cherry pick이 된다.
시간이 없다 → 더미데이터 사용(json | JS object)
await sleep(3000)
처럼 호출 지연이 포함된 자유로운 형태로 만들 수 있다.시간이 있다 → Mock 서비스워커 사용.
개발중인 프론트의 origin 과 호출 할 API의 origin이 다를 경우 CORS 이슈가 발생할 수 있다. 이때 API 서버에서 응답 헤더에 매번 Access-Control-Allow-Origin
을 설정해주는 것 보다 프록시를 수동으로 설정해주는 것이 리소스 절약에 도움이 된다.
프록시 설정 방법
lib
의 역할tsconfig에 lib
가 지정되지 않으면 target
에 따라 기본 리스트가 삽입된다.
ScriptHost
: Window Script Hosting System(WSH) API와 관련된 라이브러리다.
예를 들어, lib: ["ES2020"]
과 같은 식으로만 lib
을 추가하면 DOM 관련 코드를 사용하는 곳에서 에러가 발생하여 타겟 버전으로 빌드가 안될 수 있다. 이때, lib를 처음 추가하면 기본적으로 제공되는 리스트가 덮어씌어지기 때문에 함께 넣어주는 것이 좋다.
lib: ["DOM", "ES5", "ES2020", "ScriptHost"];
Link
태그Next의 Link
태그는 child에 따라 처리해야할 일이 달라진다.
<a>
태그로 감싸기만 하면 된다.Link
에 passHref
prop을 추가해야 한다.React.forwardRef
로 감싸고, Link
에 passHref
prop을 추가해야 한다.cypress에서 input value를 가져오기 위해서는 invoke 메서드를 활용해야 한다.
.invoke('val')
.then(value => {
// 이 value 값이 input value
});
cypress는 기본적으로 rtl에서 사용하는 getByText
같은 메서드가 없는데, 이때 cypress용 rtl을 설치하거나 아래와 같은 방식으로 구현할 수 있다.
cy.get("tbody").contains("배달상세페이지").click();
cypress에서 cy.get("body > div")
는 cy.get("body").within(() ⇒ {cy.get("div")})
으로 풀어쓸 수 있다.
dynamic routing을 리터럴 그대로 활용할때는 (ex. 특정페이지를 찾거나 페이지가 맞는지 확인하는 로직) :
문자를 염두에 두고 코드를 작성해야 한다.
const route = routes.find((it) => {
const compareTargetPath = it.path
.split("/")
.map((it_) => (it_.includes(":") ? ":id" : it_))
.join("/");
const compareLocationPath = `/${location.pathname
.split("/")
.filter((it_) => !!it_)
.map((it_) => {
if (!isNaN(Number(it_))) return ":id";
return it_;
})
.join("/")}`;
return compareTargetPath === compareLocationPath;
});
이때, react-router-dom의 matchPath
를 활용하면 코드를 더 간략하고 직관적으로 작성할 수 있다.
return matchPath(location.pathname, { path: it.path, exact: true }) !== null;
각 페이지에 관한 route config 파일을 만들고 모든 페이지를 한번에 관리하면, 타입추론 및 공통화된 속성을 통한 코드 작성이 용이하다.
export interface RootComponentProps {
readonly path: string;
readonly name: string;
viewport?: string;
exact?: boolean;
}
export interface RouteConfigure<
P extends { exact?: boolean; sidebar?: boolean }
> extends Omit<RootComponentProps, "exact"> {
readonly id: string;
component: React.FC<P>;
props?: Partial<P>;
}
export function createRouteConfigure<
T extends RouteConfigure<P>,
P = Pick<RootComponentProps, "exact">
>(params: Readonly<T[]>): (RouteConfigure<P> & T)[] {
return params as unknown as (RouteConfigure<P> & T)[];
}
const routes = createRouteConfigure([
{
id: "로그인",
path: "/login",
name: "로그인",
component: LoginPage,
props: {
exact: true,
sidebar: false,
footer: false,
spa: false,
},
},
{
id: "검증",
path: "/authorize",
name: "검증",
component: AuthorizePage,
props: {
exact: true,
sidebar: false,
footer: false,
spa: false,
validation: false,
},
},
// ...
]);
standard-version 라이브러리를 사용하면 편하다.
장점은 다음과 같다.
Ref https://musma.github.io/2020/07/27/changelog.html](https://musma.github.io/2020/07/27/changelog.html
useQuery
의 data를 다른 hook에서 사용하기useQuery
의 data 값을 다른 hook에서 사용해야 하는 경우, data의 값이 최초 undefined
일 때를 제외할 수 있는 최선의 방법은 무엇일까?
useEffect
를 사용해서 data 값이 변경되면 해당 hook의 값을 세팅한다.
➡️ useEffect
의 사용을 줄여주는 게 react-query
의 장점이기도 한데, 이를 살리지 못한다.useQuery
전용 HOC를 만들어서 data가 undefined
가 아닐 때만 하위 컴포넌트를 렌더링한다. 이렇게하면 하위 컴포넌트가 사용하는 hook의 data는 반드시 undefined
가 아니다.
➡️ pending/fulfilled 상태를 구분할 수 있는 react-query의 장점을 굳이 HOC로 다시 구현하는 느낌이다.Placeholder and Initial Data in React Query 포스팅을 참고해봐도 좋을 것 같다.
컴포넌트의 타입을 선언할 때 optional 타입은 지양하는 것이 좋다. 실제 사용부에서 props로 값을 받을 때 optional로 받을지 여부는 컴포넌트의 관심사가 아니며, 인터페이스의 타입은 원본 데이터 타입을 그대로 유지하는 것이 좋다.
optional을 사용한다면 필드 하나를 필수값으로 변경해도, 모든 사용부에서 수정이 필요해져 사용성이 굉장히 나쁘다.
// optional 사용
export interface ButtonProps {
id?: number;
name?: string;
disabled?: boolean;
onChange: (value: string) => void;
}
// 사용부
const CuteButton: VFC<ButtonProps> = ({ onChange }) => {};
const NiceButton: VFC<ButtonProps> = ({ id, disabled, onChange }) => {};
const GoodButton: VFC<ButtonProps> = ({ id, name, disabled, onChange }) => {};
위 코드에서 만약 name
필드가 필수값이 되면, ButtonProps
와 CuteButton
, NiceButton
의 사용부에서 모두 수정이 필요하다.
혹은 만약 title
이라는 필드가 새롭게 필요해진다면? ButtonProps
와 title
을 받지 않았던 모든 사용부에서 수정이 필요하다.
원본 데이터 타입은 유지하고 사용부에서 따로 타입을 선언해서 사용한다면, 값 하나를 필수값으로 변경한다거나 필드를 추가하는 등의 변경이 일어나도 사이드 이펙트가 적다.
// 원본 데이터 유지
export interface ButtonProps {
id: number
name: string
disabled: boolean
onChange: (value: string) => void
}
// 무엇을 optional로 받을 것인지 여부를 각 사용부에서 결정하고 수정한다
type CuteButtonProps = Partial<Omit<ButtonProps, 'onChange'>> & Pick<ButtonProps, 'onChange'>
type GoodButtonProps = Partial<Omit<ButtonProps, 'name' | 'onChange'>> & Pick<ButtonProps, 'name' | 'onChange'>
const CuteButton: VFC<CuteButtonProps> = (props) => {}
const GoodButton: VFC<GoodButtonProps> = (props) => {}
// 사용부
<CuteButton onChange={() => {console.log('큐트')}} />
<GoodButton id={3} name='버튼' onChange={() => console.log('좋아')} />
위 코드에서는 ButtonProps
에 title
이라는 필드가 추가되어도 CuteButton
, GoodButton
에는 아무런 영향이 없다.
또는 GoodButton
에서만 name
을 필수로 받고 싶다면? GoodButtonProps
만 수정하면 된다. 이때 CuteButtonProps
를 사용하는 CuteButton
에는 사이드 이펙트가 없음.
또 다른 방법을 살펴보자.
보통 컴포넌트의 prop은 필수값들이 optional 값보다 적은 경우가 일반적이다. 위에서 작성한 것처럼 사용부에서 따로 타입 선언을 한다면, ButtonProps
프로퍼티를 모두 optional로 만들고, 확장하는 곳(CuteButtonProps
)에서 필요한 prop들만 required로 바꿔주는 접근도 가능하다.
이때 일부 프로퍼티를 optional ➡️ required로 또는 반대로 바꾸는 Utility Type을 만들어두면 유용하다.
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
type WithOptional<T, K extends keyof T> = Omit<T, K> & { [P in K]?: T[P] };
[P in K]-?
에서-?
표시는, 해당 인터페이스에서 optional을 모두 빼주겠다는 의미다. (optional 필드들을 required 값으로 바꿔준다.)
처음의 GoodButtonProps
의 인터페이스는 아래와 같이 작성할 수 있다.
type GoodButtonProps = WithOptional<ButtonProps, "name" | "onChange">;
JavaScript에서 undefined
는 아직 값이 할당되지 않은 상태의 변수의 값을 나타낸다. let
키워드로 선언한 변수나 (값을 할당하지 않은 경우), 아무것도 반환하지 않는 함수의 리턴값이 undefined
에 해당한다.
반면 null
은 ‘할당을 위한 값’이라고 할 수 있다. null
은 JavaScript 엔진이 자동으로 할당하는 것이 아니라, 개발자가 직접 특정 변수가 비어있음을 나타낼 때 사용한다.
typeof null
은 object
며, typeof undefined
는 undefined
이다. null
이 object
타입인 것은 JavaScript 설계 초기 단계에서의 실수라고 알려져있으며, type이 undefined
인 것은 오로지 undefined
뿐이다. (즉 undefined
는 JavaScript에서 타입으로도, 값으로도 쓰인다)
궁금했던 것은, 동치 연산(==
또는 ===
)과 falsy 연산을 쓸 때 null
과 undefined
의 계산이 항상 헷갈렸기 때문이다.
null == undefined; // true
null === undefined; // false
null
과 undefined
는 공통적으로 ‘값이 없음’을 뜻하기 때문에, 느슨한 동치 연산 비교(==
)에서는 true
를 반환한다.
하지만 null
은 object
타입, undefined
는 undefined
타입으로, 서로 타입이 다르기 때문에 엄격한 동치 연산 비교(===
)에서는 false
를 반환한다.
사실 대단히 어려운 문제도 아닌데 괜히 헷갈렸다. 원래 하다보면 가장 기본적인 것들이 헷갈리기 마련이라고 위로해본다.
Ref https://medium.com/@paytonjewell/javascript-eli5-null-vs-undefined-f7112a2b72dd https://steemit.com/kr-dev/@cheonmr/js-operator
Content-Type: boundary
HTTP의 POST
메서드를 사용할 때 헤더 Content-Type
에 boundary
라는 key값을 사용할 수 있다.
Content-Type: multipart/form-data; boundary=something
boundary는 메시지 파트를 구분하는 역할을 하며, 메시지의 시작과 끝 부분도 나타낸다. 첫 번째 boundary 전에 나오는 내용은 MIME을 지원하지 않는 클라이언트를 위해 제공된다.
네트워크를 통해 메시지 및 파일을 전송할 때 패킷을 한번에 보내지 않고 나눠서 보내게 되는데, 여러 부분(multipart)으로 나누려면 각 부분의 경계를 표시해야 하며, 이 경우 메시지 및 파일의 전송되는 내용의 구분자로 사용한다.
boundary를 선택하는 것은 클라이언트의 몫이며, 보통 무작위의 문자를 선택해 메시지의 본문과 충돌을 피한다.
Ref https://lena-chamna.netlify.app/post/http_multipart_form-data/ https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Content-Type
useRef
사용하기DOM 요소에 직접 접근하거나, 리액트 컴포넌트의 리렌더링 시에도 유지되는 값을 사용하기 위해서 useRef
hook을 사용할 수 있다. 이때 useRef
를 TypeScript와 함께 사용한다면, useRef
의 타입에 유의해야 한다.
@types/react 레포를 보면 useRef
는 3개의 정의가 오버로딩되어 있다.
useRef<T>(initialValue: T): MutableRefObject<T>
인자의 타입과 제네릭의 타입이 T
로 일치하는 경우, MutableRefObject<T>
를 반환한다.
MutableRefObject<T>
의 경우, 이름에서도 볼 수 있고 위의 정의에서도 확인할 수 있듯 current
프로퍼티 그 자체를 직접 변경할 수 있다.
useRef<T>(initialValue: T|null): RefObject<T>
인자의 타입이 null
을 허용하는 경우, RefObject<T>
를 반환한다. RefObject<T>
는 current
프로퍼티를 직접 수정할 수 없다. current
가 readonly
인 RefObject
를 반환하기 때문이다.
useRef<T = undefined>(): MutableRefObject<T | undefined>
제네릭의 타입이 undefined
인 경우(타입을 제공하지 않은 경우), MutableRefObject<T | undefined>
를 반환한다. 이렇게 사용할 경우 useRef
가 반환하는 객체를 DOM 요소의 ref
프로퍼티에 집어넣을 수 없다.
import React, { useRef } from "react";
const App = () => {
const inputRef = useRef<HTMLInputElement>();
return (
<div className="App">
{/* 🚨 Type 'MutableObject<HTMLInputElement | undefined> is not assignable... */}
<input ref={inputRef} />
</div>
);
};
따라서 DOM을 직접 조작하기 위해 프로퍼티로 useRef
객체를 사용할 경우, RefObject<T>
를 사용해야하므로 초깃값으로 null
을 넣어줘야 한다.
Ref https://driip.me/7126d5d5-1937-44a8-98ed-f9065a7c35b5
:focus-within
은 포커스를 받은 요소를 포함하고 있는 요소를 나타낸다. form 안에 input 필드 중 하나가 focus 되었을 때 form을 컨트롤 할 수 있어 유용하다.align-items
의 값이 flex-start
면 flex container 기준으로 시작점에 붙고, baseline
이면 text의 밑줄 부분을 기준으로 정렬된다.100.toString()
의 경우, (소수점) 다음에 숫자가 올 것으로 기대하였지만 갑작스레 문자열이 나타난 것으로 인식하여 오류를 발생시킨다. (100).toString()
이나 100..toString()
으로 쓸 수 있다.Capitalize
내장 유틸타입이 있다.input
이나 textarea
태그에서 글씨 영역을 선택(강조)하고 싶을 경우, select()
메소드를 사용하면 영역이 선택된다.case-sensitive-paths-webpack-plugin
플러그인이 개발환경에서 잡아준다.)Core Web Vital이 개발과 비즈니스 측면에서 중요한 이유와, 그 사례들을 소개하고 있다.
Ref https://web.dev/vitals-business-impact/
기술이란 지난 문제들을 해결하기 위해서 만들어졌기 때문에, 왜 이러한 기술이 필요했고 어떠한 발전 과정을 거쳤는지 파악하는 것이 중요하다.
절차적 프로그래밍에서는 실행 순서를 강제로 바꾸는 것이 아니라 일정하게 반복되는 코드를 따로 만들어두고, 그에 해당하는 코드를 호출하고 나서 다시 원래 자리로 돌아오는 방식의 프로시저(함수)를 통해 개발이 진행된다. 즉, 데이터와 데이터를 처리하는 동작을 함수 단위로 코드를 분리하고 재사용하는 형태다.
var hp = 100
var mp = 100
gameloop:
...
if (key == 'A') {
goto magic
}
...
goto gameloop
magic:
mp -= 10
...
goto gameloop
그러나 절차적 프로그래밍은 기본적으로 전역 변수를 사용하기 때문에, 코드의 규모가 커질 때 골치가 아파진다는 문제가 있다. 서로 연관이 있는 데이터들을 하나로 묶어 namespace처럼 관리하여 해당 변수에 접근을 할 수 있는 구조체라는 형식이 탄생한다.
var character = {
name: "teo.yu"
hp: 300
mp: 500
}
function useSkill(character) {
...
character.mp -= 100 // 변수를 직접 수정하게 됨.
}
do_something(character)
의미 있는 단위로 변수들을 하나로 묶음으로써 변수 명의 중복을 줄이고 함수나 배열 등에서도 하나의 변수처럼 활용할 수 있게 되었다.
이렇게 데이터를 중심으로 코딩하는 것의 유용성이 퍼지게 되며 객체지향 프로그래밍이 등장하였고, 구조체와 항상 쓰이는 함수들을 하나로 묶어서 구조체와 함께 함수까지 포함하는 개념을 만들어 이를 class
라고 불렀다.
// class
class Character {
name = "teo.yu"
hp = 300
mp = 500
attack() {...}
useSkill() {...}
moveTo(toX, toY) {...}
}
// object
var character = new Character();
character.attack();
character.useSkill()
character.jump();
서로 독립적으로 작동하는 작은 부분들을 조립하고 결합하는 방식이 되었고, Class
와 Object
의 개념이 등장한다. 이런 식으로 작은 문제를 해결하는 것들을 모아서 하나의 문제를 해결하는 프로그램으로 개발하는 방식을 Bottom-up 방식이라고 한다.
이렇게 프로그램을 객체로 바라보는 관점으로 프로그래밍을 하는 것을 Object-Oriented Programming (OOP) = 객체지향 프로그래밍 이라고 부르게 되었다.
🤓 뒷 이야기는 👇 아래 링크에서~
가 나왔당. React와 함께 그 역사를 지켜보며 쑥쑥 자라나는 기분
Ref https://nodejs.myeongjae.kim/
Ref https://fig.io/
Ref https://github.com/nvbn/thefuck
‘주어진 업무를 수행할 때, 업무의 목표가 무엇인지 정확히 이해하고 수행하는 사람’!
철학자 드라이퍼스 (아무리 봐도 ‘드레퓌스’가 더 자연스러운데…)가 제안한 기술 습득의 5단계 모델을 소개하고 있다. 철학자들은 정말 별걸 다 분석한다!
드라이퍼스 모델이 5단계의 구분에서 제안하는 중요한 요소는 바로 직관 이라는 요소다. 인간은 구체적인 경험으로부터 직관을 얻을 수 있고, 이것은 컴퓨터와 같은 기계로 추론해 내는 것은 대단히 힘들다는 것이다. 소위 말하는 ‘짬’이 아닐까 생각한다.
무슨 느낌인지는 알겠다. 아직 Competent 단계까지 가기에도 멀리있는 것 같지만, 한 단계씩 조금씩이나마 성숙해지기 위해 부단히 경험을 쌓아나가야 할 것이다.
Ref http://blog.lastmind.io/archives/593
가구들이 하나 둘 도착했다. 독립하면 바꾸겠다고 했는데, 2년이라는 시간은 기다리기에 꽤나 긴 것 같다. 아직 다 꾸미진 않았는데, 지금까지도 맘에 든다. 일단 물건들을 정리하면서 많은 것들을 갖다 버린 게 홀가분하다.
회사에서는 2월부터 함께 해왔던 컴포넌트 하나..가 아니고 두 개를 드디어 내보냈다! 나가자마자 버그가 있어서 핫픽스했지만 ㅠ 별건 아니지만 그래도 뿌듯한 기분이다.
주말엔 전주여행을 다녀왔다. 올해 처음이기도 하고, 정말 오랜만의 여행인 것 같다… 작년 6월 이후 거의 1년만이다. 😵 날씨는 따뜻했지만 사람이 말도 안되게 많았고, 벚꽃은 아직 덜 폈다. 의도치 않게 먹짱 여행을 하고왔다. 다음 일주일은 좀 굶어야겠다.