React-Native

4. React memo(), useCallback()

Daesiker 2021. 1. 25. 11:06
반응형

4-1 목적

리액트를 이용해서 개발을 하게되면 가장 중요한 요소 중 하나가 렌더링 호출 시점입니다. 불필요한 렌더링이 발생하게되면 그만큼의 시간이 계속 낭비하게 되고 효율성이 떨어져 사용자에게 불편함을 느끼게 할 수 있습니다. 이 때 불필요한 리렌더링을 방지하기 위해 useCallback과 React.Memo를 사용하게 되는데 언제 어떤식으로 사용하는지 알아보겠습니다.


4-2 Reference

React.memo와 useCallback()을 공부하기 전에 참조라는 개념을 알아야 더욱 이해하기 쉽기 때문에 한번 짚고 넘어가겠습니다.

  • pass by reference : 한쪽에 커피를 채우면 다른 한쪽에도 동일한 커피가 채워진다.(얕은 복사)
  • pass by value : 실제로 새로운 컵이 생겨난 것이기 때문에 한쪽에 커피를 채워도 채워지지 않는다.(깊은 복사)

pass by value

//원시값
let value = 1;
let value2 = value;
value === value2 //true
value2 = 2;

//따라서 값이 변경되면 서로 다른 값을 가지게 됨
console.log(value); //1
console.log(value2); //2
//값이 다르므로 할당되었으므로 false값을 반환
value === value2 //false


→자바스크립트의 6가지 타입 중 객체를 제외한 원시 값들(number, String, null, undefined...)은 참조값이 아닌 값(Value)자체를 새로 할당하므로 자료의 2번째 컵과 동일하게 작동한다. value2값에 value값을 대입하였지만 서로 다른 메모리에 할당되었기 때문에 value2값이 변한다고 해서 value값이 변하지 않는다.

pass by reference

let obj = {number : 1};
let obj2 = obj //같은 메모리 할당
obj2.number = 2;

//따라서 값이 변경되면 해당 값을 참조하고 있는 값도 변경됨
console.log(obj) // {number : 2}
console.log(obj2) // {number : 2}

//참조 타입은 동일 참조 값이 아닌 경우 다른 값으로 취급된다.
let arr = ['hello'];
let arr2 = ['hello'];
arr === arr2 // false 다른 참조 값이므로 false 반환
arr[0] === arr[0] // true string은 원시 타입


원시 값이 아닌 객체, 배열, 함수와 같은 개체들은 1번 컵과 동일하게 작동한다. obj2값에 obj값을 대입하면 똑같은 메모리에 있는 값을 할당하기 때문에 obj와 obj2는 같은 메모리에 같은 값을 가지게 된다. 하지만 같은 값이라도 서로 다르게 선언을 하면 동일한 메모리에 있는 값이 아니기 때문에 === 비교연산자를 사용하면 JavaScript는 같은 값이 아니라고 판단을 한다.


4-3 리렌더링

💡
React가 리렌더링 되는 경우
  1. state 변경이 있을 때
  1. 부모 컴포넌트가 렌더링 될 때
  1. 새로운 props가 들어올 때
  1. shouldComponentUpdate에서 true가 반환될 때
  1. forceUpdate가 실행될 때

1, 2번의 경우 React는 얕은 비교를 통해 새로운 값인지 아닌지를 판단한 후 리랜더링이 됩니다.

원시 값들은 서로 다른 참조 값을 가지고 있어도 비교함에 있어 문제가 없지만,(서로 다른 메모리에 할당이 되어도 값이 같으면 ===에서 true값을 반환) 객체, 배열, 함수와 같은 객체들은 같은 참조 값이 아니라면 새로운 값으로 판단하기 때문에 동일한 값이더라도 리렌더링이 되어 불필요한 시간을 낭비하게 됩니다.

ex) 상위 컴포넌트의 state 변경 → 상위 컴포넌트 리랜더링 → 하위 컴포넌트에게 넘겨주는 props 생성 → props에 참조 타입이 있다면 동일한 값이라도 얕은 비교를 통해 새로운 값으로 판단 → 리랜더링

그래서 React.memouseCallback()을 사용하여 얕은 복사로 인한 렌더링을 막아줍니다.


4-4 useCallback

어떻게 사용하는지?

인라인 콜백과 그것의 의존성 값의 배열을 전달하세요. useCallback은 콜백의 메모이제이션된 버전을 반환할 것입니다. 그 메모이제이션된 버전은 콜백의 의존성이 변경되었을 때에만 변경됩니다ㅣ. 이것은, 불필요한 렌더링을 방지하기 위해(예로 shouldComponentUpdate를 사용하여) 참조의 동일성에 의존적인 최적화된 자식 컴포넌트에 콜백을 전달할 때 유용합니다.

