리액트 #6 최적화 기능

해기·2022년 11월 3일
0

리액트로 페이지를 구현할 때 성능적인 부분에서 불필요한 부분들의 렌더링을 막아줄 수 있는
여러 기능들 중 세가지를 작성해보자.

일단 세가지로

  1. useMemo
  2. React.memo
  3. useCallback

이렇게 세가지가 있는데 각각 쓰임새가 살짝씩 달라서 정리를 위해 글을 작성함.

  1. useMemo (함수 최적화)

useMemo를 사용해보기 위해 강의를 보면서 만들었던 프로젝트에 사용 예시를 들어보며 진행함.

현재까지 만들어져 있는 화면의 모습은

이러한 모습이다. 만들고 있는 프로젝트는 감정일기장으로 일기를 작성할 수 있는 곳이있고.
그날의 감정을 점수로 매길 수 있는 점수가 1~5점이 있다.
임시로 json placeholder에서 가져온 데이터로 20개의 일기를 무작위로 작성을 해두었는데.
1~5의 감정을 랜덤으로 20개를 뿌려놓은 것 중 기분좋은 일기와 기분이 안좋은 일기의 갯수와
비율을 알려주는 함수를 제작해주심.

코드는 이런식으로 작성이되었고

화면에 출력또한 잘 되고있음.

이제 이걸 콘솔창에서 확인해보면

강의에선 두번 출력이 되던데 나는 세번이출력된다.? 이부분은 일단 건너뛰고
useMemo를 사용해보기위해 사이트를 작동해보면
20개의 일기가있는데 그중 하나를 수정해보면

수정기능을 작동시켜보았는데 수정데이터는 감정 데이터를 건드릴 수 없기때문에
일기비율에 영향이 가지않는다. 하지만 콘솔창을 확인해보면

한번 더 출력이 됐다 콘솔창에 이런 불필요한 렌더링을 막기위해 useMemo를 사용하면 된다.
useMemo는 쉽게말해 연산한 값을 재사용하는 것 이라고하는데
강의에서는 쉽게 수학문제를 풀었던 문제가 나온 답을 기억해두는 것 이라고 알려주셨다.
사용방법은

  1. 함수로 제작해놨던 getDiaryAnalysis를 useMemo로 감싸준다.
    (useMemo 안에 콜백함수로 작동한다.)
  2. 그리고 사용을하는데 getDiaryAnalysis는 useMemo로 제작이되어서 함수가아니고 이제
    안에 작성한 콜백함수의 값을 리턴받는다. 그래서 값으로 사용해줘야 한다.

코드로 한번 알아보면

크게 바뀐부분은 딱히 없다. 함수로 제작한 getDiaryAnalysis를 useMemo로 감싸주었고
useMemo뒤에 대괄호는 useEffect처럼 ependency array로 동작하는 곳이다.
useEffect처럼 생각해보면 diaryData.length 다이어리데이터 배열의 수가 변화할 때만
안의 함수가 동작하게된다 라고 생각하면된다.

  • 최상단에 import해오는거 잊지않기 useMemo는 react의 기능

이제 다시 수정을 해보면

콘솔창에 다시 호출이 되지않는다!
그런데 데이터의 길이가 변화한다면? 일기삭제를 한번 해보면?

정상적으로 잘 작동한다.
이렇게 useMemo로 함수를 이용해 함수의 연산을 최적화하는 연산최적화이다.

  1. React.memo (컴포넌트 재사용. 함수형 컴포넌트의 업데이트 조건걸기)

https://ko.reactjs.org/docs/react-api.html#reactmemo
(리액트 사이트)

이러한 구조가 있을 때
app이 가진 state는 count,text가 있는데 그걸 자식컴포넌트인 CountView와 TextView에게 나눠주었을 때

  1. App에서 count라는 state를 setCount를 사용해서 값을 변경해준다.
  2. CountView컴포넌트는 count를 prop으로 받아오니까 값이 변경될 때 재렌더링이 된다.
  3. 그럼 재렌더링이되는건 App과 CountView일거같지만 아무 관련없는 TextView또한 함께 재렌더링된다.

