목표
- Hooks Performance-optimizations 정리
1. useEffect 사례
1) Props와 State를 사용하는 경우
function Example({ someProp }) {
useEffect(() => {
function doSomething() {
console.log(someProp);
}
doSomething();
}, [someProp]);
}
- useEffect 내부에서 props 값인 someProp을 사용하는 경우 종속성에 추가
2) Props와 State를 사용하지 않는 경우
useEffect(() => {
function doSomething() {
console.log('hello');
}
doSomething();
}, []);
- 초기 렌더링 시, 호출 후 종료
- componentDidMount와 같은 효과, 종속성이 없으면 componentDidUpdate
3) effect 내부 변수를 이용한 분기 처리
useEffect(() => {
let ignore = false;
async function fetchProduct() {
const response = await fetch('http://myapi/product/' + productId);
const json = await response.json();
if (!ignore) setProduct(json);
}
fetchProduct();
return () => { ignore = true };
}, [productId]);
- 내부적으로 분기가 필요하면 위와 같이 적용
- 모양새가 약간 클로저랑 비슷한 거 같음
4) Interval과 같은 형태를 사용하는 경우
4-1) 내부 State 상태를 사용하는 방법
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count => count + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
4-2) useRef를 사용하는 방법
function Example(props) {
const latestProps = useRef(props);
useEffect(() => {
latestProps.current = props;
});
useEffect(() => {
function tick() {
console.log(latestProps.current);
}
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
}
- 대표적인 사례 : Counter(Timer)
- 종속성을 사용하지 않고, setCount 내부 state 상태를 통해 지속적인 업데이트 수행
2. Memoization
1) shouldComponentUpdate
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
- useMemo, useCallback과 같은 Hook 제공
- Memoization 없이 작동하도록 코드를 작성한 다음 추가한 뒤 성능 최적화 권장
function Parent({ a, b }) {
const child1 = useMemo(() => <Child1 a={a} />, [a]);
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
- 컴포넌트 메모이제이션 생성은 Hook 규칙으로 인하여 위와 같이 적용한다.
2) Memoization을 위한 useState, useRef
2-1) useState
function Table(props) {
const [rows, setRows] = useState(createRows(props.count));
}
function Table(props) {
const [rows, setRows] = useState(() => createRows(props.count));
}
- 비용이 많이 드는 initialState 시 활용
2-2) useRef
function Image(props) {
const ref = useRef(new IntersectionObserver(onIntersect));
}
function Image(props) {
const ref = useRef(null);
function getObserver() {
if (ref.current === null) {
ref.current = new IntersectionObserver(onIntersect);
}
return ref.current;
}
}
- 비용이 많이 드는 initialValue에 활용
3) useReducer를 이용한 깊이 있는 props 전달 방지
const TodosDispatch = React.createContext(null);
function TodosApp() {
const [todos, dispatch] = useReducer(todosReducer);
return (
<TodosDispatch.Provider value={dispatch}>
<DeepTree todos={todos} />
</TodosDispatch.Provider>
);
}
- 깊을 것 같은 컴포넌트가 나타나면, Context + useReducer 패턴을 사용한다.
function DeepChild(props) {
const dispatch = useContext(TodosDispatch);
function handleClick() {
dispatch({ type: 'add', text: 'hello' });
}
return (
<button onClick={handleClick}>Add todo</button>
);
}
- Child는 위와 같이 적용하여 편리하면서 직관적으로 사용할 수 있다.
출처
Hook 자주 묻는 질문