본문 바로가기
SPA/REACT

[React] useState의 초깃값 지연

by F.E.D 2021. 1. 1.

useState 및 setState를 많이 사용들하고 계실텐데요.

 

한동안 React로 작업했다면 아마 useState를 사용했을 겁니다.

 

다음은 API의 간단한 예입니다.

 

function Counter() {
  const [count, setCount] = React.useState(0)
  const increment = () => setCount(count + 1)
  return <button onClick={increment}>{count}</button>
}

 

초기 상태 값으로 useState를 호출하면 해당 상태값과 이를 업데이트하기 위한 메커니즘(dispatch 함수)이 포함된 배열이 반환되면서, 상태에 대한 새로운 값을 전달하고 구성 요소를 다시 렌더링하여 useState가 다시 호출되어 새 상태 값과 디스패치 함수를 다시 검색합니다.

 

그러나 가끔 사용할 수 있는 덜 알려진 유용한 기능이 있습니다.

function Counter() {
  const [count, setCount] = React.useState(() => 0)
  const increment = () => setCount(previousCount => previousCount + 1)
  return <button onClick={increment}>{count}</button>
}

이전 예제와 차이점은 ?

useState는 초기상태를 반환하는 함수로 호출되고 setCount(dispatch)는 이전 상태 값을 받이들이고 새 값을 반환하는 함수로 호출된다는 것입니다..

 

useState Lazy 초기화

Counter 함수의 함수 본문에 console.log를 추가하면 버튼을 클릭 할 때마다 함수가 실행되는 것을 알 수 있습니다. 카운터 기능은 모든 렌더링 단계에서 실행되고 버튼을 클릭하면 다시 렌더링을 트리거하는 상태 업데이트가 트리거되기 때문에 이는 의미가 있습니다. 명심해야 할 한 가지는 함수 본문이 실행되면 내부의 모든 코드도 실행된다는 것을 의미합니다. 즉, 생성하는 모든 변수 또는 전달하는 인수가 렌더링 될 때마다 생성되고 평가됩니다. JavaScript 엔진은 매우 빠르며 이러한 종류의 작업에 최적화 할 수 있기 때문에 일반적으로 큰 문제가 아닙니다. 따라서 이와 같은 것은 문제가되지 않습니다.

 

const initialState = 0
const [count, setCount] = React.useState(initialState)

상태의 초기값 비용이 비싸다면 어떨까요?

const initialState = calculateSomethingExpensive(props)
const [count, setCount] = React.useState(initialState)

또는보다 실질적으로 IO 작업 인 localStorage를 읽어야하는 경우 어떻게해야합니까?

const initialState = Number(window.localStorage.getItem('count'))
const [count, setCount] = React.useState(initialState)

React가 초기 상태를 필요로하는 유일한 시간은 첫번째 뿐입니다.

실제로는 첫 번째 렌더링에서 초기 상태만 필요하다는 것을 기억하십시오.

그러나 함수 본문은 구성 요소를 다시 렌더링 할 때마다 실행되기 때문에 값이 사용되지 않거나 필요하지 않더라도 모든 렌더링에서 해당 코드를 실행하게됩니다.

이것이 게으른 초기화의 전부입니다.

이 코드를 함수에 넣을 수 있습니다.

const getInitialState = () => Number(window.localStorage.getItem('count'))
const [count, setCount] = React.useState(getInitialState)

함수가하는 일이 계산적으로 비싸더라도 함수 생성은 빠릅니다. 

따라서 함수를 호출 할 때만 성능 패널티를 지불합니다. 

따라서 useState에 함수를 전달하면 React는 초기 값이 필요할 때만 함수를 호출합니다 (구성 요소가 처음 렌더링 될 때).

 

이를 지연 초기화라고 합니다.

성능최적화 기능이며 많이 사용할 필요가 없고 어떤 특정 상황이므로 자주 사용하는 기능은 아닙니다.

 

dispatch 함수로 업데이트

이것은 조금 더 복잡합니다. 그것이 무엇인지 설명하는 가장 쉬운 방법은 예를 보여주는 것입니다. 

