# 2020.11-12
# 11/16
# Emotion 11
- TS 빌드 시간 및 타입 추론 성능 개선
- Hooks를 잘 사용함으로써 번들 사이즈가 줄어듬
- @emotion/eslint-plugin등의 파서 성능 개선
- 라이브러리 이름이 변경됨 (@emotion/core -> @emotion/react 등): @emotion/pkg-renaming으로 auto fix 가능
# 12/22
# React hooks로 데이터 fetch해오기
https://www.robinwieruch.de/react-hooks-fetch-data
- 느낀점 - 선언적인 리액트 코드에서 비동기 데이터를 페칭하는건 노깔끔... : data/loading/error 상태를 알기 위해 페치시작은 useEffect에서, 각 state는 서로 독립적인 useState에서 관리해야 한다. 그리고 이를 set하는건 또 useEffect 안에 있다. 서로 의존적인 코드니 한군데 뭉쳐있는게 좋을텐데. 예를 들어 error 핸들링을 바꾸고 싶다면 뚝 떨어진 3군데를 건드려야 하는게 짱난다.
- 커스텀 훅을 만들면 그나마 한 줄로 관리할 수 있는데, 이를 만들기 위해 또 위와 같은 코드를 낳아야하는게 불편.
const [{ data, isLoading, isError }, doFetch] = useDataApi(
"https://hn.algolia.com/api/v1/search?query=redux",
{
hits: [],
}
);
- 리듀서훅으로 위에서 따로 관리했던 state를 한 번에 관리하기.
import React, { useReducer } from "react";
const dataFetchReducer = (state, action) => {
// 액션type에 따라 state를 mutate시킨다. 추가적인 데이터는 action.payload에서 받음.
switch (action.type) {
case "FETCH_INIT":
return {
...state,
isLoading: true,
isError: false,
};
case "FETCH_SUCCESS":
return {
...state,
isLoading: false,
isError: false,
data: action.payload,
};
case "FETCH_FAILURE":
return {
...state,
isLoading: false,
isError: true,
};
default:
throw new Error();
}
};
const useDataApi = (initialUrl, initialData) => {
// useReducer: 첫번째인자는 reducer를, 두번째 인자는 initial state 오브젝트를 받는다.
// 그리고 [mutate된 state오브젝트, dispatch함수]를 반환한다.
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: initialData,
});
useEffect(() => {
const fetchData = async () => {
// dispatch함수에 type(필수인자)넘기기
dispatch({ type: "FETCH_INIT" });
try {
const result = await axios(url);
// dispatch함수에 type, payload넘기기
dispatch({ type: "FETCH_SUCCESS", payload: result.data });
} catch (error) {
dispatch({ type: "FETCH_FAILURE" });
}
};
fetchData();
}, [url]);
return [state, setUrl];
};
# 리액트 코드 분할
https://ko.reactjs.org/docs/code-splitting.html
- 번들이 넘 크다면 번들을 나누면 성능up. 런타임에 여러 번들을 동적으로 만들고 불러오기(웹팩, 롤업, Browserify 등에서 지원)
- import 코드분할 (Webpack, Next.js)
// before
import { add } from "./math";
add(1, 2);
// after
import("./math").then((math) => {
math.add(1, 2);
});
- React.lazy (서버사이드렌더링은 불가)
// before
import FooComponent from "./FooComponent"
// after
const FooComponent = React.lazy(() => import "./FooComponent")
function MyComponent() {
return (
// 모듈 로드에 실패시 에러 바운더리로 떨어짐
<MyErrorBoundary>
// lazy컴포넌트는 서스펜스 안에 감쌓여있어야함. 그래야 로드되는중 fallback보여줌
<Suspense fallback={<Loader />}>
<FooComponent />
</Suspense>
</MyErrorBoundary
)
}
- 코드분할 어디서부터 할까? 시작하기 좋은 장소는 라우트. 각 컴포넌트를 lazy하게 가져와보자.
# Error boundary
https://ko.reactjs.org/docs/error-boundaries.html
- js의 try/catch문처럼, 컴포넌트에서 에러나면 catch되어 보여줄 UI 만들기
- getDerivedStateFromError나 componentDidCatch 에서 error 받고, 보여줄 ui render하는 클래스컴포넌트 만든다.
- 이를 컴포넌트처럼 사용. 에러캐치 원하는 컴포넌트 상위에 감싸준다
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
# 전역 상태 관리에 대한 단상 (stale-while-revalidate) by jbee
https://jbee.io/react/thinking-about-global-state
- A, B 컴포넌트에 모두 필요한 api응답값 처리: 1. 둘다 호출 2. Lift state up 3. 전역 redux
- 전역 리덕스의 문제: 내가 원하는 state가 원하는 시점에 존재한다는 보장이X. 그리고 (내가 원하는 만큼)최신 데이터도X. => 걍 데이터가 필요할때마다 api호출하는게 깔끔. 다만 캐싱해서 비용을 업애버렷
- stale-while-revalidate란 HTTP 캐시컨트롤 스펙이 있음
Cache-Control: max-age=<seconds>, stale-while-revalidate=<seconds>
# ex) Cache-Control: max-age=1, stale-while-revalidate=59
# 1초내 재요청하면 캐시값 그대로 /60초 내에 반복발생시에도 캐시값 그대로, 다만 백그라운드에서 새삥으로 갈아치움 / 60초 이후에 재요청하면 다시 서버요청
- 이 swr을 리액트에 적용한 대표적 라이브러리들: swr, react-query, rtk-query.
- 강점: 데이터 가져오는&접근하는 코드가 동일. 주기적으로 revalidate해서 똑똑한 캐싱. (+ 비동기 요청에 따른 status처리, 실패에 따른 retry 처리)
- 굳이 전역상태관리가 필요할 때?: 테마, 다국어처리, 상태 넘 복잡해서 프론트 자체적으로 정규화해서 관리/최적화필요 => Redux나 Recoil 쓸 수 있음.
# 12/23
# React suspense
https://ko.reactjs.org/docs/concurrent-mode-suspense.html
React 16.6+ suspend: 유예하다
- 비동기(데이터, 이미지, 스크립트 등) 작업을 기다릴 때 씀. "컴포넌트! 니 데이터가 아직 준비 안됐어".
- suspense없는 기존방식: 렌더링 직후 불러오기
- 화면에 컴포넌트가 렌더링 완료 이후에야 비로소 useEffect로 데이터 불러옴
- 워터폴 problem: 컴포넌트가 중첩되어있다면 위의 로딩이 풀리는 시점에야 비로소 아래 데이터 가져오기 시작. 사실 필요 데이터들은 병렬로 불릴 수 있었음에도! 근데 promise.all등으로 병렬로 부른다 하면 첫 로딩이 넘 길어짐.
- suspense 도입: 데이터 부른 직후 렌더링 할 수 있음. 데이터는 그 후에 불러와짐!
- 아래 코드 보면 로딩분기 if문이 없다.
- 경쟁상태(Race condition)존재x (a를 불러온 후에 이에 따라 b를 불러야한다는 등..). 시간에 대한 것을 그다지 고려x해도 되기 때문. 데이터 있다 치자~ 하고 코딩. 에러는 try/catch가 아니고 에러 바운더리로 처리.
const resource = fetchProfileData(); // promise가 아님! Relay등 라이브러리의 suspense통합에서 나온 특수객체
function ProfilePage() {
// 1. 렌더링 시도
return (
<Suspense fallback={<h1>Loading profile...</h1>}>
<ProfileDetails user={resource.user} /> 2. 렌더링 시도(트리로 타고
내려옴). 근데 user데이터가 없어서 렌더링 정지
<Suspense fallback={<h1>Loading posts...</h1>}>
// 4. 가장 가까운 서스펜스 Fallback찾음.
<ProfileTimeline posts={resource.posts} /> // 3. 위에 기다지 않고 바로 렌더링
시도. posts데이터가 없어서 렌더링 정지
</Suspense>
</Suspense>
);
}
# 12/25
# React is becoming a black box
https://jaredpalmer.com/blog/react-is-becoming-a-black-box
- 리액트가 어떻게 돌아가는지 모르는 사람들이 늘어남(hook, concurrent mode등 나오면서). 원래 리액트 의도는 "너는 뭘(what) 구현할지 생각만 해, 어떻게(how) 구현할진 우리가 해줄게"였는데 how가 어려워짐.
- 리액트 엔지니어링 디렉터(톰 오치노) 입장: new 페북 빠르게/빨라보이게 하기 위해 CM개발중.. 아직 더 할게 많다. Vaporware(개발 중부터 요란하게 선전하지만, 실제로는 완성될 가능성이 없는 소프트웨어) 아니구 많은 리서치로 만들고있음.
# 12/26
# Concurrent mode랑 suspense로 좋은 UX 만들기
https://ko.reactjs.org/blog/2019/11/06/building-great-user-experiences-with-concurrent-mode-and-suspense.html
- 리액트는 퍼포먼스튜닝(React.memo)등 api제공은 했지만 이보다 하이레벨의 패턴에 opinionated하진 않았었음(모든앱이 빠르길 원하는것도 아니고, 진입장벽 낮추기 위해). but 더 큰 이상을 보기로함. 코드가 더 복잡해지더라도 말야 for UX, 스피드, 다양디바이스, 다양네트워크.
- 외부에는 기반작업인 context api랑 훅부터 내놓음. 페북 내부에는 c.m와 relay로 유연하고 더 app-like한 뉴페북 만들어봄. 첫로딩만 중요한게 아님. 앱안에서 인터렉션할 때 화면이 덜바뀌고 즉시 반응 보이는걸 더 빠르다고 느낌.
- 전통방식: 로딩스피너로 컴포넌트 렌더먼저 하고, mount시에 데이터 fetch한다 / Suspense방식: 로딩될때까지 화면바꾸지 않고 기다리다 스피너 없이 렌더
- 라우터 자체에서 미리 불러올 api들을 정의하면 어떨까? 링크에 hover하면 그때부터 바로 불러오는거지 (다만 데이터 열라 잡아먹음)
# 12/27
# React v17.0 (2020.10)
https://reactjs.org/blog/2020/10/20/react-v17.html
- 2.5년만의 메이저 업데이트. 사용자에게 뉴피쳐는 없고, 리액트 내부 업글이다. 나중에 릴리즈할 피쳐들 위한 디딤돌
- 메이저 업데이트는 바뀐 api 대응때문에 사실 챌린징한 일이었음(legacy context api deprecation등은 자동코드변경도 불가능). 그래서 이번 v17부터는 점진적인(gradual) 업뎃 가능하도록. 여기는 17쓰고, 다른곳은 lazy load로 16쓰고 할 수 있단거지(권장하는건 아님. 불가피할 때 쓸 수 있도록). 이를 가능하게 하기 위해 리액트 내부 이벤트 시스템을 많이 바꿔야 됐음. 그래서 이번 17이 뉴피쳐 없지만 메이저 업데이트인것임.
- 이벤트 델리게이션 내부변경: button에 onClick달면 리액트 내부적으로는 document노드에 버블링시킨다(성능개선때문). 근데 점진업뎃으로 리액트 버전 2개씩 쓰면 여기서 문제가 생김. 그래서 17부터는 document가 아닌 root DOM에 붙이기로 함.
- 외에도 전체적으로 이벤트 관련 내부 변경이 많다. 조금 더 DOM/JavaScript 기본 이벤트와 가까워지고 예상했던대로 리액트 이벤트 코드가 동작하게 됨.
- useEffect 클린업 function: 기존엔 동기로 불렸는데 이제 비동기로! 좋네. 동기로 하고싶으면 useLayoutEffect 쓰삼.
# 12/28
# 웹팩 개념들
https://joshua1988.github.io/webpack-guide/concepts/overview.html https://velog.io/@pop8682/%EB%B2%88%EC%97%AD-%EC%99%9C-babel-preset%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%98%EA%B3%A0-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80-yhk03drm7q
- entry: 여기있는 파일들을 변환할거시다
- output: 변환한 결과물은 여기에 저장할거시다
- loader: (module.rules[]에 넣는다) js말고 다른 자원(html, css, image, font등)도 변환하고싶다. 특정 파일(정규식으로 검사)에 ㅁㅁ로더를 쓴다고 명시. (자주사용: babel-loader, sass, file, ts) (use에 배열로 로더를 넣으면 오른쪽에서 왼쪽으로 적용)
- plugin: 변환된 결과물의 형태를 바꿈. (e.g. HtmlWebpackPlugin - 빌드 결과물로 html 파일 생성, ProgressPlugin - 웹팩빌드 진행률 표시, @babel/plugin-transform-arrow-functions: 바벨로 arrow function es5로 컴파일)
- mode: development | production - 에 따라 웹팩 결과물 다르게.
- preset: 플러그인 하나하나 설치하기 귀찮. 모아놓은 세트가 preset. (e.g. @babel/preset-react, @babel/preset-typescript)
# npx
https://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner (npm 5.2이상이면 자동설치)
- 로컬 패키지에서 script run할때 직접 ./node_modules/... 들어가지 않고도 간단커맨드로 실행 가능(
npx mocha
) - 로컬에 굳이 설치하기 싫은 일회성 커맨드 클라우드에서 실행 (
npx create-react-app foo
). 일단 설치하고 실행 후 자동제거시킴. 이게 cra팀도 더 좋은게 사람들이 항상 최신버전 쓸테니까.
# 12/29
# Parcel
https://parceljs.org/ https://kdydesign.github.io/2020/09/23/parcel-intro/
- 빠르고(캐싱 덕 - 웹팩 20s/파슬9.9초/파슬2회차2.6초) 설정 필요없는(러닝커브 낮음) 번들러
- 코드분할이 기본(모듈 필요시 자동으로 하위번들 처리)
- 플러그인 없이 html, css, file 등 처리, babel, postcss, posthtml 등 여러 트랜스파일러 내장
- 오픈소스X
# 12/30
# Snowpack
https://heropy.blog/2020/10/31/snowpack/ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import https://www.daleseo.com/js-module-import/
- 번들러가 아님
- babel, ts등으로 파일 빌드하고 -> js의 import/export문법으로 브라우저에서 개별 로드.
- 개빠름, 변경사항 브라우저 즉반영, TS/JSX/CSS 모듈 등 기본지원, 다른 번들러(웹팩, 롤업 등)랑 통합가능
- 의존성관리: node_modules의 모든 의존성 모듈을 js로 바꾸고 브라우저에서 직접실행(js의 import문법)
- snowpack.config.js로 관리
# 12/31
# Webpack 5 릴리즈노트 (2020.10)
https://webpack.js.org/blog/2020-10-10-webpack-5-release/
- 2년만의 메이저 업데이트(리액트랑 비슷하네). 아키텍쳐 개선과 이게 수반해야만 가능한 피쳐릴리즈. 4랑 5 둘다 메인테이닝할거임-완성본x. Breaking changes가끝났다는 의미로 릴리즈함.
- 웹팩은 오픈소스. 기업이 주도X. 코로나 때문에 큰타격입음. 상황 나아질때까지 월에 10일만 pay줄 수 있다(컨트리뷰터/메인테이너들이 후원금으로 돈을 받는군!).
- 릴리즈 방향
- 캐싱 - Persistent caching으로 빌드 퍼포먼스 업, Long term cachibng으로 알고리즘 업
- 번들사이즈, 트리셰이킹 업
- 웹 플랫폼과 호환성 업
- 4를 breaking changes업게 만드느라 짰던 이상한 코드 clean up
# 리액트 API콜 어디서? constructor vs componentWillMount vs componentDidMount
https://medium.com/@santoshpunase/integrating-apis-in-react-js-constructor-vs-componentwillmount-vs-componentdidmount-e0b98c3efecd
최근에 함수형컴포넌트+useEffect만 쓰다가 강의찍기위해 클래스컴포넌트 보는데 api콜 어디서 했는지 헷갈려서 찾아봄.
- contructor에서? X: initial state랑 이벤트핸들러 바인딩이 목적인 곳. 데이터페칭은 사이드이펙트 일으킬 수 있어 여기서 하지 말아라. 일단 setState 자체도 막아둠.
- componentWillMount에서? X: render전에 불리니 여기 둬야 가장 빠르지 않을까 하는데 그렇지도 않은게 직후에 render가 불려 로딩상태 피할 수 없음. 그리고 SSR에서도 불리기 때문에(추가 데이터 페칭 불가한..) api가 불필요하게 2번 불리게 됨. 그리고 이 생명주기 메서드가 deprecated되었슴.
- componentDidMount: 걍 mount 되고 부르는게 젤 깔끔.