diff --git "a/\354\261\225\355\204\260_12/\353\260\225\354\212\271\355\233\210.md" "b/\354\261\225\355\204\260_12/\353\260\225\354\212\271\355\233\210.md" new file mode 100644 index 0000000..03dc491 --- /dev/null +++ "b/\354\261\225\355\204\260_12/\353\260\225\354\212\271\355\233\210.md" @@ -0,0 +1,464 @@ +## 리액트 + +### 리액트의 기본 개념 + +#### JSX + +- XML과 유사한 구문을 사용하여 HTML을 자바스크립트에서 사용할 수 있게 해주는 확장 문법 +- 자바스크립트로 변환됨 + +#### Props + +- 리액트 컴포넌트의 내부 데이터 +- 컴포넌트가 만들어지기 전에 미리 결정됨 +- 컴포넌트로 전달되고 나면 읽기 전용이 됨 + +#### 하이드레이션 + +- 서버에서 생성한 마크업의 UI가 상호작용할 수 있게 만드는 과정 +- 자바스크립트 번들이 로드되고 이벤트 핸들러 등이 DOM에 추가되며 처리 + +## 고차 컴포넌트 + +> HOC : Higher-Order Component + +### 고차 컴포넌트의 개념 + +- 여러 컴포넌트에서 동일한 로직을 재사용하는 방법 중 하나 +- 애플리케이션 전체에서 컴포넌트 로직을 재사용할 수 있음 +- 다른 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환 +- **인자로 받은 컴포넌트에 추가 기능을 적용한 새로운 컴포넌트를 반환** + +#### 나의 생각 + +hook으로도 충분히 같은 효과를 낼 수 있을 것 같은데? + +### 고차 컴포넌트의 활용 + +> 다음과 같은 경우에 효과적일 수 있다. + +- 애플리케이션 전체에 걸쳐 여러 컴포넌트에 동일한 동작을 적용해야 할 때 +- 추가된 커스텀 로직 없이도 컴포넌트가 독립적으로 작동할 수 있을 때 + +#### 나의 생각 + +그러니까 이것도 hook도 다 할 수 있는 일 아니냐고요... +(알고보니 저자도 알고 있지만 hook 패턴이 나중에 등장해야 해서 그런 거였음) + +### 고차 컴포넌트의 장단점 + +#### 장점 + +- 재사용하고자 하는 로직을 한 곳에 모아 관리할 수 있음 +- 로직을 한 곳에 집중시킴으로써 코드를 DRY하게 유지 가능 +- 효과적으로 관심사 분리 가능 + +#### 단점 + +- 대상 컴포넌트에 전달하는 prop의 이름이 충돌을 일으킬 수 있음 + + +## 렌더링 Props 패턴 + +- JSX 요소를 반환하는 함수 값을 가지는 컴포넌트의 prop +- 컴포넌트 자체는 렌더링 prop 외에는 아무 것도 렌더링하지 않음 +- 자신의 렌더링 로직을 구현하는 대신, 렌더링 prop을 호출 + +```ts +const Title = props => props.render() + +... + <h1>Hello</h1>} /> +... +``` + +- 장점 : prop을 받는 컴포넌트를 재사용할 수 있음 + +### 상태 끌어올리기 + +```ts +const Input = (props) => { + const [value, setValue] = useState('') + + return ( + <> + <input + type="text" + value={value} + onChange={(e) => setValue(e.target.value)} + placeholder=... + /> + {props.render(value)} + </> + ) +} + +const App = () => { + return ( + <div className="App"> + <h1>Temperature Converter</h1> + <Input + render={(value) => ( + <> + <Kelvin value={value} /> + <Fahrenheit value={value} /> + </> + )} + /> + </div> + ) +} +``` + + +### 나의 생각 + +이게 children으로 넘기는 것보다 어떤 장점을 가지는지 잘 모르겠다. Input 내에 state를 두는 것도 그냥 별도로 컴포넌트를 구성하면 되는 거 아닌가? + + +### 렌더링 Props의 장단점 + +#### 장점 + +- 여러 컴포넌트 사이에서 로직과 데이터를 쉽게 공유할 수 있음 +- 컴포넌트의 재사용성을 높일 수 있음 + + +#### 단점 + +- 리액트 hooks는 렌더링 props 패턴으로 해결할 수 있는 문제 대부분을 이미 해결 + +#### 나의 생각 + +역시나 그렇군요. hook의 등장 이전에 존재하던 패턴들이라 hook을 알고 있는 지금의 저로서는 이해가 안 되는 거였습니다. + +## 리액트 hooks 패턴 + +> 리액트 16.8 버전에서 도입 + +- Class 컴포넌트를 사용하지 않고 상태와 라이프사이클 메서드를 활용 가능 +- 많은 전통적인 디자인 패턴을 대체 가능 + +### hook이 가능하게 한 것 + +- 함수형 컴포넌트에 상태 추가 +- componentDidMount, componentDidUpdate, componentWillUnmount 등의 클래스형 컴포넌트에서 사용하던 라이프사이클 메서드를 사용하지 않고도 컴포넌트 라이프사이클 관리 +- 여러 컴포넌트 간에 동일한 상태 관련 로직 재사용 + +### 클래스 컴포넌트와의 비교 + +```tsx +// 클래스형 컴포넌트 +class Input extends React.Component { + constructor(props) { + super(props) + this.state = { value: '' } + this.handleInput = this.handleInput.bind(this) + } + + handleInput(e) { + this.setState({ value: e.target.value }) + } + + render() { + return ( + <input type="text" value={this.state.value} onChange={this.handleInput} /> + ) + } +} + +// 함수형 컴포넌트 +const Input = () => { + const [value, setValue] = useState('') + + return ( + <input type="text" value={value} onChange={(e) => setValue(e.target.value)} /> + ) +} +``` + +위의 예시처럼 클래스형 컴포넌트에서보다 훨씬 간단하게 함수형 컴포넌트에서 상태를 관리 가능 + +### hook 관련 추가 정보 + +#### useState + +함수형 컴포넌트 내에서 상태를 업데이트하고 조작 + +#### useEffect + +- 함수형 컴포넌트의 주요 라이프사이클 이벤트 중간에 코드를 실행하는 데에 사용 +- **원래 함수형 컴포넌트의 내부에서는 값 변경, 구독, 타이머, 로깅 등 기타 부수 효과를 사용할 수 없다.**(의외 포인트) + - 이러한 작업이 허용되면 UI에 혼란스러운 버그와 오류를 초래할 수 있기 때문 +- 이 hook 하나로 클래스형 컴포넌트에서 사용하던 라이프사이클 메서드를 대체할 수 있음 + +#### useContext + +- 컨텍스트 API를 사용해 컴포넌트 트리 전체에 걸쳐 데이터를 쉽게 공유할 수 있음 + +#### useReducer + +- setState의 대안 +- 깊은 트리를 가진 복잡한 상태 로직에 유용 +- 변경 이후의 상태가 이전 상태에 따라 달라지는 경우에 특히 유용 +- 깊은 구조를 가진 컴포넌트의 업데이트 성능을 최적화 + +### hook의 장점 + +#### 복잡한 컴포넌트의 단순화 + +- 클래스는 관리가 어렵고, 핫 리로딩과 함께 사용하기 힘들며, 코드 경량화가 어려움 +- hook은 함수형 프로그래밍을 쉽게 구현할 수 있게 도와줌 + +#### UI에서 분리된 로직 공유 + +- hook 도입 이전에는 UI와 무관한 로직을 추출하고 공유할 방법이 없었음 +- **때문에 고차 컴포넌트 패턴이나 렌더링 Props와 같은 복잡한 방법을 동원해야 했음** +- hook의 등장 이후 상태 관련 로직을 단순한 자바스크립트 함수로 추출할 수 있게 해주며 문제를 해결 + +### hook의 단점 + +- hook 사용 규칙을 준수해야 함(Linter 플러그인 사용 시 규칙 위반 확인에 용이) +- 올바르게 사용하려면 상당한 연습이 필요 **(ex. useEffect)** +- 잘못된 사용에 주의해야 함 **(ex. useCallback, useMemo)** + +### 나의 생각 + +글의 작성 시기가 언제인지 모르겠으나 클래스형 컴포넌트와의 비교, 그리고 이전의 패턴들을 자꾸 언급하고 비교하는 것을 보며 hook의 등장이 오래 되지 않았을 때 작성된 글인가 하는 생각을 했어요. 압도적인 hook의 기능으로 이제는 패러다임이 확실히 넘어온 것 같아요. 클래스형의 아쉬운 부분을 역체감하는 기회가 되었어요. + +## 정적 가져오기 + +- 정적으로 가져오는 모든 모듈은 초기 번들에 추가 +- `import module from 'module'` 구문을 사용하여 모듈을 가져옴 + +```tsx +// 정적으로 가져옴 +import UserInfo from './components/UserInfo'; +import ChatList from './components/ChatList'; +import ChatInput from './components/ChatInput'; + +const App = () => { + return ( + <div className="App"> + <UserInfo /> + <ChatList /> + <ChatInput /> + </div> + ) +} +``` + +- 각 모듈은 자바스크립트 엔진이 해당 모듈을 import하는 코드에 도달하는 즉시 실행 +- 웹팩은 이 모듈들을 초기 번들에 포함 + +## 동적 가져오기 + +> "모든 모듈을 한 번에 가져올 필요는 없다!" + +- 사용자 상호작용에 따라서만 렌더링되거나, 페이지 하단에 위치하는 모듈은 나중에 가져와도 됨 +- 모듈을 동적으로 가져오면 초기 번들 크기를 줄일 수 있음 + +```tsx +// 동적으로 가져옴 +import React, { Suspense, lazy } from 'react'; + +const Send = lazy(() => import(/* webpackChunkName: "send-icon" */ './icons/Send')); +const Emoji = lazy(() => import(/* webpackChunkName: "emoji-icon" */ './icons/Emoji')); +const Picker = lazy(() => import(/* webpackChunkName: "emoji-picker" */ './components/EmojiPicker')); + +const ChatInput = () => { + const [pickerOpen, togglePicker] = useReducer(state => !state, false); + + return ( + <Suspense fallback={<p>Loading...</p>}> + <div className="chat-input-container"> + <input type="text" placeholder="Message..." /> + <Send /> + <Emoji onClick={togglePicker} /> + {pickerOpen && <Picker />} + </div> + </Suspense> + ) +} +``` + +- 위의 예시에서 사용자가 이모지를 클릭할 때 `EmojiPicker` 컴포넌트가 동적으로 로드됨 +- 이런 유형의 동적 가져오기를 **'상호작용 시 가져오기(import on interaction)'**라고 함 + + +### 화면에 보이는 순간 가져오기 + +> import on Visibility + +- 초기 페이지 로드 시에는 보이지 않아도 되는 컴포넌트들이 있음 +- 이들이 화면에 보일 때 동적으로 가져오는 것 +- `IntersectionObserver` API를 사용 + +## 코드 스플리팅 + +### 경로 기반 분할 + +- 특정 페이지나 경로에서만 필요한 리소스 + +```tsx +import React, { lazy } from 'react'; +import { render } from 'react-dom'; +import { Switch, Route, BrowserRouter as Router } from 'react-router-dom'; + +const Home = lazy(() => import(/* webpackChunkName: "home" */ './pages/Home')); +const About = lazy(() => import(/* webpackChunkName: "about" */ './pages/About')); +const Overview = lazy(() => import(/* webpackChunkName: "overview" */ './pages/Overview')); + +render( + <Router> + <Switch> + <Route path="/" exact component={Home} /> + <Route path="/about" component={About} /> + <Route path="/overview" component={Overview} /> + </Switch> + </Router>, + document.getElementById('root') +) + +module.hot.accept(); +``` + +- 현재 경로에 필요한 코드가 포함된 번들만 요청 + +### 번들 분할 + +- 최신 웹 애플리케이션 개발에서 웹팩 또는 롤업 등의 번들러는 소스 코드를 하나 이상의 번들 파일로 그룹화 +- **요청된 데이터의 로딩 및 실행 시간 최적화는 여전히 개발자의 몫** +- **메인 스레드를 차단하지 않도록 실행 시간을 최대한 단축해야 함** +- 초기 로딩 시 **현재 페이지에서 우선순위가 높지 않은 코드를 요청할 때**는 **초기 페이지 렌더링에 필요한 코드와 분리해서 로드**하는 것이 좋음 + +## PRPL 패턴 + +> Push, Render, Pre-cache, Lazy-load + +- 어려운 환경에서도 애플리케이션이 최대한 효율적으로 로드될 수 있도록 하기 +- Push: 중요한 리소스를 효율적으로 푸시하여 서버 왕복 횟수 및 로딩 시간 단축 +- Render: 초기 경로를 최대한 빠르게 렌더링 +- Pre-cache: 자주 방문하는 경로의 에셋을 백그라운드에서 미리 캐싱 +- Lazy-load: 자주 요청되지 않는 경로나 에셋은 지연 로딩 + +클라이언트와 서버 간의 왕복 횟수를 최소화하는 것이 중요 + +### HTTP/1.1 + +- keep-alive 헤더를 사용 : 연결을 유지하여 왕복 횟수 최소화 +- 요청과 응답에 줄바꿈 문자로 구분되는 일반 텍스트 프로토콜 사용 +- 클라이언트와 서버 간 TCP 연결 : 최대 6개 +- 동일한 TCP 연결을 통해 새로운 요청을 보내려면 이전 요청이 완료되어야 함 +- HOL(Head of Line) Blocking : 마지막 요청이 오래 걸리면 다른 요청을 전송할 수 없게 됨 + + +### HTTP/2 + +- 요청과 응답을 작은 프레임으로 분할 +- 양방향 스트림 사용 : 단일 TCP 연결을 통해 여러 개의 양방향 스트림을 만듦 +- 클라이언트-서버 간 여러 개의 요청 및 응답 프레임을 동시에 전달 가능 +- 이전에 보낸 요청이 완료되기 전에 동일한 TCP 연결을 통해 여러 요청을 보낼 수 있음(HOL Blocking 해결) +- 서버 푸시 : 자동으로 추가 리소스를 전송 가능 + +#### 리소스 푸시에 대하여 + +- 리소스 푸시는 추가 리소스를 받는 시간은 줄여줌 +- 하지만 서버 푸시는 HTTP 캐시를 인지하지 못함 +- 푸시된 리소스는 다음에 웹사이트 재방문 시에는 사용 불가, 다시 서버에 요청 + +#### PRPL의 해결 방법 + +- 초기 로드 이후에 **서비스 워커 사용** +- 해당 리소스를 캐시함으로써 클라이언트가 불필요한 요청을 하지 않도록 최적화 + +### preload + +- 브라우저가 어떤 리소스를 먼저 가져와야 하는지 알려주기 위해 **중요한 리소스에 preload 힌트를 추가** +- preload는 현재 경로에 중요한 리소스를 로드하는데 걸리는 시간을 최적화 +- 브라우저가 해당 리소스를 발견하는 것보다 더 빨리 가져오게 됨 +- 너무 과용하면 오히려 초기 로드 시간이 늘어날 수 있음 + - 브라우저 캐시는 제한적 + - 불필요한 리소스를 preload 처리하는 경우 불필요하게 대역폭을 많이 사용할 수도 + - 큰 번들을 캐싱하면 여러 번들이 동일한 리소스 대역폭을 공유해서 문제가 될 수 있음 + +### PRPL 패턴을 적용할 때에는 + +- 요청하는 번들이 해당 시점에 필요한 최소한의 리소스만 포함 +- 브라우저에서는 리소스를 캐싱할 수 있어야 함 +- 경우에 따라 번들을 전혀 사용하지 않는 것이 더 효율적일 수도 + +## 로딩 우선순위 + +- Preload(`<link rel="preload">`)는 브라우저의 최적화 기능 +- 브라우저가 늦게 요청할 수도 있는 중요한 리소스를 더 일직 요청할 수 있도록 함 + +### preload의 주의점 + +- 상호작용에 필요한 리소스를 먼저 로딩하다가 FCP, LCP에 필요한 리소스의 로딩이 지연되는 일은 피하기 +- **자바스크립트 자체의 로딩을 최적화하려면 `<body>` 태그보다는 `<head>` 태그 안에서 `<script defer>`를 사용하는 것이 초기 로딩에 더 효과적** + +### SPA의 Preload + +- Prefetching은 곧 요청될 가능성이 있는 리소스를 캐시하는 좋은 방법 +- 즉시 사용해야 하는 리소스의 경우에는 preload를 사용 + +```tsx +const EmojiPicker = import(/* webpackPreload: true */ './components/EmojiPicker');; + +<link rel="prefetch" href="emoji-picker.bundle.js" as="script"> +<link rel="prefetch" href="vendors~emoji-picker.bundle.js" as="script"> +``` + +- prefetch : 브라우저가 인터넷 연결 상태와 대역폭을 고려해 어떤 리소스를 미리 가져올지 결정 +- preload : 미리 로드된 리소스는 어떤 상황에서든 무조건 미리 로드 + +### Preload + async 기법 + +- **브라우저가 스크립트를 높은 우선순위로 다운로드하면서도, 스크립트를 기다리는 동안 파싱이 멈추지 않도록 하는 기법** +- preload는 다른 리소스의 다운로드를 지연시킬 수 있지만, 얻을 수 있는 이점을 위해 트레이드오프를 감수 + +```tsx +<link rel="preload" href="emoji-picker.bundle.js" as="script"> +<script src="emoji-picker.bundle.js" async></script> +``` + +### 크롬 95+ 버전에서의 Preload + +> preload에 대한 새로운 사용 권장사항 + +- HTTP 헤더에 preload를 넣으면 다른 모든 리소스보다 우선적으로 로드 +- 미리 로드되는 폰트는 `<head>` 태그 끝 부분이나 `<body>` 태그 시작 부분에 위치 +- 이미지 preload는 기본적으로 우선순위가 낮음 +- 비동기 스크립트 및 기타 낮은/최저 우선순위 태그와 관련하여 순서 지정 필요 + +## 리스트 가상화 + +> 대규모 데이터 리스트의 렌더링 성능을 향상시키는 기술 + +- 전체 목록을 모두 렌더링하는 대신 현재 화면에 보이는 행만 동적으로 렌더링 +- 사용자가 스크롤함에 따라 보이는 영역(윈도우)이 이동 + +### 작동 방식 + +- 사용자가 스크롤할 때마다 이전 항목을 윈도우에서 제거하고 새로운 항목으로 대체 + +### content-visibility + +- 최신 브라우저 중 일부는 CSS의 `content-visibility` 속성을 지원 +- 이 속성은 요소의 내용이 화면에 보이는 경우에만 렌더링하도록 지시 +- `content-visibility: auto` 속성을 사용하면 화면 밖 컨텐츠의 렌더링과 페인팅을 필요한 시점까지 지연 +- 큰 HTML 문서에서 렌더링 성능을 향상시키는 데 도움이 됨 + + +## 결론 + +### 양날의 검, preload + +- preload는 신중하게 사용해야 함 +- preload를 잘못 사용하면 FCP에 필수적인 리소스(ex. css, font)를 지연시켜 원하는 결과와 반대되는 결과를 초래 + +### 나의 생각 + +preload와 관련해서는 잘 알지 못했는데 배워가는 계기가 되었네요. +