March 12, 2022 • ☕️☕️☕️ 14 min read
유캔두 100일 종료!
document.activeElement
를 통해 현재 focus 된 element 요소를 가져올 수 있다. modal이 띄워진 상태에서 새로운 modal을 띄울 때, 가장 최근에 focus 된 element (기존에 띄워진 modal의 자식 element)를 저장해두고, 새로운 modal을 focus한다.
그리고 focus된 새로운 modal을 닫을 때, 이전에 저장해둔 element를 다시 focus 함으로써 focusing 대상이 첫번째 modal의 자식이 될 수 있게 구현한다. (실제 이벤트 동작을 위해서는 이벤트 버블링을 이용한다.)
위와 같은 방식으로 띄워진 modal의 순서에 맞춰 focusing이 가능해졌다. react-component dialog가 위와 같은 방식으로 구현되어 있다.
TS 4.6 이전 버전에서는 Discriminated union(Tagged union)을 구조 분해 할당할 경우 TypeScript가 문맥을 잃어 태그에 따른 나머지 필드에 대한 타입을 잡아주지 못했지만, 이제는 잡아줄 수 있다.
type Action = { tag: "a"; payload: number } | { tag: "b"; payload: string };
type A = {
tag: "a";
payload: number;
};
type B = {
tag: "b";
payload: string;
};
type AorB = A | B; // tagged union
function typeCheck(param: AorB) {
const { tag, payload } = param;
if (tag === "a") {
// v4.6 : payload는 number로 추론됨
// v4.5 : payload는 number | string으로 추론됨
console.log(payload.toFixed()); // v4.5에서만 error
} else {
// v4.6 : payload는 string으로 추론됨
// v4.5 : payload는 number | string으로 추론됨
console.log(payload.toLocaleUpperCase()); // v4.5에서만 error
}
}
// v4.5에서 error가 나지 않으려면
function typeCheckAgain(param: AorB) {
if (param.tag === "a") {
console.log(param.payload.toFixed());
} else {
console.log(param.payload.toLocaleUpperCase());
}
}
추가적으로, 이전에는 jsx 코드를 컴파일할 경우 의미 없는 void 0
이라는 코드가 붙어서 컴파일되었지만, 이제는 이 부분이 제거되었다. 그래서 번들 사이즈가 향상되었다.
React-Router v6부터 Routes 이하에는 Route
와 React.Fragment
만 들어갈 수 있다.
Routes 이하의 컴포넌트에 제약을 걸어, 복잡하지 않고 효과적인 경로 분기 처리를 위함이다.
// 가능
<Routes>
<Route path={...} element={<Foo/>}/>
<>
<Route path={...} element={<Bar/>}/>
</>
</Routes>
// 불가능
<Routes>
{routes.map(({ path, element }) => (
<RouteWrapper key={path} path={path} element={element}/>
)}
</Routes>
// ...
function RouteWrapper(info: RouteInfo): React.ReactNode {
return <Route {...info} />
}
Art direction은 각기 다른 디스플레이 사이즈에 맞추어 보여지는 이미지를 제공하고자 하는 것이다. 데스크톱 화면에서는 전체 풍경을 보여 주는 가로 이미지를 보여주고 모바일 화면에서는 좀 더 편하게 볼 수 있게 세로 이미지를 제공한다.
미디어 쿼리를 통해 웹페이지 가로모드 / 세로모드에 맞춰 css를 작성할 수 있다.
@media (orientation: portrait)
세로모드@media (orientation: landscape)
가로모드Ref https://studiomeal.com/archives/1068
리액트에서 비즈니스 로직과 뷰를 분리하기 위해 custom hook을 사용할 수 있다. 이를 hooks & presentational pattern 이라고 한다.
많은 경우 custom hook은 반복되는 로직을 줄이는 목적으로 사용을 하는데, 컴포넌트 서비스 로직의 대부분을 custom hook에 넣고 컴포넌트에서 사용되는 데이터들만 따로 반환해 줌으로써 view와 logic을 분리할 수 있다.
하지만 잘못 사용하게 되면, 어떤 내용으로 코드가 동작하는지 로직들이 훅에 가려져서 코드 파악이 어려워 지니, 당장 몰라도 되는 디테일만 custom hook으로 빼내고, 코드 파악에 핵심이 되는 요소들은 컴포넌트에서 직접 다루는 연습이 필요하다.
Ref https://felixgerschau.com/react-hooks-separation-of-concerns/ https://vallista.kr/Component-분리의-미학/
리액트에서 innerHTML
대신 string 형태의 HTML 문자열을 렌더링하기 위한 속성이다.
innerHTML
은 XSS 공격에 취약하기 때문에 위험하다는 걸 상기시키는 역할을 한다. <div dangerouslySetInnerHTML={ {__html: ${stringHTML}} }>
와 같은 방식으로 작성한다.
dangerouslySetInnerHTML
을 사용하면 리액트는 해당 요소의 컨텐츠가 동적이라는 것을 알고, 해당 노드의 자식에 대해서는 가상 DOM을 비교하는 과정을 수행하지 않는다.
유저가 많은 양의 텍스트를 입력하여 해당 내용을 보여주고자 할 때와 같은 경우, 데이터에 html 태그가 표함되어 있는 경우 등에서 사용할 수 있다.
예를 들어,
const App = () => {
const data = "lorem <b>ipsum</b>";
return <div>{data}</div>;
};
export default App;
위와 같은 경우 <b>
태그는 html 태그로 인식되지 않고 <b>ipsum</b>
가 문자열 그대로 화면에 출력될 것이다. 이럴 때 dangerouslySetInnerHTML
을 사용하여 <b>
태그를 올바르게 인식하여 html을 출력하게 해준다.
const App = () => {
const data = "lorem <b>ipsum</b>";
return <div dangerouslySetInnerHTML={{ __html: data }} />;
};
export default App;
Ref https://ko.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml https://blog.logrocket.com/using-dangerouslysetinnerhtml-in-a-react-application/
ReactNode
는 ReactElement
이자 & JavaScript의 원시타입이다. 가장 넓은 타입을 가지고 있고, 클래스 컴포넌트에서 render()
메소드의 반환 값으로 사용된다.
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode =
| ReactChild
| ReactFragment
| ReactPortal
| boolean
| null
| undefined;
ReactElement
는 type
과 props
를 가지는 타입으로 props
및 type
의 타입을 지정할 수 있다. 함수형 컴포넌트의 반환 값으로 사용된다.
interface ReactElement<
P = any,
T extends string | JSXElementConstructor<any> =
| string
| JSXElementConstructor<any>
> {
type: T;
props: P;
key: Key | null;
}
JSX.Element
는 ReactElement
의 제네릭 타입에 any
가 지정된 타입이다. JSX.Element
는 글로벌 네임스페이스로 정의되어있어 각 라이브러리에서 지정하는 방식으로 설정할 수 있다.
// React에 지정된 JSX.Element
declare global {
namespace JSX {
interface Element extends React.ReactElement<any, any> { }
toPrecision()
메서드는 Number
객체를 지정된 정밀도로 나타내는 문자열을 반환한다. 인자로는 유효 자릿수를 지정하는 정수를 넣는다. 아무것도 넣지 않으면 값을 그대로 반환한다.
function precise(x) {
return x.toPrecision(4);
}
console.log(precise(123.456));
// expected output: "123.5"
console.log(precise(0.004));
// expected output: "0.004000"
console.log(precise(1.23e5));
// expected output: "1.230e+5"
123.455.toPrecision()
과 같이 number 값을 그대로 사용할 수는 없다.
const num = 123.456;
num.toPrecision();
유의할 점은, 숫자가 아닌 문자열을 반환한다는 것이다! 그리고, 유효자릿수가 작아 큰 자리 단위수를 반환하면 e+
형태의 지수로 값을 표현한다.
let num = 927;
let n = num.toPrecision(1);
console.log(n); // '9e+2'
Ref https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision
Array.prototype.every()
은 빈 배열 순회 시 true
를, Array.prototype.some()
은 빈 배열 순회 시 false
를 반환한다. every()
는 ‘어긋나는 게 있는지 검사’하고, some
은 ‘충족하는 게 있는지 검사’한다고 생각하면 편하다.font-style
의 기본값은 none
이 아니라 normal
이다.git remote prune origin
명령어를 사용하여 local에 존재하는 유효하지 않은 ref를 제거할 수 있다src/pages
하위에 존재하는 모든 .tsx
파일을 자동으로 직접 접근이 가능한 라우트로 취급한다. 따라서 도메인 컴포넌트(페이지가 아닌 다른 컴포넌트)는 다른 디렉토리에 옮겨야한다 (ex. src/common/pages
)a
태그의 href
속성에는 파일이 존재하는 경로를 지정하고, download
속성에는 파일명을 집어넣어 클릭 메소드를 호출해서 원하는 파일을 다운로드 받을 수 있게 구현할 수 있다..code-workspace
파일을 이용해 monorepo에서 multi-root workspace를 구현할 수 있다. (Ref)enum
도 Object.keys()
의 인자로 들어갈 수 있다.key
값을 바꿀 때 컴포넌트를 리렌더링시킬 수 있다. (Ref)only-of-type
은 같은 유형의 형제가 없을 때 적용된다.여태 본 SOLID 원칙에 대한 설명 예제 중에 가장 이해가 잘 된다! 🤓
Ref https://blog.bitsrc.io/solid-principles-in-typescript-153e6923ffdb
테스트 코드는 작동 원리를 쉽게 이해할 수 있게끔 작성해야 하며, 주석이 필요 없을 정도로 간단해야 한다. 조건부 (if~else
) 테스트 로직은 테스트를 실제보다 더 복잡하게 만드는 요인 중 하나다.
조건부 로직을 테스트에서 사용한 경우에 어떻게 개선할 수 있을까?
첫 번째, 테스트 실행마다 결과가 달라지는 경우다. 매번 테스트를 수행할 때마다 테스트 환경이 변경되는 경우 하나의 테스트 코드가 수많은 케이스를 고려해야만 한다.
이런 경우 테스트 환경을 직접 제어하는 코드를 작성한다. 여러 케이스를 하나의 테스트로 검증하고 싶다면 Parameterized Test를 작성하면 된다.
두 번째, 테스트에서 프로덕션 로직을 사용한 경우다. 하나의 메소드에서 모든 입력값에 대한 결과를 검증하기 위해서 테스트 내부에 로직을 넣게 된다. 이렇게 작성된 테스트는 프로덕션 로직에 변경이 있을 때마다 테스트 코드도 함께 변경해야만 한다.
이러한 SUT(System Under Test)를 테스트하기 위해서는 미리 계산된 값을 사용할 수 있다. 또는 첫 번째 경우와 마찬가지로 Parameterized Test를 사용할 수 있다.
세 번째, 중복 코드를 리팩토링하는 과정에서 발생한다. 테스트 코드에서 중복되는 코드를 제거하고자 검증 부분만 조건문으로 작성하게 된다면 테스트 코드를 이해하기 더욱 어려워진다.
이 경우, 리팩토링을 하더라도 2가지의 전제조건을 가져가면 좋다.
관련 코드는 링크에서 다시 확인해보자!
Ref https://jojoldu.tistory.com/642?fbclid=IwAR39pHWihFnYuudqUr5ScRVFpFhKDeNX0nLZiz4Xs_AvwmzmCe51TsqkgIA
팀에서 핫한 최신 기술만을 좇다가는 어떻게 되는지 보여주고 있다. 기술의 등장 배경이나 원리, 사용 시 장단점을 제대로 파악해보지도 않은 채 그저 트렌드라는 이유로, 작성법이 fancy해보인다는 이유로 여러 라이브러리/프레임워크 등을 써왔던 지난 날들을 반성하게 해준다…
결정하기 전에 연구하고 테스트하기 가 좋은 방법이라는 것은 알지만, 실제 바쁘게 업무를 하면서 신기술에 대한 연구와 테스트를 진행하는 것도 많은 경우 현실적으로 어려운 상황이라는 것도 안다. 😵
Ref https://lazygyu.net/blog/hype_driven_development
리액트에서 디렉토리 구조를 나누는 것은 정말 귀찮은 일이다. 하나의 best practice라는 게 존재하지도 않고 (구글에 ‘react folder structure best practice’를 검색하면 수많은 글들이 나온다…), 도메인마다, 또는 팀의 스타일에 따라서 달라지는 법이다.
이 아티클에서는 기능별로 폴더의 구조를 나누는 방법을 소개하고 있다. 파일의 ‘타입’이 아닌 ‘기능(feature)’를 중심으로 폴더 구조를 나누는 것이다.
└── src/
├── components/
├── contexts/
└── hooks/
위와 같은 폴더 구조는 ‘나는 리액트 앱이다’라고 강력하게 얘기하고 있는 반면,
└── src/
├── features/
│ ├── todos/
│ ├── projects/
│ ├── ui/
│ └── users/
└── pages/
├── create-project.js
├── create-todo.js
├── index.js
├── login.js
├── privacy.js
├── project.js
├── signup.js
└── terms.js
위 폴더 구조를 보고 우리는 이 앱이 ‘프로젝트 관리 툴’이라는 것을 알 수 있다.
아래는, 덧붙여 소개하고 있는 Best Practices들의 내용이다.
Ref https://profy.dev/article/react-folder-structure
글의 요약은 다음과 같다.
페이스북을 비롯한 실리콘 밸리의 이름난 기업들, FAANG 아니 MAANG… 실제로 이렇게 일하고 있는지 궁금하다! 한번 찾아가 보고 싶다. 자칫하면 국내의 그 어떤 기업들보다도 성과 중심적인 치열한 분위기가 되지 않을까 싶기도 하고.
아래 문장이 기억에 남는다.
개발자의 성과에는 책임이 있더라도 성장에는 책임이 있지 않습니다.
그리고, 개발자로서 증명해보일 수 있는 성과를 만들어내기 위해 구체적으로 생각해볼 지표들. 즉 ‘프로젝트 영향력’을 잊지 않고 잘 챙겨야겠다.
프로젝트 영향력은 그 명칭으로 짐작하실 수 있듯 프로젝트에 공헌한 성과들입니다. “AA 를 유지 보수했음.” 혹은 “BB 파트를 맡아 스프린트를 N 개 진행했음” 이런 것은 성과로 증명할 수 없는 내용입니다.
메서드 체이닝 형식으로 유효성 검사를 할 수 있는 라이브러리다. custom한 rule을 만들어서 적용할 수도 있다. v8n의 풀네임은 당연하게도 ‘validation’이다.
아래와 같이 작성한다. 상당히 fancy하다! (하지만 설레발 주도 개발의 위험성을 잊어서는 안될 것이다…)
v8n().string().first("H").last("o").test("Hello"); //true
Ref https://imbrn.github.io/v8n/#what-s-v8n
대통령이 바뀌었다. 아직 취임을 안했으니 바뀐 건 아니지만… 앞으로의 5년이 어떻게 될지… 어떻게든 되겠지…
작년 12월 1일에 시작했던 유캔두도 끝났다! 진짜로 내가 100일 연속 인증을 채웠다. 유캔두 주최자였던 웨지에게 기프티콘을 선물로 받았다. ㅋㅋㅋ 그리고 포코가 지정한 다음 유캔두 공동 주최자가 되었다. 😱 열흘 쉬고, 다시 시작!!!
대선일과 주말에 쉬는 동안 운전 연수를 받았더니 갑자기 자신감이 생겼다. 면허 딴지 4년 만에 처음 운전대를 잡아보는데, 시내도로와 고속도로도 타고, 꽉꽉 막힌 강남대로와 올림픽대로를 지나 양평까지 다녀왔다. 셀프 주유도 해봤다. 다음 달부터 쏘카 빌려서 드라이브 다녀야지 😎
회사에서 맡은 티켓은 끝날 듯 끝나지 않고 있다. 수많은 코드리뷰를 받고, QA에서 잘못된 부분들을 검수받고 나니 아직 갈 길이 먼듯 싶다. 운전은 “초보인데 어쩌라고” 마인드로 했지만, 회사에서는 ‘신입이니까 그럴 수 있어’로 살 수만은 없다. 그런 시간은 아아아주 짧게 가지고, 신입이어도 능력을 낼 수 있는 팀원으로 거듭나기 위해 분발해야겠다. 너무 스트레스는 받지 말구 🤩