링크 : https://ko.reactjs.org/docs/hooks-reference.html#usecallback

Example

const memoizedCallback = useCallback(
	() => {doSomething(a,b);}, //inline callback
	[a, b], //dependency
);


memoizedCallback에 할당되는 값은 a, b 값이 수정될 때에만 inline callback이 새로 생성되는 함수이다.

useEffect에 의존성 값이 있을 경우 componentDidUpdate(), 빈 배열일 경우 componentDidMount()와 같이 동작하는 것과 유사한 방식이다.

useCallback은 최초에 혹은 특정 조건에서 생성한 함수의 참조를 기억하여 반환해주는 Hook이다. 2번째 파라미터가 빈배열이면 최초에 생성된 함수를 지속적으로 기억하고, 배열을 주면 배열안에 있는 값이 바뀔 경우에만 함수를 새로 생성한다.

새로 생성되지 않는다함은 메모리에 새로 할당되지 않고 동일 참조 값을 사용하게 된다는 것을 의미하고, 이는 최적화된 하위 컴포넌트에서 불필요한 렌더링을 줄일 수 있다는 것을 뜻한다.

하지만 여기서 또 주의해야할 점이 있다. useCallback은 상하위 컴포넌트 관계에서 상위 컴포넌트가 넘겨주는 props를 핸들링하는 역할을 하는데, 하위 컴포넌트가 그 부분을 신경쓰지 않고 상위 컴포넌트의 렌더링 여부에 따라 자동으로 렌더링이 되면 무용지물이된다.

이것때문에 React.memo를 같이 사용해야 한다.


4-5 React.memo

사용하는 이유

useCallback 사용만으로는 하위 컴포넌트의 리렌더를 막을 수 없다.

하위 컴포넌트가 참조 동일성에, 의존적인 최적화된 props이어야만 불필요한 리렌더링을 막을 수 있다.

React.memo는 React 컴포넌트의 shouldComponentUpdate 라이프 사이클이 기본으로 내장된 함수형 컴포넌트이다.

얕은 비교연산을 통해 동일한 참조 값의 prop이 들어온다면 리렌더링을 방지시킨다.

어떻게 사용하는 것일까?

React.memo는 고차 컴포넌트(Higher Order Component)입니다. React.PureComponent와 비슷하지만 class가 아니라 함수 컴포넌트라는 점이 다릅니다. 당신의 함수 컴포넌트가 동일한 props로 동일한 결과를 렌더링해낸다면, React.memo를 호출하고 결과를 메모이징(Memoizing)하도록 래핑하여 경우에 따라 성능 향상을 누릴 수 있습니다. 즉, React는 컴포넌트를 렌더링하지 않고 마지막으로 렌더링된 결과를 재사용합니다. React.memo는 props 변화에만 영향을 줍니다. React.memo로 감싸진 함수 컴포넌트 구현에 useState 또는 useContext 훅을 사용한다면, 여전히 state나 context가 변할 때 다시 렌더링됩니다. props가 갖는 복잡한 객체에 대하여 얕은 비교만을 수행하는 것이 기본 동작입니다. 다른 비교 동작을 원한다면, 두 번째 인자로 별도의 비교 함수를 제공하면 됩니다.

링크 : https://ko.reactjs.org/docs/react-api.html#reactmemo

고차 컴포넌트이므로 사용중인 component를 memo로 감싸주기만 하면 된다.

얕은 비교가 아닌 커스텀을 하고 싶다면 2번째 인자로 비교함수를 넣어 사용할 수 있다.

const memoizedComponent = React.memo(
	//props를 사용하여 렌더링
	(props) => ();
	//nextProp이 prevProp와 동일한 값을 가지면 true, 아니면 false
	(prev, next) => {};
);

→ memo 내부에 코드를 작성하는 경우

function MyComponent(props) {
	/* props를 사용하여 렌더링 */
}
function areEqual(prevProps, nextProps) {
	/* nextProp === prevProp ? true : false */
}
export default React.memo(Mycomponent, areEqual)

→ 컴포넌트와 비교함수를 각각 별도로 작성해서 memo로 감싸는 경우


React.memo의 비교함수는 false인 경우에 리렌더링을 발생시킨다.

shouldComponentUpdate와 유사한 역할이지만 반대로 동작한다.


반응형