만족

[React] Lazy Initializing를 사용해 최적화 본문

[React] Lazy Initializing를 사용해 최적화

FrontEnd/React Satisfaction 2021. 8. 26. 18:42

import React, { useState } from "react";

//foo는 매우 오래 걸리는 함수의 예이다.
const foo = () => {
  console.log("foo is called");
  console.time("foo");
  //약 0.2~0.8초 소요
  for (let i = 0; i < 1000000000; i++) {}
  console.timeEnd("foo");

  return 1;
};
const Test = () => {
  //value라는 state는 foo()의 값으로 초기값이 결정된다
  const [value, setValue] = useState(foo());
  const [cnt, setCnt] = useState(0);
  return (
    <div>
      <p>value is {value}</p>
      <button onClick={() => setCnt(cnt + 1)}>increase cnt ({cnt})</button>
    </div>
  );
};

export default Test;

이 코드를 가지고 알아보자.

 

우선 위 코드는 어떤 오래 걸리는 함수(foo)가 state의 초기값으로 지정되었을 때의 문제점을 알아보고자 작성되었다.

 

버튼을 누를 때 마다 cnt가 업데이트되어 Test컴포넌트가 리렌더링 될 것이다.

 

우리는 cnt값을 변경시켰으므로, foo()는 다시 호출되지 않을 것이라고 생각하지만 실제로는 그렇지 않다.

 

버튼을 4번 눌렀을 때, 콘솔을 보게 되면

이런 식으로 리렌더링 마다 foo를 계속해서 호출한다.

 

게다가 이 foo는 오래 걸리는 함수이기 때문에, 버튼을 누르고 foo()값을 계산하고 다시 컴포넌트가 렌더되기 전 까지는 화면이 정지한다.

 

lazy initialize 사용

import React, { useState } from "react";

const foo = () => {
  console.log("foo is called");
  console.time("foo");
  //약 0.2~0.8초 소요
  for (let i = 0; i < 1000000000; i++) {}
  console.timeEnd("foo");

  return 1;
};
const Test = () => {
  //const [value, setValue] = useState(foo());
  //state의 초기값을 함수 형태로 전달한다
  const [value, setValue] = useState(()=> foo());
  const [cnt, setCnt] = useState(0);
  return (
    <div>
      <p>value is {value}</p>
      <button onClick={() => setCnt(cnt + 1)}>increase cnt ({cnt})</button>
    </div>
  );
};

export default Test;

value의 초기값을 결정할 때 foo()가 아닌 ()=> foo()를 대신 사용했다.

 

이렇게 하면 어떤 일이 일어날까?

 

다시 한번 버튼을 눌러 cnt를 증가시켜보자.

 

버튼을 5번이나 눌렀지만 foo는 단 한번밖에 실행되지 않았다.

 

따라서 useState의 매개변수로 값 자체가 아닌 함수를 전달하면, 첫 렌더링 시 딱 한번만 실행된다는 것을 알 수 있다.

 

state의 초기값을 지정할 때, 연산이 오래 걸리는 경우 lazy-initializing을 사용하여 불필요한 중복 연산을 제거함으로써 퍼포먼스를 증대시킬 수 있다.

 

useEffect를 사용한다면

import React, { useEffect, useState } from "react";

const foo = () => {
  console.log("foo is called");
  console.time("foo");
  //약 0.2~0.8초 소요
  for (let i = 0; i < 1000000000; i++) {}
  console.timeEnd("foo");

  return 1;
};
const Test = () => {
  const [value, setValue] = useState();
  const [cnt, setCnt] = useState(0);
  useEffect(() => {
    setValue(foo());
  }, []);
  return (
    <div>
      {value ? <p>value is {value}</p> : <p>loading...</p>}
      <button onClick={() => setCnt(cnt + 1)}>increase cnt ({cnt})</button>
    </div>
  );
};

export default Test;

 

이와 같이 초기 상태를 지정하지 않고 useEffect를 통해 첫 렌더링 이후 foo()를 호출한다면 어떻게 될까?

 

첫 렌더링시에만 foo()를 계산한다는 점은 동일하지만 약간의 차이점이 있다.

 

일단 첫 렌더링 때는 value의 값이 undefined가 되므로 value값이 없을 때 로딩을 표시해줘야 한다.

 

이후 useEffect에 전달한 사이드이펙트가 실행되고 foo()의 값이 value로 지정된다.

 

foo()의 실행이 완료되고 value값의 업데이트가 완료되면 그때 value값이 표시된다.

물론 UI가 표시될 뿐, 스크립트는 계속 돌고 있기 때문에 foo가 실행되는 동안 웹과 사용자는 상호작용 할 수는 없다.

 

다만 useEffect(()=>{} ,[])내에서 처리할 경우, value값을 초기화하기 전에

렌더링을 진행하여 사용자에게 어떤 일이 일어나는지를 알릴 수 있다는 차이가 있다.

 

이런 과정이 필요하지 않을 경우 lazy-initializing을 사용하고,

너무 오래 걸리는 경우 사용자에게 무언가 표시해주긴 해야 하므로 useEffect에서 처리한다.

 

 



Comments