데이터를 전역적으로 관리하는 Hook
Props를 전달하는 기존 방식
: 부모 컴포넌트 -> 자식 컴포넌트 -> 자식의 자식 컴포넌트 -> ... -> 최종 목적지 컴포넌트
문제점
Prop Drilling
현상이 발생함
특히, Props의 출발지와 목적지의 거리가 멀수록, 오류 발생 시 해당 오류의 소스(source) 파악과 추적이 힘들어짐
따라서, 출발지에서 목적지까지 한번에 전달할 수 있는 방법이 필요함 => Context API
createContext()
메서드를 사용하여 전역 context를 생성한다. // context/MainContext.js
import { createContext } from 'react';
export const MainContext = createContext(null);
// 별도의 초기값이 존재하는 경우, null 대신 초기값을 입력한다.
Provider
속성을 사용한다. // UpperComponent.jsx
import MiddleComponent from "./MiddleComponent";
import { MainContext } from "../context/MainContext.js";
function UpperComponent() {
const data1 = "데이터1";
const data2 = "데이터2";
return (
<MainContext.Provider value={{ data1, data2 }}>
<MiddleComponent />
</MainContext.Provider>
);
}
// MiddleComponent.jsx
import LowerComponent from "./LowerComponent";
function MiddleComponent() {
return <LowerComponent />;
}
export default MiddleComponent;
useContext()
메서드를 사용한다. // LowerComponent.jsx
import { useContext } from "react";
import { MainContext } from "../context/MainContext.js";
function LowerComponent({ data1, data2 }) {
const data = useContext(MainContext);
return (
<>
<div>
Data 1 : {data.data1}
</div>
<div>
Data 2 : {data.data2}
</div>
</>
);
}
export default LowerComponent;
데이터를 전역적으로 관리하더라도, props는 상위 컴포넌트 -> 하위 컴포넌트 방향으로 전달되기 때문에, props를 제공한 컴포넌트에서 해당 props의 값을 변경하면, 해당 props를 사용하는 모든 컴포넌트가 리렌더링된다.
React는 렌더링의 횟수에 따라 성능이 좌우된다.
예를 들어, 7개의 자식 컴포넌트를 가진 부모 컴포넌트에 리렌더링이 발생하면, 7개의 자식 컴포넌트도 '부모 컴포넌트에 리렌더링이 발생했다'는 이유로 강제로 리렌더링을 수행한다.
따라서, 1번의 리렌더링만 발생하면 되는 작업에서 총 8번의 리렌더링이 발생했다.
이러한 상황은 성능에 좋지 않기 때문에, React는 렌더링의 횟수를 가능한 최대로 줄여서 프로그램의 성능을 최적화하려 한다.
여기서 메모이제이션(Memoization)
은 대표적인 최적화 방법이다.
[주요 용어]
- 캐싱(Caching) : 데이터의 복사본을 임시 저장소에 저장하고, 해당 데이터가 필요할 때마다 이를 재사용하여 프로그램의 처리 시간을 단축시키는 기법
- 순수 함수(Pure Function) : 동일한 값을 입력했을 때 동일한 결과값을 출력하는 함수
React의 대표적인 메모이제이션(Memoization) 방법
속성/메서드 | 설명 |
---|---|
React.memo() | 컴포넌트를 캐싱(Caching)함 |
useCallback() | 함수의 정의문을 캐싱(Caching)함 |
useMemo() | 함수의 결과값을 캐싱(Caching)함 |
컴포넌트를 캐싱하는 React API
const 컴포넌트명 = React.memo(function 컴포넌트명() {
// 생략
return (
// 생략
);
});
export default 컴포넌트명;
// 간소화된 형태
function 컴포넌트명() {
// 생략
return (
// 생략
);
}
export default React.memo(컴포넌트명);
부모 컴포넌트가 리렌더링 되어도 React.memo
로 지정한 자식 컴포넌트의 내부가 변경되지 않으면, 해당 자식 컴포넌트는 리렌더링 되지 않는다.
함수의 정의문을 캐싱하는 Hook
JavaScript의 함수는 Function 객체다.
따라서, 컴포넌트의 props로 함수를 받은 경우, 이는 Function 객체를 받은 것과 동일하다.
따라서, 부모 컴포넌트에 리렌더링이 발생하면, 컴포넌트는 새로운 주소에 할당된 Function 객체를 props로 받게 되고, 이를 '내부가 변경되었다' 고 판단하기 때문에, 해당 컴포넌트는 React.memo
로 지정했더라도 리렌더링을 수행한다.
useCallback(() => {수행할 함수}, [의존성 배열]);
useEffect() 와 마찬가지로, 리렌더링될 때마다 특정자업을 수행하게 할 컴포넌트를 bind한다.
빈 배열([]
)을 입력할 경우, 처음 렌더링된 이후 어떠한 렌더링에도 수행되지 않는다.
함수의 결과값을 캐싱하는 Hook
주로 연산이 오래 걸리는 함수에 사용하여 프로그램의 처리 시간을 단축시킨다.
// AS-IS
const value = 반환할_함수();
// TO-BE
const value = useMemo(() => {
return 반환할_함수()
}, [의존성 배열]);
useCallback(수행할 함수, 의존성 배열)
은 useMemo(() => 수행할 함수, 의존성 배열)
와 같다.
function useCallback(수행할 함수, 의존성 배열) {
return useMemo(() => 수행할 함수, 의존성 배열);
}
캐싱(Caching) 자체가 별도의 메모리에 데이터를 저장하는 것이므로, 남발하면 오히려 성능에 악영향을 줄 수 있다.