diff --git a/chapter03/JungHun98.md.md b/chapter03/JungHun98.md.md new file mode 100644 index 0000000..8c44b59 --- /dev/null +++ b/chapter03/JungHun98.md.md @@ -0,0 +1,359 @@ +# 3장 리액트 훅 깊게 살펴보기 + +훅은 클래스 컴포넌트에서만 가능했던 state, ref 등 리액트의 핵심적인 기능을 함수에서도 가능하게 만들었고, 클래스 컴포넌트보다 간결하게 작성할 수 있어 많은 사랑을 받고 있다. 현재 대부분의 서비스 들이 훅으로 만들어 지고 있기 때문에 리액트로 웹 서비스를 만든느 개발자라면 훅이 어떻게 동작하는지 이해할 필요가 있다. + +[https://react.dev/reference/react/hooks](https://react.dev/reference/react/hooks) + +```jsx +type Hook = { + memoizedState: any, + baseState: any, + baseQueue: Update | null, + queue: any, + next: Hook | null, +}; + +// react hook의 구성 +``` + +훅은 컴포넌트가 리렌더링 될 떄마다 실행된다. + +## useState + +함수형 컴포넌트 내부에서 상태를 정의하고 관리할 수 있게 해주는 훅이다. + +함수형 컴포넌트는 매번 함수를 실행해 렌더링이 일어나고, 함수 내부의 값은 함수가 실행될 때마다 다시 초기화한다. 그렇다면 useState 훅의 결과값은 어떻게 함수가 실행돼도 그 값을 유지 할 수 있을까? + +리액트는 자바스크립트의 클로저를 활용해 함수형 컴포넌트에서 상태를 관리한다. + +useState의 기본적인 흐름은 다음과 같다. + +**마운트 단계** + +1. 전달받은 매개 변수로 state를 초기화한다. +2. 현재 상태를 업데이트하는 함수를 만든다. +3. [state, dispatch] 형태로 반환한다. + +**업데이트 단계** + +1. setState에 설정된 값으로 상태 업데이트 작업을 예약한다. +2. 리렌더링을 요청한다. + +**리렌더링 단계** + +1. 컴포넌트가 리렌더링 되면서 useState가 실행된다. +2. 예약된 상태 업데이트 작업을 실시한다. +3. 이때 이미 초기화한 값이 있으므로 새로운 값으로 상태를 업데이트한다. +4. 최종적으로 업데트한 상태 값과 dispatch 함수를 다시 반환한다. + +### 게으른 초기화 + +일반적으로 useState에서 기본 값을 선언하기 위해 원시값을 넣지만 특정한 값을 넘기는 함수를 인수로 넣어 초기화 할 수 있다. 이를 게으른 초기화라고 부른다. + +공식문서에 따르면 게으른 초기화는 useState의 초기값이 복잡하거나 무거운 연산을 포함하고 있을 때 사용하라고 나와있다. 이유는 state가 만들어지고 나서 리렌더링 될 때 이 함수는 무시되기 때문이다. + +useState는 컴포넌트 리렌더링 시 같이 호출된다. 만약 useState 인수에 무거운 연산을 넣어두었다면 렌더링마다 이 연산이 실행된다는 것이다. 초깃값이 필요 없는 리렌더링 상황에도 연산작업을 실시하기 때문에 낭비가 발생할 수 있다. + +localStorage, sessionStorage에 대한 접근, map, filter, find 같은 배열에 대한 접근이나 무거운 연산을 통해 값을 얻어야 하는 경우 사용을 권장하고 있다. + +## useEffect + +useEffect를 설명하는 말은 많지만 정확하지는 않다. useEffect는 애플리케이션 내 컴포넌트의 여러 값들을 활용해 동기적으로 부수 효과를 만드는 메커니즘이다. 중요한 것은 부수효과가 일어나는 시점이 아니라 어떤 상태값과 함께 실행되는지 아는게 중요하다. + +```jsx +function Component() { + // ... + useEffect(() => { + //... do something + }, [props, state]); +//... +} +``` + +자주보는 useEffect의 형태이며, 의존성 배열이 변경될 때마다 useEffect의 첫 번째 인수인 콜백을 실행한다는 것은 익히 아는 사실이다. 그렇다면 useEffect는 어떻게 의존성 배열이 변경된 것을 알고 콜백함수를 실행할까? + +결국 useEffect역시 컴포넌트가 리렌더링 될 때마다 호출된다. 특수한 기능을 통해 값의 변화를 관찰하는 것이 아니고 렌더링할 때마다 의존성에 있는 값을 비교하면서 이전과 다른 게 하나라도 있으면 부수효과를 실행하는 평범한 함수다. + +### 클린업 함수의 목적 + +useEffect에서는 함수를 반환할 수 있다. 이는 정확히 무엇이고 어떤 일을 할까? + +1. 컴포넌트가 페이지에 추가될 때 *(마운트)* 마다 콜백 함수 실행 +2. 의존성이 변경된 컴포넌트를 다시 렌더링할 때마다: + - 먼저 이전 props와 state로 클린업 힘수 실행 + - 그런 다음 새 props와 state로 콜백 함수 실행 + - useEffect ⇒ clean up ⇒ useEffect callback +3. 컴포넌트가 페이지에서 제거되면 *(마운트 해제)* 마지막으로 한 번 클린업 함수 실행 + +**클린업 함수는 비록 새로운 값을 기반으로 렌더링 뒤에 실행되지만 변경된 값을 읽는 것이 아니라 함수가 정의됐을 당시에 선언됐던 이전 값을 보고 실행된다.** + +이처럼 클린업 함수는 생명주기 메서드의 언마운트 개념과는 조금 차이가 있다. 클린업 함수는 언마운트라기보다는 함수형 컴포넌트가 리렌더링됐을 때 의존성 변화가 있었을 당시 이전의 값을 기준으로 실행되는 이전 상태를 청소해 주는 개념으로 보는 것이 옳다. + +### 의존성 배열 + +의존성 배열은 아예 아무런 값도 넘기지 않거나 빈 배열, 혹은 사용자가 원하는 값을 넣어줄 수 있다. 빈 배열을 넣는다면 리액트가 해당 useEffect는 비교할 의존성이 없다고 판단해 최초 렌더링 직후에 실행된 다음 부터는 더 이상 실행되지 않는다. 아무런 값도 넘기지 않는다면 렌더링 할 때마다 실행이 필요하다고 판단해 렌더링이 발생할 때마다 실행된다. + +아무런 값도 넘기지 않고 매 렌더링마다 실행되게 만들고 싶다면 꼭 useEffect안에 넣어야 할 이유가 있을까? + +```jsx +function Compoment() { + console.log('render'): +} + +function Compoment() { + useEffect(() => { + console.log('render'); + }); +} +``` + +이 두 코드는 차이점이 있다. + +useEffect는 **컴포넌트 렌더링의 부수효과,** 즉 컴포넌트의 렌더링이 완료된 이후에 실행된다. 반면 직접 실행은 컴포넌트가 렌더링되는 도중에 실행된다. 이 작업은 함수형 컴포넌트의 반환을 지연시키는 행위다. 무거운 작업일 경우 렌더링을 방해하므로 성능에 악영향을 미칠 수 있다. **** + +### 동작 순서 + +1. 이전 useEffect 정보가 있는지 확인한다. +2. 이전 값이 있다면 의존성을 얕은 비교로 비교한다. +3. 이전 값이 없다면 최초 실행이므로 변경이 일어난 것으로 간주해 실행을 유도한다.(의존 값을 전달하지 않았을 경우도 여기에 포함) +4. 변경이 일어났다면 첫 번째 인수인 콜백 함수를 실행한다. + 1. 클린업 함수가 있다면 먼저 실행 + +### useEffect를 사용할 때 주의할 점 + +useEffec를 잘못 사용하면 예기치 못한 버그가 발생할 수 있으며, 심한 경우 무한루프에 빠지기도 한다. 사용할 때 주의해야 할 점은 무엇인지 알아보자. + +- eslint-disable-line react-hooks/exhaustive-deps 주석은 최대한 자제하라 + - 이 주석은 useEffect 인수 내부에서 사용하는 값 중 의존성 배열에 포함돼 있지 않은 값이 있을 때 보내는 경고 + - 이런 상황이 필요한 경우가 있겠지만 버그 발생 가능성 증가 + - 의존성을 넘기지 않은 채 콜백 함수 내부에서 특정 값을 사용한다는 것은 부수 효과가 실제로 관찰해서 실행돼야 하는 값과 별개로 작동하는 것을 의미 + - 의존성 배열을 빈 배열로 넣어 마운트 단계에만 실행하는 useEffect는 쉽게 볼 수 있다. + - 정말로 필요한 경우에는 다시한 번 확인해보자. 이 부수효과가 실행되어야하는 장소가 잘못 됐을 수도 있다. +- useEffect 첫 번째 인수에 함수명을 부여하라 + - useEffect가 간단하면 문제 없겠지만 useEffect의 코드가 복잡할 수록 무슨 일을 하는지 파악하기 어렵다. +- 거대한 useEffect를 만들지 마라 + - useEffect는 의존성 배열을 바탕으로 렌더링 시 부수 효과를 실행한다. 부수 효과의 크기가 커질 수록 애플리케이션 성능에 악영향을 미칠 수 있다. 일단 자바스크립트 위에서 동작하는 것이기 때문이다. + - 부수효과가 크다면 적은 의존성 배열을 사용하는 여러 개의 useEffect로 분리하자. +- 불필요한 외부 함수를 만들지 마라 + - useEffect 내에서 사용할 부수 효과라면 내부에서 만들어 사용하는 편이 훨씬 도움된다. + +⭐ useEffect의 콜백 인수로 비동기 함수를 바로 넣을 수는 없을까? + +결론부터 말하자면 넣을 수 없다. useEffect에 비동기 함수를 넣을 경우 부수 효과 실행과정에서 경쟁 상태가 발생할 수 있다. (Race condition) + +물론 내부에서 비동기 함수를 실행 시킬 수는 있다. 다만 비동기 함수가 내부에 존재하게 되면 useEffect 내부에서 비동기 함수가 생성되고 실행되는 것을 반복하므로 클린업 함수에서 이전 비동기 함수에 대한 처리를 추가하는 것이 좋다. fetch의 경우 이전 요청을 취소하는 작업을 넣을 수 있다. + +즉, 비동기 부수 효과는 state의 경쟁 상태를 야기할 수 있고 clean up 함수의 실행 순서도 보장할 수 없기 때문에 인수로 받지 않는다. + +### useMemo + +useMemo는 비용이 큰 연산에 대한 결과를 저장(메모이제이션)해두고 저장된 값을 반환하는 훅이다. + +```jsx +useMemo(() => { // ... return ...}, [ //... ]) +``` + +첫 번째 인수로는 어떠한 값을 반환하는 생성 함수 + +- 캐시하려는 값을 계산하는 함수 +- 이 함수는 순수함수여야 하며, 인자를 받지 않고 반드시 어떤 타입이든 값을 반환해야 한다. + +두 번쨰 인수로는 해당 함수가 의존하는 값의 배열을 전달한다. + +- 코드 내에서 참조되는 모든 반응형 값들의 목록 +- props, state 및 컴포넌트 본문 내에서 직접 선언된 모든 변수와 함수가 포함된다. + +초기 렌더링에서 `useMemo` 는 인자 없이 콜백함수를 호출한 결과를 반환한다. + +useMemo는 렌더링 발생시 의존성 배열 값이 변경되지 않았다면 함수를 재실행 하지 않고 이전에 저장한 결과 값을 반환한다. 변경 되었을 때만 다시 실행한 뒤 결과 값을 저장한다. + +하지만 이 ‘비용이 많이 드는 연산’에 대한 기준은 무엇일까? + +연산 비용의 확신을 얻고 싶다면 console.log를 추가해 코드에 소요된 시간을 측정할 수 있다. + +```jsx +console.time('filter array'); +const visibleTodos = filterTodos(todos, tab); +console.timeEnd('filter array'); +``` + +이 시간이 1ms이상으로 합산된다면 연산을 메모해 두는 것이 좋다. 이후 시험삼아 `useMemo` 로 감싸보고 해당 상호작용해 대해 시간이 감소했는지 확인 할 수 있다. + +### useCallback + +useMemo가 값을 기억했다면 useCallback은 인수로 넘겨받은 콜백 자체를 기억한다. 새로 함수를 만들지 않고 다시 재사용한다는 의미다. + +만약 컴포넌트 내부에 함수를 선언했다면 렌더링 될 때마다 함수가 다시 생성되고 함수 객체의 레퍼런스가 달라지기 때문에 리액트는 서로 다른 함수라고 받아들인다. useCallback을 사용하면 함수의 재할당을 방지할 수 있다. + +useCallback은 useMemo로 구현되어 있다. 즉 useMemo와 useCallback의 차이는 캐싱의 대상이 값이냐 함수냐이다. useMemo로 함수를 캐싱하려면 코드가 길어지고 혼동될 가능성이 있으니 제공하는 것으로 추측할 수 있다. + +```jsx +const handleClick1 useCallback(()=> { + setCounter((prev) => prev + 1); +}, []); + +const handleClick2 useMemo(()=> { + return () => setCounter((prev) => prev + 1); +}, []); +``` + +위 두 코드는 모두 동일한 기능을 한다. 다만 useMemo는 값을 캐싱하는 훅이기 때문에 함수를 반환해야 한다. + +### useRef + +useRef는 useState와 동일하게 컴포넌트 내부에서 렌더링이 일어나도 변경 가능한 상태값을 저장한다는 공통점이 있다. 그러나 큰 차이점이 있는데, + +- useRef는 반환값인 객체 내부에 있는 current로 값에 접근 또는 변경할 수 있다. +- useRef는 값이 변하더라도 렌더링을 발생시키지 않는다. + +useRef의 가장 일반적인 사용 예는 DOM에 접근하고 싶을 때일 것이다. 한 가지 명심해야 할 것은 useRef의 최초 기본 값은 컴포넌트에 정의된 DOM이 아니고 useRef로 넘겨 받은 반환값이다. + +또한 useRef는 useState로 반환된 이전 상태 값을 기억하는 용도로 유용하게 사용할 수 있다. 렌더링에 영향을 미치지 않고 값을 보관할 수 있다. + +정보가 공유되는 외부 변수와 달리 컴포넌트 로컬에 값을 저장하면서 순수성을 지킬 수 있다. 또한 리렌더링을 촉발하지 않으므로 state를 저장하는 것으로는 적합하지 않다. + +### useContext + +**context란?** + +리액트 애플리케이션은 기본적으로 부모 컴포넌트와 자식 컴포넌트로 이뤄진 트리 구조이기 때문에 부모의 데이터를 자식으로 넘겨줄 때, props로 데이터를 넘겨주는 것이 일반적이다. 그러나 전달해야 하는 데이터가 있는 컴포넌트와 전달 받아야 하는 컴포넌트의 거리가 멀어질수록 코드는 복잡해진다. (props drilling) + +이러한 props drilling을 극복하기 위해 등장한 개념이 Context다. + +`useContext` 는 상위 컴포넌트에서 만들어진 Context를 함수형 컴포넌트에서 사용할 수 있도록 만들어진 훅이다. `useContext` 를 사용하면 상위 컴포넌트 어딘가에 선언된 `` 에서 제공하는 값을 사용할 수 있게 된다. 만약 여러 개의 Provider가 있다면 가장 가까운 Provider의 값을 가져온다. + +**useContext 주의점** + +useContext를 함수형 컴포넌트 내부에서 사용할 때는 컴포넌트가 재사용이 어려워 진다는 점을 염두해 둬야한다. 결국 Provider에 의존성을 가지고 있는 셈이므로 아무데서나 재활용하기 어려운 컴포넌트가 된다. + +Context가 미치는 영향의 범위를 최대한 좁게 해야한다. + +Context와 useContext는 상태 관리를 위한 리액트의 API가 아니라 상태를 주입해 주는 API다. 상태관리 라이브러리가 되기 위해저는 다음 두 가지 조건을 만족해야한다. + +1. 어떠한 상태를 기반으로 다른 상태를 만들어 낼 수 있어야 한다. +2. 필요에 따라 이러한 상태 변화를 최적화 할 수 있어야 한다. + +Context는 둘 중 어느하나도 가능하지 않고 그저 props를 하위로 drilling을 피해 전달해주는 역할만을 한다. 렌더링 최적화를 따로 해주는 기능은 없다. + +React는 context가 변경되면 context를 읽는 컴포넌트를 자동으로 리렌더링된다. + +**Context 리렌더링 최적화** + +[https://react.dev/reference/react/useContext#optimizing-re-renders-when-passing-objects-and-functions](https://react.dev/reference/react/useContext#optimizing-re-renders-when-passing-objects-and-functions) + +### useReducer + +useReducer는 useState와 비슷한 형태를 띠지만 좀 더 복잡한 상태값을 미리 정의해 놓은 시나리오에 따라 관리할 수 있다. + +```jsx +const [state, dispatcher] = useReducer(reducer, initialState); +``` + +- 반환 값은 useState와 동일하게 길이가 2인 배열이다. + - state: 현재 useReducer가 가지고 있는 값을 의미한다. + - dispatcher: state를 업데이트하는 함수. useState의 setState함수는 값만 넘겨줘도 되지만 여기서는 `action` 을 넘겨준다는 점이 다르다. +- useState와 달리 인수를 하나 더 넘겨줘야 한다. + - reducer: useReducer의 기본 action을 정의하는 함수다. action에 따른 상태를 반환하는 순수함수. + - initialState: 초기 상태 값을 의미한다. + +간단한 상태값을 관리하는데 useState로 충분하지만 state하나가 가져야 할 값이 복잡하고 이를 수정하는 경우의 수가 많아진다면 state를 useState만으로 관리하기 어려워진다. + +useReducer의 목적은 생각보다 간단하다. state를 dispatcher함수로만 수정할 수 있게 만들어 줌으로써 제한한다. 이렇게 useReducer를 사용해 state를 관리하면 state를 사용하는 로직과 이를 관리하는 비즈니스 로직을 분리할 수 있어 state를 관리하기 한결 쉬워진다. + +useReducer, useState는 모두 세부 작동과 쓰임에만 차이가 있을 뿐, 클로저를 활용해 값을 가둬 state를 관리한다는 사실에는 변함이 없다. + +### useImperativeHandler + +**forwardRef** + +ref는 useRef에서 반환한 객체로, 리액트 컴포넌트의 props인 ref에 넣어 HTMLElement에 접근하는 용도로 흔히 사용된다. 하지만 이 ref를 하위 컴포넌트로 전달하려고 할 때 props로 전달할 수 없다.(예약어기 때문에) 그렇다고 다른 이름의 props로 전달하려고 해도 동시성의 문제 때문에 두 번의 렌더링을 해야 정상적으로 접근이 가능하다. (ref의 DOM참조는 렌더링 이후에 일어나므로) + +> React가 DOM 노드를 생성하고 화면에 그린 후, React는 ref 객체의 `current`프로퍼티를 DOM 노드로 설정합니다. +[https://react.dev/reference/react/useRef#manipulating-the-dom-with-a-ref](https://react.dev/reference/react/useRef#manipulating-the-dom-with-a-ref) +> + +props로 ref를 하위 컴포넌트로 전달할 수는 있다. 하지만 ref를 어떤 이름으로 전달할지 알 수 없기 때문에 네이밍에 자유도를 주는 것보다는 forwardRef를 사용하면 더 확실하게 ref를 전달할 것임을 예측 할 수 있다. + +먼저 ref를 전달받고자 하는 컴포넌트를 forwardRef로 감싸고, 두 번째 인수로 ref를 전달받는다. + +```jsx +const ChildComponent = forwardRef((props, ref) => { + // ... + console.log(ref); + // 부모 컴포넌트의 ref를 ref라는 이름으로 접근 할 수 있다. +}); +``` + +useImperativeHandler는 부모에게 전달받은 ref를 원하는 대로 수정할 수 있는 훅이다. 이를 통해 부모컴포넌트는 단순히 HTMLElment 뿐만 아니라 자식 컴포넌트에서 새롭게 설정한 객체의 키와 값에 대해서도 접근할 수 있게 됐다. + +### useLayoutEffect + +공식문서에 따르면 다음과 같은 정의를 내리고 있다. + +> 이 함수의 시그니처는 useEffect와 동일하나, 모든 DOM 변경 후에 동기적으로 발생한다. +> + +여기서 말하는 DOM 변경은 렌더링이지 브라우저에 실제로 해당 변경 사항이 반영되는 시점을 의미하는 것은 아니다. + +1. 리액트가 DOM을 업데이트 (렌더) +2. useLayoutEffect 실행 +3. 브라우저에 변경 사항을 반영 (커밋) +4. useEffect 실행 + +실행 순서를 바탕으로 useLayoutEffect의 실행이 완료되기 전까지 컴포넌가 잠시 일시 중지되는 것과 같은 일이 발생한다. useLayoutEffect는 DOM은 계산됐지만 이것이 화면에 반영되기 전에 하고 싶은 작업이 있을 때와 같이 반드시 필요할 때만 사용하는 것이 좋다. + +특정 요소에 따라 DOM 요소를 기반으로 한 애니메이션, 스크롤 위치를 제어하는 등 화면에 반영되기 전에 하고싶은 작업에 사용한다면 useEffect보다 훨씬 더 자연스러운 사용자 경험을 제공할 수 있다. + +### useDebugValue + +디버깅하고 싶은 정보를 이 훅에 사용하면 리액트 개발자 도구에서 볼 수 있다. useDebugValue는 사용자 정의 훅 내부의 내용에서만 사용할 수 있다. 사용자 정의 훅 내부의 내용에 대한 정보를 남길 수 있다. 컴포넌트 레벨에서 사용한다면 작동하지 않을 것이다. + +### 훅의 규칙 + +리액트에서 제공하는 훅은 사용하는 데 몇 가지 규칙이 존재한다. ESLint 규칙인 react-hooks/rulse-of-hooks에도 존재한다. + +1. 최상위에서만 훅을 호출해야 한다. 반복문이나 조건문, 중첩된 함수 내에서 훅을 실행할 수 없다. 이 규칙을 따라야만 컴포넌트가 렌더링될 때마다 항상 동일한 순서로 훅이 호출되는 것을 보장할 수 있다. +2. 훅을 호출할 수 있는 것은 리액트 함수형 컴포넌트, 사용자 정의 훅 두 가지 경우 뿐이다. + +리액트 훅은 파이버 객체의 링크드 리스트의 호출 순서에 따라 결정된다. 각 훅이 파이버 객체 내에서 순서에 의존해 state나 effect의 결과에 대한 값을 저장하고 있기 때문이다. 리렌더링 시 훅의 호출 순서가 바뀌게 되면 리액트는 에러를 발생시킨다. + +그러므로 훅은 리액트에서 예측 불가능한 순서로 실행되게 해서는 안된다. + +## 사용자 정의 훅과 고차 컴포넌트 + +일반적으로 자바스크립트에서 재사용 로직을 작성하는 방식 외에도 리액트에서는 재사용할 수 있는 로직을 관리할 수 있는 두 가지 방법이 있다. 커스텀 훅, 고차 컴포넌트이다. 공통된 코드를 하나로 만들고자 할 때 어떤 것을 선택해야 할까? + +### 사용자 정의 훅 + +서로 다른 컴포넌트 내부에서 같은 로직을 공유하고자 할 때 주로 사용되는 것이 커스텀 훅이다. 만드시 `use`로 시작해야 한다. + +- useFetch + +### 고차 컴포넌트 + +고차 컴포넌트(HOC)는 고차 함수의 일종으로 자바스크립트의 일급 객체, 함수의 특징을 이용한 것이다. 가장 유명한 고차 컴포넌트는 리액트에서 제공하는 `React.memo`다 . + +props의 변화가 없음에도 컴포넌트의 렌더링을 방지하기 위해 만들어진 고차 컴포넌트다. React.memo는 렌더링하기에 앞서 props를 비교해 이전과 같다면 렌더링 자체(호출)을 생략하고 이전에 기억해 둔 컴포넌트를 반환한다. + +**고차 함수를 활용한 리액트 고차 컴포넌트** + +고차 컴포넌트는 함수(함수형 컴포넌트)를 인수로 받으며 또 다른 함수형 컴포넌트를 반환한다. 고차 컴포넌트는 컴포넌트 전체를 감쌀 수 있다는 점에서 사용자 정의 훅보다 더욱 큰 영향력을 컴포넌트에 미칠 수 있다. 단순히 값을 반한하거나 부수 효과를 실행하는 커스텀 훅과 다르게, 고차 컴포넌트는 컴포넌트의 결과물에 영향을 미칠 수 있는 다른 공통된 작업을 처리할 수 있다. + +고차 컴포넌트의 이름은 `with`로 시작해야 한다. 강제는 아니지만 관습이라고 볼 수 있다. + +고차 컴포넌트는 또 다른 컴포넌트를 인수로 받기 때문에 콜백 지옥을 피해야 한다. 또한 컴포넌트의 props를 수정하거나 삭제하면 안된다. + +### 그래서 뭘 써야 해? + +커스텀 훅과 고차 컴포넌트는 리액트 코드에서 어떠한 로직을 공통화해 별도로 관리할 수 있다는 특징이 있다. + +**커스텀 훅이 필요한 경우** + +- 단순히 리액트에서 제공하는 훅으로 공통 로직을 분리할 수 있는 경우 + - 커스텀 훅 그 자체로는 렌더링에 영향을 미치지 못하기 때문에 반환하는 값으로 무엇을 할지는 개발자의 몫이다. + +단순히 컴포넌트 전반에 걸쳐 동일한 로직으로 값을 제공하거나 특정한 훅의 작동을 취하게 하고 싶다면 커스텀 훅을 사용하는 것이 좋다. + +**고차 컴포넌트를 사용해야 하는 경우** + +함수형 컴포넌트의 반환값, 즉 렌더링의 결과물에도 영향을 미치는 공통 로직이라면 고차 컴포넌트를 사용하는 것이 좋다. 고차 컴포넌트는 공통화된 렌더링 로직을 처리하기에 매우 훌륭한 방법이다. \ No newline at end of file