useState를 사용해서 state를 관리할 때, 렌더링을 고민하게 되면서
state 변수와 관련되어 있지만
직접적으로 렌더링에 관여하지 않아도 되는 함수 혹은 변수에 관심이 생겼다.
'state와 구분해서 사용한다면 렌더링 되는 횟수를 줄일 수 있지 않을까?' 같은 의문에서 포스팅을 시작했던 것 같은데..
어떤 변수 혹은 함수를 state로 관리하지 않을 수 있을까? 모든 것을 state로 관리하지는 않아도 될텐데.
일단 index.js에서 React.StrictMode를 삭제한다. 그렇지 않으면 두번 실행되어서 값에도 영향을 미치게 된다.
//src/index.js
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
.
//App.jsx
import { Fragment, useState } from "react";
import Main from "./Components/Main";
console.log("App 컴포넌트");
export default function App() {
const [number, setNumber] = useState(1);
console.log(number, "inApp");
const handleButton = () => {
console.log(number, "handleButton");
setNumber((prev) => prev + 1);
};
return (
<Fragment>
<button onClick={handleButton}>버튼</button>
<Main />
</Fragment>
);
}
.
//Main.jsx
import { Fragment } from "react";
//1번
let x = 1;
console.log("x", x);
function Main() {
//2번
let y = 10;
console.log(`렌더링 외부 변수 x:${x} 내부 변수 y:${y}`);
return (
<Fragment>
<div>{(x += 1)}</div>
<div>{(y += 1)}</div>
</Fragment>
);
}
export default Main;
1번 위치는 Main 함수 밖으로 아예 빼서 선언하고 사용하는 방법
2번 위치는 Main 함수 안에서 선언하고 사용하는 방법
*내가 생각하는 예상 시나리오*
1. 처음 화면이 렌더링 되었을 때 값 자체는 number는 1, x는 1, y 는 10, 화면에 보여지는 x는 2, y는 11
2. 버튼을 클릭해서 화면이 리-렌더링 되었을 때 number는 2, x는 2, y 는 10, 화면에 보여지는 x는 3, y는 11
실행해보니 1번 위치는 파일이 한번 생성되면, 그 이후에는 렌더링 되어도 다시 실행되지 않는다. 'x 1'과 'App 컴포넌트'에 해당하는 출력이 한번 등장한 것 외에 또 등장하지 않는다.
그렇지만 2번 위치는 리-렌더링이 발생하면 다시 실행된다. 무조건 함수를 다시 실행하기 때문인데, 결국 마지막 return문까지 재실행 된다.
그렇다면 1번 위치는 렌더링에 영향을 받지 않는 변수(또는 함수)를 따로 빼주면 좋을 것 같고 2번 위치는 렌더링 될 때마다 재실행 되기 때문에 그때마다 초기화가 필요한 변수(또는 함수), 아니면 리-렌더링되었을 때 초기화되는 state와 연관된 .. 변수또는 함수가 포함되어야 할 것 같다.
두번째 의문
그러면 함수, 객체도 다시 생성되는 걸까?
//Main.jsx
import { Fragment } from "react";
//1번
let x = 1;
console.log("x", x);
let prevFunc = null;
let prevObj = null;
function Main() {
//2번
let y = 10;
const z = {};
console.log(`렌더링 외부 변수 x:${x} 내부 변수 y:${y}`);
const print = () => {
console.log("print");
};
console.log("비교 prevFunc===print: ", prevFunc === print, prevFunc, print);
prevFunc = print;
console.log("비교 prevObj===z: ", prevObj === z, prevObj, z);
prevObj = z;
return (
<Fragment>
<div>{(x += 1)}</div>
<div>{(y += 1)}</div>
</Fragment>
);
}
export default Main;
Primitive type이었기 때문에 주소값 비교가 가능했는데, 함수는 동일한 기능을 하고 마찬가지로 객체도 객체끼리는 동일하지만, 비교문에서는 둘다 모두 false이다.
그러면 useMemo와 useCallback을 좀 이해할 수 있을 것 같다.. 이젠 이해 해야해
1. useMemo
useMemo는 함수를 실행 후 그 return된 값을 재사용한다. 계산이 복잡한 경우에 보통 쓰인다.
1번 위치에 함수를 선언했다고 하더라도, getFunc()를 통해 함수값을 다시 사용한다면 결국 연산 시간에 영향을 미칠 수 있다. 만약 해당 함수가 state에 영향을 받고 있다고 한다면 useMemo를 통해 값을 재사용 할 수 있다.
봉-인! 아직 안써요!
느낌임.
2. useCallback
useCallback은 state가 바뀌었을 때만 함수를 생성한다.
렌더링을 최적화 하기 위해 useCallback을 써서 함수를 다시 생성한다
이.. 설명은.. 좀 이상한.. 느낌인데,
'왜 최적화 하기 위해 함수를 다시 생성하지?!'
아니 최적화.. 다시 생성하지 않으려면 그냥 1번 위치에 함수를 선언하면 안되나?
라고 생각했는데. 보면 state가 바뀌었을 때 함수를 생성한다 는 설명이 있다.
그러면 이 함수는 state에 영향을 받는 함수라는 의미이고, state는 Main() 함수 안에서만 선언, 할당이 가능하다.
import { useState } from "react";
function Main(){
const [state, setState] = useState(1)
const exFunc = () => {
setState((prev)=>prev+1)
}
return(<div>{state}</div>)
}
export default Main;
아래는 불가능. 'setState' is not defined.' ESLint 에러를 확인할 수 있다.
import { useState } from "react";
const exFunc = () => {
setState((prev)=>prev+1)
}
function Main(){
const [state, setState] = useState(1)
return(<div>{state}</div>)
}
export default Main;
그러면 Main 함수 안에 함수를 선언하고 useCallback을 사용한다면 결과는 어떻게 될까
import { useCallback, useState } from "react";
let prevFunc = null;
function Main() {
const [state, setState] = useState(1);
const exFunc = useCallback(() => {
setState((prev) => prev + 1);
}, []);
console.log("prevFunc===exFunc?", prevFunc === exFunc, prevFunc, exFunc);
prevFunc = exFunc;
return <div>{state}</div>;
}
export default Main;
위에서 실험한 내용을 포함해서 생각해보면 일단 1번 위치에서 선언할 수 없는 경우 useCallback을 해주면 함수를 다시 생성해서 할당하지 않는다.
'렌더링을 최적화 하기 위해 useCallback을 써서 함수를 재사용한다.'가 더 내가 이해하기 쉬운 표현인 것 같다.
1번 위치에서 선언할 수 있는 함수인 경우, useMemo를 사용해 결괏값을 재사용할 수 있고
1번 위치에서 선언할 수 없는 함수인 경우, useCallback을 사용해 함수를 재사용할 수 있다.
(1) 최대한 함수 바깥으로 뺄 수 있는지 생각하기
(2) 바깥으로 빼지 못하면 재생성을 막는 방법 생각하기 => 재사용하는 방법 (useCallback)
(3) 함수 호출시 재호출을 막는 방법 생각하기 => 반환값을 재사용 하는 방법 (useMemo)
이렇게 일단 정리해볼 수 있을 것 같다.
아직 잘 이해하지 못한건, useCallback에서 의존성 배열이 들어가는 경우 함수를 재생성하는 부분인데.. 좀 더 생각해봐야 할 것 같다.
'Front-End > React.js' 카테고리의 다른 글
state 란? 무엇인가 . . .🙄 (0) | 2023.03.22 |
---|---|
React.memo 실험 (0) | 2023.03.17 |
useState의 setState((prev)=> !prev) (0) | 2023.03.14 |
커스텀 훅 (0) | 2023.03.14 |
프로젝트 디렉토리 구조 (0) | 2023.03.13 |