카운트 상태를 업데이트하기 전에 비동기 작업을 수행해야한다고 가정해 보겠습니다.

function DelayedCounter() {
  const [count, setCount] = React.useState(0)
  const increment = async () => {
    await doSomethingAsync()
    setCount(count + 1)
  }
  return <button onClick={increment}>{count}</button>
}

이런식으로 작성한 후에 버튼을 세번 빠르게 클릭하면 매번 카운트가 0인 상태에서 1증가하는 porp을 통해 React에 제공한 증분 함수가 생성 당시의 count 값을 닫아버립니다.

그래서 쉽사리 3으로 증가하지 않습니다.

 

따라서 비동기 작업 전에 다시 렌더링을 트리거 할 수 있다면 이 문제를 해결할 수 있다고 생각할 수 있지만, 그렇게해도 해결되지 않습니다. 이 접근 방식의 문제는 increment가 이 렌더의 count에 액세스 할 수 있지만 다음 렌더를 위해 count 변수에 액세스 할 수 없다는 것입니다. 사실, 다음 렌더링에서 완전히 다른 count 변수에 액세스 할 수있는 완전히 다른 증분 함수를 갖게됩니다. 따라서 우리는 각각 렌더링되는 모든 변수의 두 복사본을 효과적으로 갖게됩니다. (가비지 컬렉션은 일반적으로 복사본을 정리하고 여기에 대해서만 걱정할 필요가 있습니다.) 그러나 카운트가 아직 업데이트되지 않았기 때문에 새로운 증분 복사본이 생성 될 때 카운트 값은 여전히 ​​0입니다. 그래서 첫 번째와 마찬가지로 두 번째 버튼을 클릭하면 0이됩니다.

 

버튼을 다시 클릭하기 전에 카운트 값이 업데이트 될 때까지 기다리면 모든 것이 잘 작동합니다.

이는 다시 렌더링이 발생할 때까지 충분히 기다렸고 다음과 같이 새로운 증분 함수가 생성 되었기 때문입니다.

 

그러나 분명히 이것은 약간 혼란스럽고 또한 약간 문제가 있습니다. 그렇다면 해결책은 무엇입니까? 기능 업데이트! 정말로 필요한 것은 업데이트를 할 때 이전 count 값을 결정하여 이전 count 값을 기반으로 결정할 수있는 방법입니다.

 

이전 상태를 기반으로 새 상태를 계산해야 할 때마다 함수 업데이트를 사용합니다.

 

function DelayedCounter() {
  const [count, setCount] = React.useState(0)
  const increment = async () => {
    await doSomethingAsync()
    setCount(previousCount => previousCount + 1)
  }
  return <button onClick={increment}>{count}</button>
}

원하는만큼 자주 클릭할 수 있으며 클릭 할 때마다 카운트 업데이트를 관리합니다. 

이것은 더 이상 "부실"한 값에 액세스하는 것에 대해 걱정하지않고 대신 필요한 변수의 최신 값에 액세스 할 수 있기 때문에 작동합니다.

따라서 우리가 실행하고있는 증분 함수에 이전 버전의 개수가 있더라도 함수 업데이트 프로그램은 최신 버전의 상태를 수신합니다.

 

useReducer를 사용하는 경우 동일한 문제가 발생하지 않을 것입니다.

왜냐하면 항상 가장 최신 버전의 상태(감속기에 대한 첫 번째 인수로)를 수신하므로 오래된 것에 대해 걱정할 필요가 없습니다.

이에 대한 예외는 상태 업데이트가 props 또는 일부 외부 상태에 의해 결정되는 경우이지만, 이 경우 해당 prop 또는 외부 상태의 최신 버전을 확보하는 데 도움이되도록 useRef가 필요할 수 있습니다.

 

결론적으로 useState 지연 관련해서는 일부 시나리오에서는 성능 문제를 개선하는 데 유용하며, 디스패치 기능 업데이트는 오래된 값 문제를 방지하는데 도움이 됩니다.

 

 

 

 

 

출처 : kentcdodds.com/blog/use-state-lazy-initialization-and-function-updates

댓글