메모이제이션(Memoization)
:
말그대로 메모리에 기록해 리랜더링 되는것을 막는 방법
해당부분을 이해하기위해 먼저 코드를 작성하여 살펴보았다.
부모와 자식 컴포넌트를 만든다.
부모에게는 부모가 랜더링 됩니다라는것을 콘솔에 나오게하고, 자식에게는 자식이 랜더링됩니다 가 나오게 한 다음 자식컴포넌트를 부모컴포넌트에 import 한다.
먼저 let 을 사용해 카운트를 만들고, 하나는 state를 사용해 카운트를 만들었다.
그리고 각각의 함수를 만들어 onClick에 바인딩 해주었다.
그리고 각 값이 콘솔과 화면에 나오도록 작성해주었다.
let 으로 카운트를 만들경우:
콘솔상에서는 카운트가 올라가고있다.
그러나 화면에 나오지는 않는다. ==>리랜더링이 안되기에 화면에적용되지 않는것이다.
state를 사용해 카운트를 만들경우:
state는 클릭시 1씩 오르고 바뀐state로 다시 그려지는 리랜더링이 일어난다. ==> 리랜더링 된다는 것은 다시 전부 그려진다는 의미! 당연히 자식 컴포넌트 또한 다시 그려지게된다.
즉, let 으로 만든 카운트도 다시 초기화되어 1부터 시작하게된다.
=====> 새롭게 다시만들어진다. === 메모리 어딘가에 새로운게 저장된다. --> 메모리 금방 차게된다.
일정수준 이상이되면 메모리가 차게되고 그러면 프로그램이 종료가된다.
따라서 크롬 내부에서는 일정수준이상이 되면 안쓰이는 것들을 판단해 지운다.
이러한것을 최적화라고 하는데, 최적화가 안되어있다는 것은 반대로 내가 수시로 지워야한다는 것이다.
이렇게 안쓰는 메모리를 지워 새롭게 변수 저장할 수 있게 만드는 과정이 가비지 컬랙션이라한다.
이 가비지 켤랙션과정으로 한방에 모아 지우려면 그때마다 약간의 멈춤현상이 일어난다.
다시말해, 메모리를 낭비한다면 그만큼 가비지 컬렉션으로 멈춤 현상이 많이 일어나게되어 좋지 않다는 것이다.
따라서 useMemo라는 기능을 사용한다.
해당 변수를 기록하는 방식으로 useEffect와 사용방법이 비슷하다.
만약 아까 선언한 let 방식의 카운트 변수 아래 const aaa = Math.random()하여 랜덤한 숫자를 받아와보자. 해당 숫자는 state가 바뀌면서 또 랜더링되며 랜덤으로 받아온 숫자도 바뀌어 나온다.
그럼 이 랜덤한 숫자를 useMemo를 사용해 기록해둔다면?
const aaa = useMemo(()=>Math.random(),[])
대괄호 부분은 마찬가지로 dependencyarray라고 하는 부분이다.
이 부분에는 리랜더링을 했으면 좋겠는 함수 등을 넣어준다.
없으면 넣어주지 않아도된다.
const aaa = useMemo(()=>Math.random(),[])
console.log(aaa);
이것을 실행해 본다면 첫랜더링시에는 랜덤한 숫자가, 그다음 state카운트올리기버튼을 통해 state가 변하면서 랜더링되는 시점에는 이전의 랜덤한 숫자가 useMemo로 인해 저장되어 숫자가 변경되지 않고 같은 숫자만 나오는것을 볼 수 있다.
// 한번 저장해놓고 그것 계속 재사용
그러면 변수는 useMemo로 저장한다고 하고, 함수는 저장할 수 없을까?
함수일 경우에는 useCallback을 사용한다. 방법은 동일하다.
const onClickCountLet = useCallback(() => {
console.log(countLet + 1);
countLet += 1; // countLet = countLet + 1
}, []);
이런식으로 작성해 준다.
그런데 이러한 useCallback을 잘못적용하는 사례가있다.
const onClickCountState = useCallback(() => {
// 함수 전체는 useCallback으로 저장되어있음,
console.log(countState + 1);
setCountState(countState + 1);
}, []);
이런식으로 작성할 경우, 함수는 저장되어있는데 거기에 state 첫 state도 저장되기에 state가 변경되지 않는 현상이 일어난다.
초기에 0이었던게 버튼은 클릭시 1이 증가한 1이 스테이트에저장되고 그것이 메모리(useCallback)에 저장되어 아예 기억을 해버려 일어나는 현상.
이럴때는 state+1을 하는것이아니라 prev로 이전값을 꺼내와 거기에 +1하는 방식으로 작성해야한다.
setCountState((prev) => prev + 1);
이렇게 바꾼다면 저장해놨다고 하더라도 , 실행되는 시점에서 prev를 꺼내오는 과정이기에 리랜더링은 안되며 카운트는 올라간다.
dependencyarray에 리랜더링할경우를 적을 시 주의사항
** 이 안에 넣는 경우들에 한에서는 리랜더링 해야된다는 것을 의미하는데, 한두개만 적어줄경우에는 상관없으나, 그 이상의 경우에는 차라리 리랜더링을 하는경우가 나을 수도 있으니 상황에따라 적용하는 것이 좋다.
onClick에 바인딩하지 않고 그 안에서 바로 함수를 작성해줘도 된다.
onClick={useCallback(() => {
console.log(countLet + 1);
countLet += 1; // countLet = countLet + 1
},[])}
컨테이너 부분이 줄어 사용방법이 간편하나, 가독성이 별로 좋지 못하다.
규모가 커지면 유지보수가 힘드니 가급적 사용하지 말것.
자식컴포넌트에 memo라는 것이 붙으면 부모가 리랜더링될때 같이 리랜더링 되는것을 방지할 수 있다.
자식부분 적용한 예제
unction MemoizationChildPage() { console.log("자식이 랜더링 됩니다."); return ( <> <div>=======================</div> <h1>저는 자식컴포넌트 입니다</h1> <div>=======================</div> </> ); } // React.memo() 같은것 export default memo(MemoizationChildPage); // memo로 감싸면 기록됨. 즉, HOC임.
자식컴포넌트에서 특정조건시 리랜더링 되게 하려면 props로 넘겨주면된다. 그러면 자식이 받든, 받지 않든 일단 넘겨주는 props가 변경이 되었다면 자식이 리랜더링된다.
즉, 메모가 적용된 자체가 유지된다는 것.
그렇다면 모든 컴포넌트에 memo라고 적용하면 되지 않을까?
그러면 무척 편하겠지만... 애초에 자식이 리랜더링 될 일이 없는 컴포넌트에도 memo를 붙일경우 그 memo때문에 메모리에 기록이 되어 쓸데없이 메모리를 낭비하게 되는경우가 생길 수 있다.
따라서 필요한 부분에만 걸어야한다.
부모에 리렌더링하는 로직이 있는데, 자식이 리랜더링되는게 비효율적이라면 자식컴폰넌트에 memo를 걸어두자!
부모 자체에 리랜더링 될 로직들이 없다면 자식에게도 굳이 memo가 필요하지 않다.
:원래 그리기위해 한번 메모리에 들어가고 memo로 인해 그려준것을 저장하게되니 결국 메모리가쌓여 가비지 컬렉션 초기화가 필요하게된다.
가비지 컬랙션 정의: 필요없게된 영역을 해제하는 기능!
프로그램 내부에 가비지 컬렉터가 안쓰는 메모리를 지운다.
브라우저에 어떻게 그림이 그려질까?
다운로드를 받아옴--> html과 css를 준비해 합치며 위치를 그림, --> 색칠
브라우저마다 그리는 과정의 이름과, 칠하는 과정의 용어가 다른데,
크롬의 경우에는 레이아웃/ 페인트
파이어폭스 등에는 리플로우/리페인트라고한다
그러면 둘중 뭐가 속도가 더 느릴까?
위치를 그리는 작업이 더 느리다고 할 수 있다.
위치가 바뀌거나 크기가바뀌면 다시 색칠 해야한다는 것. 마우스를 올렸을때 등으로 실습을 해보자면 마우스를 올렸을때 단순히 색만 바꾼다면 repaint만 일어나 색만 바뀐다.
그런데, 마우스를 올렸을때 크기만 바뀔경우, 색은 변경되지 않았지만 적용된 색도 그 변경된 크기에따라 다시 변경된다는것을 알수 있었다. 해당부분은 React dev Tools를 다운받아 프로파일러 부분을 사용해 볼 수 있었다.
위와같은 과정들을 다 고려하며 코딩을 하고 하지는 않는다. 일단 만들고 나서 특정 페이지에서 느린것 같다면 이때 최적화가 들어간다. 우리는 이때 어떤 부분이 Reflow되는지 Repaint되는지 보고 Reflow가 일어날때 Repaint가 같이 따라다닌다는것을 기억해야한다.
브라우저에서 요청을 받아 그리는 과정
브라우저--> 사이트 접속 --> 프론트엔드서버에 접속해 html을 받아옴. 그 후 html에서 link태그와 script태그를 만나면 그 주소들의 요청을 보내고, 그 다음 html의 윗부분부터 코드를 하나씩 읽어 실행한다.
즉, html을 받아오며 링크태그와 스크립트 태그의 링크들로가 다운로드를 받아오는것이 먼저 일어난다!!
미리 link태그 등으로 페이지 이동했을때 실행할 부분을 받아와 실제 페이지 이동시에는 미리 받아오는것을 꺼내쓰는 방식!
해당실습은 a 태그를 클릭시에 특정 페이지로 이동하는것을 작성하고 link태그를 통해 해당 페이지를 먼저 받아올 수 있게 작성하였다.
<!DOCTYPE html> <html lang="ko"> <head> <title>프리페치</title> <link rel="prefetch" href="./board.html" /> <!-- 미리 받아와 캐쉬에 저장 a태그를 클릭해 이동할때는 시간단축됨. --> </head> <body> <a href="./board.html">게시판으로 이동하기</a> </body> </html>
이렇게 실행해보면 개발자 도구에서는 html을 받아오며 link태그의 주소도 같이 다운받아오고, 그렇게되면 실제 a태그를 클릭해 페이지 이동을 했을때 size부분에 prefetch cached 라고 나온것을 알 수 있다. 미리 다운받아 온것을 꺼내쓰는 것이다.
그러나 리엑트와 넥스트엔 이미 내장되어있는기능이다.
이것을 개선할 수 있는 방법이 마우스를 올리면 그 페이지에서 받아올 데이터를 미리 캐싱해 아폴로 캐시에 저장하는것이다.
미리 다운받아 메모리에 올려놓는 방법. 먼저 보여주게 순서를 조작한다.
일단 link나 script를 한번에 받아올 수 있는 개수는 6개이다. 따라서 일단 6개 정도를 적었다.
그리고 body태그에 img를 하나 넣었는데 이것을 미리 실행되게 한다면 좀더 빨리 볼 수 있다. 이때 사용하는게 프리로드.
<!DOCTYPE html> <html lang="ko"> <head> <!-- 프리로드: 한 번에 6개씩 받아오므로, body태그의 이미지는 가장 마지막에 다운로드 --> <!-- 눈에 보이는 이미지를 먼저 다운로드 받아서 보여주고, 클릭하면 실행되는 JS는 나중에 받아오기 --> <!-- 따라서, DOMContentedLoaded 이후, Load까지 완료되는 최종 로드 시간이 더 짧아짐 --> <title>프리로드</title> <link rel="preload" as="image" href="./dog.jpeg" /> <!-- as ="image" preload를 image로 썼을것이다. 주소는 href="./dog.jpeg" ==> 이미지주소 --> <!-- 일반로드 --> <link rel="stylesheet" href="./index.css" /> <script src="./index1.js"></script> <script src="./index2.js"></script> <script src="./index3.js"></script> <script src="./index4.js"></script> <script src="./index5.js"></script> <!-- 여기까지가 6개. 위에부터 6개가 먼저 받아지고 그다음 6개정도를 받음 --> <script src="./index6.js"></script> </head> <body> <img src="./dog.jpeg" /> </body> </html>
<link rel="preload" as="image" href="./dog.jpeg" />
링크 태그를 넣어 preload할건데 해당부분은 이미지일 거고 주소는 href 부분이다 라는 것이다.
그렇다면 프리패치와 프리로드 차이는?
프리패치: 다음페이지에서 나중에 쓸것미리 받아놓기. 즉, 무조건 맨 마지막에 받는다.
프리로드: 지금페이지에서 눈에 보이는 부분들을 먼저 받는, 눈에 보이는 부분의 순서를 앞으로 당기고싶을때 사용한다.
errors: [{message: "$regex has to be a string", locations: [{line: 2, column: 3}],…}]
내가 좋아요한 아이템을 불러와야했는데 다음과 같은 오류가떴다.
찾아보다가 우연히 어떤 블로그를 보게되었다.
원인
regex??
regular expression 의 약자 regex 는 텍스트를 일치시키고 찾고 관리하는 데 도움이 되는 패턴을 만들 수 있는 텍스트 문자열입니다.
출처 : https://www.computerhope.com/jargon/r/regex.htm
서버에 검색이 있는 경우 none대신 string을 달라는 말, 즉 검색어가 입력하지 않았기 때문에 생긴 오류였다.
해결방법
const { data } = useQuery(FETCH_USED_ITEMS_I_PICKED, {
variables: { search: "" },
});
이렇게 search: "" 넣어주니
데이터를 잘 불러올 수 있었다.