3. 리액트 훅 깊게 살펴보기
함수 컴포넌트가 상태 사용 및 생명주기 메서드 대체를 위해 사용하는 것이 바로 훅(hook) 이다.
3.1 리액트의 모든 훅 파헤치기
✨클래스 컴포넌트만 영유하던 리액트의 핵심적인 기능들을 함수 컴포넌트가 사용할 수 있게 하여 함수의 시대를 열어준 귀중한 친구.
3.1.1 useState
🔖 함수 컴포넌트 내부에서 상태를 정의 및 관리할 수 있게 해주는 훅
// 기본적인 형태
const [num, setNum] = useState(0);
setNum(1);
- 구조분해할당을 통해 가져오며, 첫번째 원소는
state
값 자체, 두번째 요소는state
를 변경하는데 사용하는setter
함수를 가진다.
❗만약 useState
의 형태를 사용하지 않는다면?
기본적으로 리액트는 "상태 변화"를 감지하고, 이에 따라 리렌더링을 발생시키며, "클로저"를 통해
state
값을 계속 기억하고 리렌더링 시 이를 사용한다.따라서 우리가 사용하는
useState
의 올바른 효과를 기대하기 위해서 알맞게 이를 사용해야 한다.
☝️state는 상수가 아닌 함수다.
// useState 훅 구현 예시
const react = () => {
let state = [];
let setters = [];
let cursor = 0;
const createSetter = (cursor) => {
return (newValue) => {
state[cursor] = newValue;
};
};
const useState = (initialValue) => {
if (state[cursor] === undefined) {
state.push(initialValue);
setters.push(createSetter(cursor));
}
const resState = state[cursor];
const resSetter = setters[cursor];
cursor++;
return [resState, resSetter];
};
return useState;
};
// 새로운 useState 훅과 렌더 함수 생성
import { useState } from "react";
// 예제 컴포넌트
const MyComponent = () => {
const [count, setCount] = useState(0);
// 상태 업데이트를 예제에서 직접 호출
const handleClick = () => {
setCount((prevCount) => prevCount + 1);
};
return (
<>
<div>{count}</div>
<button onClick={handleClick}>클릭!</button>
</>
);
};
state
는 값이므로 변수로 선언state
가 함수 안에 선언될 경우 변수기 때문에 호출 시마다 초기화 되므로, 클로저를 이용해 기억state
를 여러 컴포넌트에서 사용해야하기 때문에, 배열 형태로 만들어 사용첫 사용 시
state
를 설정하고setter
함수 작성 (createSetter
)다음 사용부터
setter
함수는 기존에 작성된 함수로 계속 사용(비용 감소)
🏷️ 게으른 초기화
useState
에 변수 대신 함수를 넘기는 것const [count, setCount] = useState(Number.parseInt(window.localStorage.getItem(cachekey)));
게으른 초기화를 사용할 경우, 함수는
state
가 처음 만들어질 때만 실행되므로, 무거운 연산이나 복잡한 함수의 재실행을 방지하여 성능을 향상시킬 수 있다.Storage
에 대한 접근 이나 고차 함수의 배열에 접근하는 경우 등, 비용이 큰 경우 한번씩 사용해주자링크 : useState의 동작원리
3.1.2 useEffect
🔖 앱 내 컴포넌트의 여러 값들을 활용해 동기적으로 부수 효과를 만들기 위한 훅
☝️ 부수 효과가 '언제' 보다 '어떤' 상태값과 함께 실행되는지가 중요
// 기본적인 형태
useEffect(() => {
// 실행할 효과
}, []);
첫번째로 실행할 부수효과를 포함한 함수를, 두번째로 의존성 배열을 전달
❗의존성 배열은 값이 없을 수도, 있을 수도 있다.
useEffect
는 렌더링 시마다 함수를 실행하는 리액트의 특성을 활용하여, 의존성에 있는 값의 변화를 통해 부수 효과를 가진 함수를 실행한다.
🏷️ 클린업 함수의 목적
기본적으로
useEffect
내에서 이벤트를 등록하고 지울 때 사용const ExampleComponent = () => { const [count, setCount] = useState(0); const handleClick = () => { setCounter((prev) => prev + 1); }; useEffect(() => { // 사이드 이펙트: 이벤트 리스너 추가 const call = () => { console.log("Window resized"); }; window.addEventListener("click", call); // 클린업 함수: 이벤트 리스너 제거 return () => { window.removeEventListener("click", call); }; }, []); // 빈 배열은 이 이펙트가 처음 렌더링될 때 한 번만 실행됨 return ( <div> <p>Count: {count}</p> <button onClick={handleClick}>클릭</button> </div> ); };
useEffect
는 컴포넌트가 DOM에서 사라짐을 의미하는 언마운트와는 개념이 조금 다르다.콜백이 실행 시, 이전의 클린업 함수가 존재한다면 클린업을 실행한 후 콜백을 실행한다.
이를 활용해 무한히 DOM 이벤트 핸들러가 추가되는 것을 방지한다.
🏷️ 의존성 배열
빈 배열 혹은 사용자가 원하는 값을 넣은 배열을 사용
빈 배열일 시 최초 렌더링 후 실행 ❌
의존성 배열 자체가 없다면 렌더링 시마다 실행이 필요하다고 판단, 렌더링 발생 시마다 실행
function Component() { // 1 console.log("실행"); // 2 useEffect(() => { console.log("실행"); }); }
둘은 차이가 없다고 생각할 수 있으나,
useEffect
는 컴포넌트가 렌더링 완료된 후 실행되므로, 1번같은 경우 컴포넌트 렌더링에 악영향을 끼침 (의미없는 코드)
let currentEffectIndex = 0;
const effects = [];
const runEffects = () => {
effects.forEach((effect, index) => {
if (effect.cleanup) {
effect.cleanup(); // 이전 클린업 함수 실행
}
const cleanup = effect.effect(); // 새로운 이펙트 실행
effects[index].cleanup = cleanup; // 새로운 클린업 함수 저장
});
currentEffectIndex = 0;
};
const useEffect = (effect, dependencies) => {
const hasNoDeps = !dependencies;
const deps = effects[currentEffectIndex]?.dependencies;
const hasChangedDeps = deps ? !dependencies.every((dep, i) => dep === deps[i]) : true;
if (hasNoDeps || hasChangedDeps) {
effects[currentEffectIndex] = { effect, dependencies, cleanup: null };
}
currentEffectIndex++;
};
- 의존성 배열의 현재와 이전 값을 '얕은 비교'하여
callback
으로 선언한 부수 효과 실행
🏷️ 주의할 점
eslint-disable-line react-hooks/exhaustive-deps
주석은 놉!useEffect
인수 내부에 의존성 배열에 포함되지 않은 값을 사용하는 경우 발생빈 배열이든 아니든,
useEffect
를 활용한 부수 효과를 정말로 사용하는 게 맞는지 고민하고 사용해야 한다.
첫번째 인수인 함수에 이름 지어주기~
useEffect
의 목적을 분명히 하고 책임을 최소한으로 좁히기 위해 함수명을 부여하는 것이 좋다.
돼지같은
useEffect
만들지 않기!!- 부수 효과가 커질 수록, 의존성 배열의 요소가 많을 수록
useEffect
에 의해 실행되는 부수 효과의 관리가 힘들어지고 JS 실행 성능에 악영향을 미친다.
- 부수 효과가 커질 수록, 의존성 배열의 요소가 많을 수록
불필요한 외부 함수는 안돼요~
useEffect
내에서 사용하는 부수 효과라면 그냥 안에서 직접 작성하고 사용하는 것이 바람직하다.
링크 : useEffect의 경쟁상태
3.1.3 useMemo
🔖 비용이 큰 연산 결과를 메모이제이션 하고 , 저장된 값을 가져와 반환하는 훅
import React, { useState, useMemo } from "react";
const ExampleComponent = () => {
const [count, setCount] = useState(0);
const [numbers, setNumbers] = useState([10, 20, 30, 40, 50]);
// useMemo를 사용하여 계산 비용이 큰 연산을 최적화
const sortedNumbers = useMemo(() => {
console.log("Sorting numbers...");
return numbers.sort((a, b) => a - b);
}, [numbers]); // numbers가 변경되지 않았다면 저장된 값 사용
const incrementCount = () => {
setCount(count + 1);
};
const addNumber = () => {
setNumbers([...numbers, Math.floor(Math.random() * 100)]);
};
return (
<div>
<h1>useMemo Example</h1>
<p>Count: {count}</p>
<button onClick={incrementCount}>Increment Count</button>
<button onClick={addNumber}>Add Number</button>
<ul>
{sortedNumbers.map((number, index) => (
<li key={index}>{number}</li>
))}
</ul>
</div>
);
};
export default ExampleComponent;
- 컴포넌트도 감쌀 수 있지만, 그냥
React.memo
를 사용하자
3.1.4 useCallback
🔖 특정 함수를 다시 만들지 않고 재사용하는 훅
import React, { useState, useCallback } from "react";
const ChildComponent = React.memo(({ onClick }) => {
console.log("ChildComponent render");
return <button onClick={onClick}>Increment Parent Count</button>;
});
const ParentComponent = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
// useCallback을 사용하여 콜백 함수 메모이제이션
const incrementCount = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []); // 의존성 배열이 비었으므로 첫 렌더링 시 함수 참조 후 리렌더링 시에도 동일 함수 참조
const handleTextChange = (e) => {
setText(e.target.value);
};
return (
<div>
<h1>useCallback Example</h1>
<p>Count: {count}</p>
<input type="text" value={text} onChange={handleTextChange} />
<ChildComponent onClick={incrementCount} />
</div>
);
};
export default ParentComponent;
React.memo
를 사용하여 컴포넌트를 메모이제이션 했지만,ChildComponent
에 내려준 onClick이 실행될때마다increment
함수가 재생성되어 의미가 없다.useCallback
을 사용해 함수를 메모이제이션하면, 함수가 재생성되지 않아 리렌더링을 방지한다.기본적으로
useCallback
은useMemo
를 통해서도 구현 가능하다.
❗useMemo
와 useCallback
의 차이는 메모이제이션의 목표가 '변수'냐, '함수'냐의 차이일 뿐이다.
3.1.5 useRef
🔖useState
와 동일하나 변경되도 리렌더링되지 않으며, 객체의 current
값으로 내부에 접근 가능한 훅
고정된 값의 컨트롤은 함수 외부에 선언 및 관리도 동일하나, 불필요한 메모리 소모와 컴포넌트가 여러개일 시, 동일한 값을 가리키게 되어버리는 단점이 있다.
useRef
는 이러한 단점을 '리액트적 관점'에서 완벽하게 커버한다.
🏷️useRef
의 일반적인 사용 예시
DOM에 접근하는 경우
function Component() { const inputRef = useRef(null); useEffect(() => { console.log(inputRef.current); }, []); return <input ref={inputRef} type="text'/> }
일반적으로
return
문을 통해 실행된 DOM 트리에 접근하므로, 렌더링된 후에 적용된다.값을 변경해도 렌더링 시키지 않기 위해, 오히려 객체의 값 자체를 변경시켜 렌더링을 방지하는 형식으로 구현된다.
3.1.6 useContext
🔖 props drilling
을 방지하기 위해 사용하는 Context API
를 사용하기 위한 훅
import React, { createContext, useContext, useState } from "react";
// 1. Context 생성
const ThemeContext = createContext();
const ThemeProvider = ({ children }) => {
// 2. 제공할 상태 정의
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
};
return (
// 3. Context Provider로 상태와 함수를 제공
<ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>
);
};
const ThemedComponent = () => {
// 4. useContext를 사용하여 Context 값 사용
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div
style={{
background: theme === "light" ? "#fff" : "#333",
color: theme === "light" ? "#000" : "#fff",
padding: "20px",
}}
>
<p>Current Theme: {theme}</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
};
const App = () => {
return (
<ThemeProvider>
<ThemedComponent />
</ThemeProvider>
);
};
export default App;
createContext
로Context
생성상태와
setter
를 정의하고Provider
를 통해 제공Context.provider
를 이용해 제공할 함수를 감싼 후, 해당 함수에서useContext
를 통해 선언 및 사용
❗다수의 provider
와 useContext
사용 시, 별도의 함수로 감싸 사용하는 것이 타입 추론과 에러 방지에 용이하다.
🏷️Context
사용 시 주의점
Context
를 사용한다면provider
와의 의존성이 어떻게든 생기게 되므로, 재사용성이 감소하고provider
하위에 존재하지 않을 경우 에러가 발생할 수 있다.Context
가 많아질 수록 루트 컴포넌트에Context
를 넣는 것은 좋지 않다. (리소스 낭비)Context
의 사용으로 최적화에 이슈가 발생할 수 있으므로,Context
를 사용한 컴포넌트 렌더링의 전체 과정을 항상 유념하자.
☝️따로 찾아본 Context.provider
로 모달창 만들기
const ModalProvider = ({ children }: { children: ReactNode }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [modalContents, setModalContents] = useState(<></>);
const openModal = (children: ReactNode) => {
setIsModalOpen(true);
setModalContents(children);
};
const closeModal = () => setIsModalOpen(false);
const onDimmerClick = (event: MouseEvent) => {
if (event.currentTarget !== event.target) return;
closeModal();
};
return (
<ModalContext.Provider value={{ isModalOpen, openModal, closeModal }}>
{children}
{isModalOpen && <Dimmed onClick={onDimmerClick}>{modalContents}</Dimmed>}
</ModalContext.Provider>
);
};
어디서든 호출될 수 있는 모달의 특성 때문에
context
로 구현하면 편리하게 사용 가능하다.링크 : 허걱순 멘토님의 모달창 구현
3.1.7 useReducer
🔖useState
와 기본적으로 비슷하나 좀 더 복잡한 상태값을 정의한 시나리오대로 사용 가능한 훅
반환값은
useState
와 동일한 두가지 요소를 가진 배열이다.state
와dispatcher
를 가진다.
useState
와 달리 2 ~ 3개의 인수를 필요로 한다.- 액션을 정의하는
reducer
, 초기값인initialState
, 초기값을 지연생성 시키는init
(필수 ❌)
- 액션을 정의하는
// 1. 리듀서 함수 정의
const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
};
const Counter = () => {
// 2. useReducer 훅 사용
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>Count: {state.count}</h1>
<button onClick={() => dispatch({ type: "increment" })}>Increment</button>
<button onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
</div>
);
};
궁극적인 목적은 복잡한 형태의
state
를 사전에 정의한dispatch
함수로만 수정할 수 있게 하여 안전한 접근과 업데이팅을 구현하는 것reducer
함수를 정의하고 인수로state
와action
을 받아 조건식의 형태로 각각의action
에 해당하는 로직을 구현한다.세번째 인수인
init
(게으른 초기화)는 굳이 사용하지 않아도 무방, 대신useState
와 동일한 효과 및 초기화에 사용할 수 있는 장점이 있다.
🏷️useReducer
와 useState
의 차이
import React, { useState } from "react";
// useState를 사용한 카운터 훅
const useCounter = (initialValue = 0) => {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
};
const CounterWithState = () => {
const { count, increment, decrement, reset } = useCounter(0);
return (
<div>
<h1>useState Counter: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
};
// 리듀서 함수 정의
const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return { count: action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
// useReducer를 사용한 카운터 훅
const useCounterReducer = (initialValue = 0) => {
const [state, dispatch] = useReducer(reducer, { count: initialValue });
const increment = () => dispatch({ type: "increment" });
const decrement = () => dispatch({ type: "decrement" });
const reset = () => dispatch({ type: "reset", payload: initialValue });
return { count: state.count, increment, decrement, reset };
};
const CounterWithReducer = () => {
const { count, increment, decrement, reset } = useCounterReducer(0);
return (
<div>
<h1>useReducer Counter: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
};
export default CounterWithState;
- 결국 동일한 기능을 제공하나, 상태 관리 로직의 복잡도에 따라 취사 선택하는 것이 바람직하다.
3.1.8 useImperativeHandle
🔖실제 개발 과정에선 잘 사용되지 않으나 간혹 쓸모 있는 훅
이를 위해 먼저
React.forwardRef
에 대해 알 필요가 있다.// ref를 props로 넘겨주는 경우 function Child({ ref }) { useEffect(() => { console.log(ref); }, []); } function Parent() { const inputRef = useRef(null); return ( <> <input ref={inputRef} /> <Child ref={inputRef} /> </> ); }
위와 같이 사용할 경우,
ref
는props
로 내려줄 수 없다는 경고가 출력된다.이를 위해
ref
값이 아닌 다른 값으로inputRef
를 내려줄 수 있다.// ref를 props로 넘겨주는 경우(props의 이름 변경) function Child({ parentRef }) { useEffect(() => { console.log(ref); }, []); } function Parent() { const inputRef = useRef(null); return ( <> <input ref={inputRef} /> <Child parentRef={inputRef} /> </> ); }
forwardRef
는 이러한ref
를 내려줄 때props
이름의 일관성을 유지하기 위해 사용한다.// ref를 props로 넘겨주는 경우(forwardRef 사용) const Childe = forward(() => { useEffect(() => { console.log(ref); }, []); }); function Parent() { const inputRef = useRef(null); return ( <> <input ref={inputRef} /> <Child ref={inputRef} /> </> ); }
🏷️useImperativeHandle
이란?
부모에게서 넘겨받은
ref
를 원하는대로 수정할 수 있는 훅이다.// 자식 const ChildComponent = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }, clear: () => { inputRef.current.value = ""; }, })); return <input ref={inputRef} type="text" />; }); // 부모 const ParentComponent = () => { const childRef = useRef(); return ( <div> <h1>useImperativeHandle Example</h1> <ChildComponent ref={childRef} /> <button onClick={() => childRef.current.focus()}>Focus Input</button> <button onClick={() => childRef.current.clear()}>Clear Input</button> </div> ); };
위 예제의 경우
focus
와clear
를 정의하여 부모에서current
값 안의 키를 불러와 사용했다.
3.1.9 useLayoutEffect
🔖기본적으로 useEffect
의 동작과 동일하지만, 모든 DOM의 변경 후에 동기적으로 발생하는 훅 (DOM의 변경이지 변경사항 반영이 아니다.)
< useLayoutEffect의 실행 순서 >
리액트가 DOM을 업데이트
useLayoutEffect
실행브라우저 변경사항 반영
useEffect
실행
🏷️ 사용하는 경우
- DOM이 계산됐으나 화면에 반영되기 전에 하고 싶은 작업이 있을 경우 사용
3.1.10 useDebugValue
🔖일반적으로 dev 모드에서 사용하며, 디버깅 정보를 보여주는 훅
사용자 정의 훅 내부 내용에 관한 정보를 남길 수 있다.
오직 다른 훅 내부에서만 실행 가능하다.
디버깅 관련 정보 제공 시 유용
3.1.11 훅의 규칙
🔖리액트의 훅은 사용 시 몇 가지 규칙이 존재한다..(신경 쓸게 너무 많다잉)
최상위에서만 훅을 호출해야 하며, 반복, 조건문, 중첩된 함수 내에서 훅을 사용하지 못한다.
훅을 호출 가능한건 리액트 컴포넌트 및 사용자 정의 훅 뿐이다.
🏷️파이버의 관점
리액트 훅은 기본적으로 파이버의 링크드 리스트의 호출 순서에 따라 저장
각 훅은 순서에 의존하여 결과값을 저장
순서를 보장받지 못하는 상황(조건 및 반복문, 중첩 함수 등)에서 에러 발생
3.1.12 책 정리 + 주관적인 정리
🔖책 정리
함수 컴포넌트 내에서 훅을 사용하면서 동작에 대해서 고민하거나, 클래스보다 쉽다고 했는데 내용이 많아 땀을 삐질삐질 흘렸을 것이다.
훅을 정확히 이해하고 사용한다면 성능 좋은 리액트 앱을 만드는데 큰 도움이 될 것이다.
🏷️주관적인 정리
너무 많아서 징그럽다... 가 아니라 사용하던 훅이 너무 한정적이어서 놀랐고 이번에 공부한 것을 바탕으로 다양하게 사용해야겠다고 생각했다.
생각보다 여러가지 상황에 맞는 다양한 훅이 있어서 스프린트나 프로젝트를 진행하면서 맞닥뜨렸던 상황들이 생각났고 그때 알고 있었으면 야무지게 사용했을 듯 하다.
3.2 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?
✨사용자 정의 훅과 고차 컴포넌트를 활용하여 재사용 로직을 관리해보자!
3.2.1 사용자 정의 훅
🔖서로 다른 컴포넌트가 같은 로직을 공유하고자 할 때 사용하는 훅
기존 리액트의 훅을 활용해 개발자가 직접 훅을 정의하는 방식이다.
훅의 이름을 무조건
use
로 시작해야 하며, 이를 바탕으로 사용자 정의 훅이라는 것을 인식한다.import { useEffect, useState } from "react"; export default function useIntersectionObserver(ref) { const [isIntersecting, setIsIntersecting] = useState(false); useEffect(() => { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) setIsIntersecting(true); else setIsIntersecting(false); }); }, { threshold: 0.5 } ); if (ref.current) observer.observe(ref.current); return () => { if (ref.current) observer.unobserve(ref.current); }; }, [ref]); return isIntersecting; }
위의 경우
intersectionObserver
DOM API를 이용하여 만든useIntersectionObserver
훅이다.해당하는
Element
의 ref 값을 받아와 뷰포트에서 관측되었을 경우,true
orfalse
를 반환하여 로직을 실행시킨다.
❗결국 use
를 앞에 붙히는 것은 useState
와 useEffect
와 같은 리액트의 훅이 바탕이므로, 이를 사용해 커스텀 훅을 만들었다는 것을 간접적으로 몀시한다.
3.2.2 고차 컴포넌트
🔖 컴포넌트 자체 로직을 재사용하기 위한 방법
- 일종의 고차 함수로, JS의 함수는 일급 객체라는 특성을 이용한 것이다.
👍가장 유명한 것이 바로 React.memo 이다.
🏷️고차 함수 만들어보기
대표적인 고차함수는
Array.prototype.map
이 있다.리액트를 예로 드는 경우,
useState
의setter
또한 실행 가능한 함수를 반환하므로 고차 함수라고 할 수 있다.
🏷️고차 함수를 활용한 리액트 고차 컴포넌트 만들어보기
const checkAuth = () => {
const isAuthenticated = Math.random() < 0.5; // 임의의 인증 여부 확인 로직
return isAuthenticated;
};
// 고차 컴포넌트 함수 정의
const withAuth = (WrappedComponent) => {
return (props) => {
const [isAuthenticated, setIsAuthenticated] = useState(checkAuth());
return isAuthenticated ? <WrappedComponent {...props} /> : <p>로그인이 필요합니다.</p>;
};
};
// 실제 컴포넌트
const Profile = () => {
return <div>프로필 페이지</div>;
};
// 고차 컴포넌트와 실제 컴포넌트 연결
const AuthProfile = withAuth(Profile);
// 애플리케이션 컴포넌트
const App = () => {
return (
<div>
<h1>고차 컴포넌트 예시</h1>
<AuthProfile />
</div>
);
};
export default App;
고차 컴포넌트는 컴포넌트 전체를 감쌀 수 있어 사용자 정의 훅보다 큰 영향력을 끼친다.
컴포넌트 결과물 자체에 영향을 끼치는 공통된 작업의 처리가 가능하다.
❗사용 시 주의할 점
with
로 시작되는 이름을 사용해야한다. (일종의 관습)반드시 컴포넌트를 인수로 받기 때문에, 컴포넌트 자체의
props
를 수정, 추가, 삭제 하는 등의 부수 효과를 항상 최소화해야 한다.컴포넌트를 감싸는 형태다 보니, 항상 최소화해야 결과를 예측하기 쉽다.
3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?
🔖리액트가 제공하는 훅으로만 로직을 처리할 수 있다면 사용자 정의 훅을 사용하는 것이 좋다. (컴포넌트 내부에 미치는 영향 최소화)
function HookComponent() {
const { loggedIn } = useLogin();
useEffect(() => {
if (!loggedIn) {
// 할 거
}
}, [loggedIn]);
}
부수 효과가 항상 제한적이다.
컴포넌트에 어떤 결과를 끼칠지 예측이 상대적으로 쉽다.
❗고차 컴포넌트를 사용해야 하는 경우
// 훅 사용 시
function HookComponent() {
const { loggedIn } = useLogin();
if (!loggedIn) {
return <LoginComponent />;
}
return <>하이</>;
}
// 고차 컴포넌트
const Component = withLoginComponent(() => {
return <>하이</>;
});
- 특정 상황에서 아예 다른 일을 하는 컴포넌트를 노출시켜야 하는 경우 등
✨결론적으로 렌더링의 결과물에 직접 영향을 끼쳐야 하는 로직을 구현한다면 고차 컴포넌트를 사용해야 하지만, 무분별한 사용은 악영향을 끼치므로 사용자 정의 훅을 사용해야 한다.
- 링크 : 고차 컴포넌트
3.2.4 책 정리 + 주관적인 정리
🔖 책 정리
앱의 규모가 커지고, 처리할 로직이 많아지면 중복 작업에 대한 고민도 많아질 것이다.
공통화할 작업과 처리할 방법을 항상 고민하며 이 두가지를 적절히 사용한다면 개발을 효율적으로 할 수 있을 것이다.
🏷️ 주관적인 정리
사용자 정의 훅은 단순히 보조 용도로 구글링을 통해 로직을 검색하며 찾아보곤 했는데, 반복적인 작업을 할 때 구체적으로 생각하여 만들어 보고 싶다는 생각이 들었다.
고차 컴포넌트는 이름과 로직 모두 생소해서 조금 흥미가 생겼고, 컴포넌트 자체를 감싸서 처리하는 방식 또한 조건적인 렌더링을 구현할 때 활용도가 높을 것 같다고 느꼈다.
'Front-End Study > 모던 리액트 딥다이브 스터디' 카테고리의 다른 글
모던 리액트 딥다이브 - 9회차 [4-3] (1) | 2024.06.25 |
---|---|
모던 리액트 딥다이브 - 8회차 [4-1, 4-2] (0) | 2024.06.25 |
모던 리액트 딥다이브 6회차 [2-3, 2-4, 2-5] (0) | 2024.06.17 |
모던 리액트 딥다이브 - 5회차 [2-1, 2-2] (1) | 2024.06.10 |
모던 리액트 딥다이브 - 4회차 [1-6, 1-7] (1) | 2024.06.08 |