June 4, 2022 • ☕️☕️ 12 min read
꿈에 그리던 제주도 코딩(?)
이미지 로딩이 실패 했을 때 대체 이미지를 띄우려면 onError
핸들러에서 currentTarget.src
에 대체 이미지를 넣어줄 수 있다.
<img src={originImg} onError={handleImgError} />;
const handleImgError = (e) => {
e.target.src = defaultImg;
};
function sayHi(): void {
console.log("Hi!");
}
let speech: void = sayHi();
console.log(speech); // undefined
일반적으로 void
는 함수가 아무것도 리턴하지 않을 때 사용한다. 그러나 함수의 실제 리턴 값은 undefined
일 것이다.
타입스크립트 문서에는 다음과 같이 설명되어 있다.
Contextual typing with a return type of void does not force functions to not return something.
리턴 타입으로 void
를 명시한 함수에게 무언가를 리턴하도록 강제하지 않을 뿐, 실제로는 무언가 값을 리턴해도 상관없다. 따라서 함수의 리턴 타입을 void
로 선언한 후 number
, string[]
등의 값을 리턴한다고 해서 에러가 나지 않는다.
const f1: () => void = () => {
return "foo"; // ✅ OK
};
사실 이는 타입체킹을 하지 않는 것이 아니라, 문자열을 반환해도 void
를 반환하는 것과 다름없기 때문이다.
declare const a: () => string;
declare const b: () => void;
function doSomething(): void {
a();
b();
}
일반적으로는 string extends void
는 유효하지 않지만, 함수 반환 타입에 있어서는 string
이 void
보다 넓은 타입, 즉 서브타입이라고 해도 무방하다. 공변과 반변에서, 반환 타입은 공변함을 알 수 있다.
아래와 같은 함수 타입을 정의했을 때,
type F<T> = () => T;
함수 a
와 b
는 각각 다음처럼 표현할 수 있다.
type A = F<string>;
type B = F<void>;
string
은 void
보다 넓은 타입이기 때문에, b
의 자리에 a
를 넣는 것이 가능하다.
declare let a: A;
declare let b: B;
b = a;
그러면 아래와 같은 코드가 가능하다.
let b: () => void = () => "dd";
그러나 리터럴 함수 정의문이 void
타입을 리턴한다면 해당 함수는 절대로 아무것도 리턴해서는 안 된다.
function f2(): void {
return true; // 🚨 Type 'boolean' is not assignable to type 'void'
}
const f3 = function (): void {
return true; // 🚨 Type 'boolean' is not assignable to type 'void'
};
이는 그저 특이 케이스라고 하는데, 이럴 거면 그냥 void
타입은 아무것도 리턴하게 하지 말지. 괜히 헷갈리게.. 🤨
URI는 인터넷에 있는 자원을 나타내는 유일한 주소
encodeURIComponent
- URI의 특정한 문자를 UTF-8로 인코딩한다.decodeURIComponent
- encodeURIComponent
, 또는 비슷한 방식으로 만들어진 URIComponent를 해독한다.22년 4월 17일에 react-redux v8이 릴리즈되었다.
타입스크립트에서 useSelector
의 state
인자의 기본타입이 DefaultRootState
였는데 unknown
으로 바뀌었다.
기존에는 useSelector
를 사용할 때 useSelector((state: RootState) => state.someReducer);
와 같이 매번 RootState 써주기 번거로워 아래와 같이 작성 후,
declare module "react-redux" {
interface DefaultRootState extends RootState {}
}
useSelector(state => state.someReducer);
로 사용했었는데 이제 이런 코드는 유효하지 않게 되었다!
이유는 수많은 코드에서 라이브러리의 타입을 덮어씌우는 행위가 프로젝트와 Redux 라이브러리 간의 타입을 간섭하고 오염시킨다고 판단한 것 같다.
위 방법은 막혔지만, 아래처럼 useSelector
자체를 선언해버리면 된다.
declare module "react-redux" {
export declare const useSelector: <T>(
selector: (state: RootState) => T,
equalityFn?: EqualityFn<T>
) => T;
}
console.log()
에 %c
(치환 문자)를 사용하면 스타일을 설정할수 있다.
console.log()
에 복잡한 객체를 출력할 때는 객체를 깊은 복사(Deep Copy) 해주어야 정확한 값이 출력된다. ([object object]
로 출력되는 경우)
console.log(JSON.parse(JSON.stringify(obj)));
객체는 참조형 데이터이기 때문에 console.log
를 사용한 시점의 객체값을 보장할 수 없다. JSON 문자열로 변환(stringify
)했다가 다시 객체로 변환(parse
) 해줌으로써 객체에 대한 참조를 없앨수 있다.
console.table()
을 사용하면 배열이나 JSON 형태의 값을 key-value 형태의 테이블 형식으로 보여준다.
tsconfig.json
에서 일반적으로 typeRoots
에 node_modules/@types
를 설정해놓기 때문에, .d.ts
파일을 포함한 모듈은 @types/~~
로 배포해야 한다.
빌드된 모듈을 배포할 수도 있다. output: { ... libraryTarget: 'umd' ... }
을 명시해주면, umd 로 패키지를 번들할 수 있다.
이때 모듈을 빌드된 상태로 배포를 하면, 트리셰이킹이 되지 않을 수 있다. 모듈을 사용하는 프로젝트의 번들 크기가 커질수 있으니, react와 같이 큰 라이브러리는 번들 대상에서 제외시켜주고 peerDependencies
에 넣어주는 것이 좋다.
Semantic Versioning은 버전을 표시할 때 자주 사용되는 규약 (npm, node 모두 이 규약을 따름)으로, MAJOR, MINOR, PATCH로 구분된다.
Dependencies
에 기재된 버전에 붙은^
(캐럿) 기호의 의미: MINOR나 PATCH 버전은 하위호환성이 보장되므로 업데이트를 한다.
1.0.2
: >=1.0.2 <2.0
1.0
: >=1.0.0 < 2.0
1
: >=1.0.0 < 2.0
userEvent.click()
을 사용할 때 해당 엘리먼트의 상위 엘리먼트에서 pointer-events: none
속성을 가지고 있다면 throw 에러를 발생시킨다.
<div className="App">
<h2>RTL Test Sample</h2>
<div style={{ pointerEvents: "none" }}>
<div>{count}</div>
<div style={{ pointerEvents: "auto" }}></div>
</div>
</div>
<button onClick={() => setCount(count + 1)}>Click Me!</button>
// userEvent.click() => throw Error("Unable to perform pointer interaction as the element inherits `pointer-events: none`")
이 때 skipPointerEventsCheck
옵션을 활성화시켜 엘리먼트를 클릭할 수 있다.
userEvent.click(elem, undefined, { skipPointerEventsCheck: true });
일반적으로 :first-child
는 자식의 첫번째 요소를 의미하고, :first-of-type
은 div:fist-of-type
과 같이 어떤 태그의 첫번째 요소를 뜻한다.
태그 없이 :first-of-type
을 사용했을 때는, 모든 엘리먼트의 첫번째 요소들을 의미한다. 즉 기본 선택자 없이 :first-of-type
을 사용하면 전체 선택자(*)가 암시된다.
const DSSample: React.FC<SampleProps> = (props) => {
return (
<Article>
<h1>h1</h1>
<p>Paragraph 1.</p>
<h1>h1</h1>
<p>Paragraph 2.</p>
<p>Paragraph 3.</p>
<div>hh</div>
</Article>
);
};
export const Sample = DSSample;
const Article = styled.article`
& :first-of-type {
color: red;
}
`;
운영중인 서비스를 중단하지 않고 신규 소프트웨어를 배포하는 기술이다. 무중단 배포의 핵심은 로드밸런서를 통해 연결된 서로 다른 인스턴스에 트래픽을 제어해 배포하는 것이다.
무중단 배포는 크게 세 종류로 나뉜다.
롤링 배포
블루-그린 배포
카나리 배포
@testing-library/react
를 사용하는 경우 screen.debug()
를 통해 현재 렌더되고 있는 JSDOM을 console로 확인할 수 있다.configure({ isolateGlobalState: true })
설정이 필요하다.와! 정말 fancy하다. recording 버튼을 클릭하여 사용자 플로우를 그대로 녹화하고, 재생하고, 수정과 측정까지 가능하다. 구글에서 제공하는 coffee-cart 사이트에서 체험 가능하다. 기록 결과를 JSON이나 Puppeteer로 export도 가능하다고 한다!
Ref https://developers.google.com/codelabs/devtools-recorder#0
아직도 CSS 잘 모르는데 이것저것 또 추가되고 바뀌고~! State of CSS 2022를 살펴보면 된다.
킹콜라스 선생님의 영상에서 간략하게 살펴볼 수도 있다.
상당히 fancy하고 그동안 왜 안 해줬나 싶었던 기능들이 포함됐다! :has
라니.. 유용하게 사용할 수 있을 것 같다. @scope
도 유사한 기능을 보여주고 있다. 그리고 css에도 중복 코드를 줄이기 위해 @nest
가 등장했다. 상당히 어색하지만 혁신적이다.
지난주 엠티를 다녀오자마자 제주도 여행을 갔다. 사전투표하고 갔당. ㅎㅎ 개발자 남자친구와 함께 노트북까지 챙겨서 갔고, 남자친구가 일 좀 해야 한다고 했는데, 정작 남친 말고 내가 일했다 😭 바다가 보이는 카페에서 미팅도 참석하고, 방에 돌아와서 코드도 짰다. 그렇게 아주아주 나쁘진 않았다. 놀러가서도 일하는 개발자가 멋져보이는 아직 1년차 개발자… 하지만 나는 멋지지 않았다. 배포 전까지 요구사항이 조금씩 바뀌는데 그 점을 고려하지 못하고 휴가쓰고 갔다온 것 같다. 아직 이런 회사 업무와의 밸런스 조절이 어려운 것 같다.
그래도 평화롭게 (운전도 무사히 하고 ✌️) 잘 다녀왔더니 팀명이 바뀌어 있었다! 알고는 있었지만 뚝딱 바뀌어버리니 괜히 어색. 이름도 엄청 길당. fancy한 축약어가 나왔으면 좋겠다. 정말정말로 모든 팀원분들, 실장님까지 참석하신 프론트엔드 송별회 겸 회식에서 늦게까지 신나게 놀다왔다. 다들 알고보면 정말 재밌는 분들이다 내가 제일 조용할 지도 모른다 🤫