이렇게 강제로 TextView까지 함께 재렌더링 될 필요가없다. 이런상황에 다음 동작으로 setText를 작동시켰을 때 일어날 일 또한 똑같을것이다.
이런 낭비를 막아줄 수 있는 기능이있다.

원하는 작동방식은 이러한 이미지이다. CountView는 count가 변경될때만 TextView는 text가 변경될때만 렌더링되는것

위의 예시들을 코드로 작성해서 확인을해보면

이렇게 OptimizeTest 라는 부모컴포넌트가 state로 count,text를 가지고있을 때
자식 컴포넌트 두개를 생성해주고 각각 state를 넘겨주어 화면에 출력한 상황인데
두개중 하나의 state에 업데이트가 발생하면 두가지 전부 다 콘솔창에 업데이트가 되었다고
출력되는걸 볼 수 있을것이다.

함께 업데이트가 되어버리는 낭비가 생기는데 이 문제를 컴포넌트 재사용을 해보면 된다.
React.memo를 사용하면되는데 사용방법은 useMemo와 흡사하다.
사용하고자하는 컴포넌트를 React.memo로 감싸주면 된다.

React.memo로 감싸놓은 모습, 이상태에서 하나씩 업데이트를 시켜보면

깜박이는 불 들어오는것부터 확인할 수 있다.

콘솔창 역시 count만 업데이트된걸 출력해준다.

  • 여기서 주의할점은 import를 잘 해와야한다

앞에 React 안써놓으면 에러남

하나 더 예시를 들어보면

객체를 가진 state를 업데이트시키는 예시로

그냥 count 라는 이름의 1을 가지고있는 state와
obj라는 이름으로 객체안에 1을가진 coun가 있는데 두개를 버튼에 가지고있는 값을 그대로
보내주는 버튼을 만들어주었을 때 1이 들어가있는 state를 버튼으로 다시 1을주는 상황이라
React.memo를 통해서 업데이트가 되지않는걸 알 수 있다.
그런데 객체의경우는 업데이트가 이루어지고있는걸 확인할 수 있는데

왜 이런상황이 발생하느냐는

이러한 얕은 비교가 이루어지고있어서 그렇다.
객체는 값을 비교하는것이 아닌 객체가 저장되어있는 주소를 비교하기때문이다.
값도 같고 형태도 같지만 다른 주소에 있기때문이다. 쉬운 비교로

사람으로 비교하면 나이 키 체중이 다 같다해서 같은 사람이 아니라는 말
그래서 이러한 경우에 사용해 볼 수 있는 areEqual 이라는게 있는데

이러한 녀석인데 nextProp과 prevProps가 동일한 값을 가지면 true 아니면 false라 설명이되어있다.
코드로 작성을해서 살펴보면 일단 CounterB에 React.memo는 없애주고

이렇게 작성을 해주면 버튼을눌러도 CounterB가 재렌더링이 되지않는걸 볼 수 있다.
이 부분에 대해서는 이해가 막 쉽게 되지않아서 나중에 사용하면서 더 이해를 해보아야겠다.

  1. useCallback

useCallback은 일단 useMemo와 비슷한 Hook이다.
일단 둘의 가장 큰 차이는 useMemo는 값을 반환하고, useCallback은 함수를 반환한다.
라는것 정도만 알아두고 이번에 최적화할 부분은

화면에 보이듯이 삭제기능을 작동했을 때 일기를 작성하는 공간까지 함께 재렌더링된다.
위의 일기작성하는 부분까지 재렌더링 될 필요가없으니 이걸 막기위해 useCallback을 사용한다.

컴포넌트가 렌더링 되는 조건은 본인이 가진 State의 변화, 부모컴포넌트의 재렌더링, 자신이 받은 Prop이 변경되는 경우에 렌더링이 일어나는데 지금 일기를 작성하는 부분은 함수 하나를 Prop으로 받고있다.
Prop으로 받고있는 함수의 역할은 일기 저장 버튼을 눌렀을 때 데이터에 아이템을 추가하는 함수이다.
(App.js에서 넘겨주고있음)

