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.01.0: >=1.0.0 < 2.01: >=1.0.0 < 2.0userEvent.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한 축약어가 나왔으면 좋겠다. 정말정말로 모든 팀원분들, 실장님까지 참석하신 프론트엔드 송별회 겸 회식에서 늦게까지 신나게 놀다왔다. 다들 알고보면 정말 재밌는 분들이다 내가 제일 조용할 지도 모른다 🤫