[Tistory] Context API 리렌더링 되는 경우 알아보기

원글 페이지 : 바로가기

👍 참고 1. https://solo5star.dev/posts/42/ 2. https://velog.io/@velopert/react-context-tutorial#%EA%B0%92%EA%B3%BC-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%ED%95%A8%EC%88%98%EB%A5%BC-%EB%91%90%EA%B0%9C%EC%9D%98-context%EB%A1%9C-%EB%B6%84%EB%A6%AC%ED%95%98%EA%B8%B0 3. https://velog.io/@dahyeon405/Context-API%EC%9D%98-%EB%A6%AC%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%8C%80%ED%95%9C-%EC%98%A4%ED%95%B4 👀 Provider 내부에 컴포넌트가 있는 경우 () const CounterValueContext = createContext(0);
const CounterActionContext = createContext({
increase: () => {},
decrease: () => {},
});

export function CounterProvider({ children }: { children: React.ReactNode }) {
const [counter, setCounter] = useState(0);
const increase = () => setCounter((prev) => prev + 1);
const decrease = () => setCounter((prev) => prev – 1);
console.log(‘Counter Provider’);

return (



{children}


);
}

export function useCounter() {
const value = useContext(CounterValueContext);
return value;
}

export function useSetCounter() {
const value = useContext(CounterActionContext);
return value;
} export default function ContextPage() {
console.log(‘Context Page’);
return (






);
} // Text.tsx
export default function Text() {
console.log(‘Text’);
return

Counter Text

;
}

// Plus.tsx
export default function Plus() {
const { increase } = useSetCounter();
console.log(‘Plus’);

return ;
}

// Minus.tsx
export default function Minus() {
const { decrease } = useSetCounter();
console.log(‘Minus’);

return ;
}

// Number.tsx
export default function Number() {
const count = useCounter();
console.log(‘Number’);

return

{count}

;
}

// Hello.tsx
export default function Hello() {
console.log(‘Hello’);
return

Hello!

;
} 컴포넌트를 제외한 , , , 컴포넌트 모두 counter 상태가 변경될때마다 리렌더링이 발생한다. 는 CounterProvider의 내부 상태가 변경되었기 때문에 리렌더링되었고, 리렌더링 되면서 increase, decrease 함수도 새로 만들어지기 때문에 context의 값을 바라보고 있는(useCounter, useSetCounter) 모든 자식 컴포넌트들이 리렌더링 되었다. 리렌더링을 막는 방법 context의 값을 사용하고 있지 않은 의 리렌더링을 막는 방법은 컴포넌트에 React memo 를 적용하거나 컴포넌트처럼 children으로 렌더링 하는 방법이 있다. increase, decrease 함수를 사용하고 있는 컴포넌트의 리렌더링을 막는 방법은 useMemo를 적용한 actions 객체를 만들어서 전달하는 것이다. 이러면 , 컴포넌트만 리렌더링 된다. export function CounterProvider({ children }: { children: React.ReactNode }) {
const [counter, setCounter] = useState(0);
const actions = useMemo(
() => ({
increase() {
setCounter((prev) => prev + 1);
},
decrease() {
setCounter((prev) => prev – 1);
},
}),
[],
);
console.log(‘Counter Provider’);

return (


{children}


);
} increase, decrease 각 함수에 useCallback을 적용해 value = {{ increase, decrease }} 로 전달한 경우, 렌더링 최적화가 적용되지 않는다. 왜냐하면 가 리렌더링 되면서 increase, decrease 함수는 메모이제이션 됐지만, value로 전달하는 객체는 새로 만들어지기때문에 최적화가 되지 않는다. 만약 increase 함수 하나만 전달한다면 useCallback만으로 최적화할 수 있다. 👀 children으로 컴포넌트를 렌더링 한 경우 export function CounterProvider({ children }: { children: React.ReactNode }) {
const [counter, setCounter] = useState(0);
const actions = useMemo(
() => ({
increase() {
setCounter((prev) => prev + 1);
},
decrease() {
setCounter((prev) => prev – 1);
},
}),
[],
);
console.log(‘Counter Provider’);

return (


{children}


);
} export default function ContextPage() {
console.log(‘Context Page’);
return (









);
} // Text.tsx
export default function Text({ children }: { children: React.ReactNode }) {
console.log(‘Text’);

return (

Counter Text
{children}

);
}

// HeadingNumber.tsx
export default function HeadingNumber() {
const count = useCounter();
console.log(‘Heading Number’);

return ★ {count} ★;
}

// Hello.tsx
export default function Hello() {
console.log(‘Hello’);
const count = useCounter();

return

Hello!

;
} – 는 children으로 컴포넌트를 렌더링한다. – 는 count 상태를 가지지만 사용하지 않는다. count 상태가 변경되면 와 useCounter()를 사용한 , , 컴포넌트만 리렌더링 된다. (actions는 useMemo를 적용했기 때문에, 컴포넌트는 리렌더링 발생하지 않음) 가 리렌더링 되었어도 context 값을 사용하지 않기 때문에 리렌더링 되지 않는다. 위 경우들을 통해 Context API의 모든 자식 컴포넌트들이 리렌더링 되는 것은 아니라는 사실이다! 👀 하나의 context provider로 값과 함수를 사용한다면? const CounterContext = createContext({
counter: 0,
actions: { increase: () => {}, decrease: () => {} },
});

export function CounterProvider({ children }: { children: React.ReactNode }) {
const [counter, setCounter] = useState(0);
const actions = useMemo(
() => ({
increase() {
setCounter((prev) => prev + 1);
},
decrease() {
setCounter((prev) => prev – 1);
},
}),
[],
);
const value = useMemo(() => ({ counter, actions }), [counter, actions]);
console.log(‘Counter Provider’);

return (
{children}
);
}

export function useCounter() {
const value = useContext(CounterContext);
return value;
} // Plus.tsx
export default function Plus() {
const {
actions: { increase },
} = useCounter();
console.log(‘Plus’);

return ;
}

// Minus.tsx
export default function Minus() {
const {
actions: { decrease },
} = useCounter();
console.log(‘Minus’);

return ;
} 하나의 provider를 통해 값을 전달하고 있는 경우 context의 값을 사용하고 있는 모든 컴포넌트에서 리렌더링이 발생한다. actions는 useMemo를 적용했어도, counter가 변경될 때마다 value의 useMemo dependencies가 바뀌기 때문이다. 그래서 상태가 자주 변경되는 경우에는 값과 상태 변경 함수 provider를 분리하여 관리하는게 좋다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다