이제 일기작성하는 부분을 담당하고있는 Edit컴포넌트에 React.memo를 적용시켜서 확인해보면

(export default 하는곳에서 React.memo입혀도 똑같이 작동함)

아직도 똑같이 재렌더링되고있는걸 확인해 볼 수 있는데 아직 같이 재렌더링이 되는 이유는
Edit에서 받아오고있는 prop이 함수인데 그 함수가 App컴포넌트가 재생성될 때 마다
계속 생성되기때문이다. 이제 prop으로 받는 함수가 재생성되지않아야만 최적화가 가능하다.

받아오고있는 함수는

이녀석인데 이녀석을 최적화 시켜줘야한다. 이경우에 useMemo를 사용할 수 없다.
그 이유는 위에 써놓은것처럼 useMemo는 값을 반환하기때문이다.

지금 구현해야하는건 값을 반환받아서 사용할게 아니라.
저 함수를 원본 그대로 Edit컴포넌트에게 전달해주는것이라 useCallback을 사용해야한다.
(함수를 전달해줘야 함 https://ko.reactjs.org/docs/hooks-reference.html#usecallback 공식문서)

공식문서에는 useCallback 안에 작성된 콜백함수를 반환한다. 라고 되어있다. 근데 그냥 반환하는게 아니라 메모이제이션된 콜백을 반환한다. useEffect처럼 dependency array를 작성하는데
dependency array 안에 들어있는 값이 변화하지않으면
콜백함수를 재사용할 수 있도록 도와주는 리액트 훅이다

이제 createEvent 라는 함수를 useCallback으로 최적화시켜보자.

  1. 함수를 useCallback으로 감싸준다.
  2. 두번째 인자인 dependency array에 원하는게 있다면 넣고 아니면 비워두자
  3. useCallback import해왔는지 확인하기

useCallback으로 감싸준 모습

어떻게 설계가 되어있냐면
첫번째로 useCallback 안에 콜백함수로 안에 코드들이 다 저장되어있고(일기저장 했을때 데이터추가기능)
두번째로 dependency array를 비워두어서 마운트 된 시점에 한번만 만들고 그다음부터는
첫번째로 만들었던 함수를 재사용할 수 있도록 만들었음.
이제 다시 확인을해보면

처음에 생성될 때 렌더링되는것 말고는 렌더링이되지않는다.

그런데 여기에서 오류하나가 발생하는데

보다시피 삭제했을때 렌더링의 문제는 잘 해결을했는데 일기저장을 하고나면
19개의 일기가 1개가되어버린다.

이러한 오류가 발생한 이유는 dependency array에 아무값도 넣어주지 않아서그렇다.

dependency array에 아무값도 없기때문에 createEvent는 컴포넌트가 마운트될 때 한번만 생성되기 때문에 그 당시에 diaryData의 state는 빈배열이기 때문에 가장 마지막으로 생성됐을 때
빈배열이라는것 때문에 이런 오류가 발생한것이다.

그래서 빈배열을 기억하고있다가 일기를 추가하면 빈배열에 새로운 데이터를 추가하는것이기 때문에
일기가 하나만 나타나게되는것 그래서 dependency array에 diaryData를 넣어줘야한다.
근데 dependency array에 무언갈 넣으면 그게 변경될 때 마다 함수를 재생성하게되는데
그러면 결과는 원하는 동작이 되지않는다. 이러한 오류를 고치기위해서 함수형 업데이트를 활용한다.

함수형 업데이트는 State에 setState인 상태변화함수엔 값을 넣어야하지만 여기에 함수를 넣어도된다.
화살표 함수로 전달을 해주는데 인자로 diaryData를 넣어준다.

setState에 함수를 전달하는걸 함수형 업데이트라고한다.
이제 이렇게 되면 dependency array를 비워놔도 항상 최신의 State를 인자를통해 참고할 수 있게된다.

이제 작동해보면

정상작동 한다!

profile
프론트엔드 개발 공부중, 글쓰는데 재주가없음

0개의 댓글