프론트엔드 개발자 - 자곰

Front-end/React

React Hook의 모든 것 (2) - useState를 직접 구현해보자

자곰 2022. 2. 28. 00:23

 useState를 깊게 알아보자

State Hook을 자체적으로 다시 구현하여 내부적으로 어떻게 작동하는지 배우는 것으로 시작하겠습니다. 다음으로 Hook의 몇 가지 제한 사항과 Hook이 존재하는 이유에 대해 알아봅니다. 마지막으로 Hooks의 한계로 인해 발생하는 일반적인 문제를 해결하는 방법을 배웁니다. 최종적으로 React에서 stateful 함수 컴포넌트를 구현하기 위해 State Hook을 사용하는 방법을 알게 될 수 있도록 하겠습니다.

 

useState 구현

Hooks가 내부적으로 어떻게 작동하는지 더 잘 이해하기 위해 Hooks를 다시 구현해보겠습니다.

지금부터 작성하는 코드는 실제 useState를 구현하는게 아니라 개념과 아이디어를 파악하는데 이해를 위한 코드라는 것을 기억해주세요.

import React from 'react'
import ReactDOM from 'react-dom'

function Temp () {
	 //  .........
    function useState (initialState) {
        let value = initialState
        function setState (nextValue) {
        value = nextValue
        ReactDOM.render(<MyName />,
          document.getElementById('root'))
      }
        return [ value, setState ]
    }
    // .........
}

코드에 대해서 간략히 설명해보자면,

먼저, ReactDOM은 Component를 재랜더링하기 위해 필요합니다.

반환값이 객체가 아닌 배열을 사용되는 이유는 일반적으로 value & setState 을 rename하여 사용하기 때문입니다.

  const [name, setName] = useState('')   

정의한 Hook 함수를 보면 값을 저장하기 위해 closure를 사용하고 있습니다.

setState함수는 동일한 closuer 내에서 정의되어 있으며, 이것이 우리가 함수 컴포넌트 내에서 우리가 값에 접근할 수 있는 이유입니다. 또한, useState없이는 해당 값을 직접 접근할 수 없으며 변수 값을 반환받지도 않습니다.

 

위 코드의 문제점 

1. Global Variable

Hook을 실행하게 되면, 컴포넌트가 다시 렌더링될 때 상태가 재설정되어 초기화됩니다. 우리는 렌더링 될때마다 useState를 호출하기 때문에 값이 새로 초기화 되기 때문입니다.

우리는 이것을 global 변수로 해결할 수 있습니다.

value는 useState에 의해 정의된 클로져안에 저장되어있습니다. 컴포넌트가 re-rendering될 때마다, 그 클로저는 다시 초기화됩니다. 그것은 값의 리셋을 의미합니다. 이것을 해결하기 위해, 우리는 값을 함수 외부의 global variable에 저장할 필요가 있습니다.

그 방법은, value는 함수의 밖 클로저에 존재 하게 해주어 랜더링을 다시 해도 초기화가 되지 않도록 해줍니다.

useState 구현과 별 다른 것이 없다. useState밖에 변수를 정의하고 내부 함수에 조건을 조금 추가해주면 끝이다.

let value
function useState (initialState) {
  if (typeof value === 'undefined') value = initialState

이제부터는 우리의 useState 는 전역값 변수를 정의하는 대신 값 변수가 외부 클로저에 있으므로 함수가 다시 호출 되어도 다시 초기화되지 않습니다.

2. 여러 hook을 사용하는 방법

우리는 한 컴포넌트에 여러 hook을 사용해야 하는 경우가 분명히 있습니다. 하지만 위에서 구현한 것은 value 변수 하나만을 이용하기에 여러 hook을 사용하기 어렵습니다. 그럼 어떻게 해야할까 고민하면 답은 [] (배열)입니다. 

리팩토링을 해보겠습니다.

values[currentHook++]을 사용하여,  values배열의 index로 현재 value를 전달 받을 수 있게 되었습니다.

여태까지 useState의 아이디어를 통한 간단한 구현을 하였습니다. 아이디어 개념과 hook을 이해하기 위해 구현했을 뿐 실제 useState와 다르다는 것을 알아주세요.

그럼 이제 여태까지 배운 hook을 통해 알 수 있는 것을 정리 해보겠습니다.

1. hook에는 clouser를 사용했기 때문에 컴포넌트 안 어디서든 사용이 가능하다. 

2. Hook을 정의할 때, 배열을 이용하게 되므로 Index가 들어간다.

Conditional Hook는 정의 가능한가?

위에서 Hook을 정의할 때 인덱스가 들어간다는 것을 배웠습니다. 근데 만약에 조건에 따라 useState가 정의되고 안되고 차이가 있다면, Hook의 index는 순서가 엉망이 될 수 밖에 없습니다. 결국 조건에 따라 useState를 사용하면 안된다는 것이죠

const [ enableFirstName, setEnableFirstName ] = useState(false)
const [ name, setName ] = enableFirstName ? useState(''): [ '', () => {} ] 

 

실제 Hook 비교

Hook을 구현을 해보았지만 이것은 내부적으로 어떻게 동작하는지에 대한 아이디어를 제공할 뿐 실제와는 다릅니다.

먼저, Hooks는 global variable을 사용하지 않습니다. 대신 React Component안에 state를 저장합니다. 또한 내부적으로 Hook카운터를 처리하므로 컴포넌트 내에서 수동으로 재설정할 필요도 없습니다.

두번째, 실제 Hook은 상태가 변경될 때 구성 요소의 재렌더링을 자동으로 트리거 합니다. 그러나 이를 수행하기 위해서는 React 함수 컴포넌트 내에서 Hook을 호출해야 합니다. React Hooks는 React 외부 또는 클래스 컴포넌트에서 사용할 수 없습니다.

결론적으로 useState를 재구현해보면서 우리는 3가지를 배울 수 있었습니다.

  • Hooks는 단순히 React기능에 접근하는 기능입니다.
  • Hooks의 정의 순서는 중요하다
  • Hooks는 재랜더링에 지속되는 side Effect를 처리한다.

특히, 정의 순서는 Hooks를 조건부로 정의 할 수 없음을 의미하기 때문에 중요합니다.

Hook의 일반적인 문제 해결 

일반적인 Hooks를 구현하는 것은 장단점이 있습니다. 이제 React Hooks의 한계에서 비롯된 이러한 문제들을 극복하는 방법들을 알아 보겠습니다.

  • 조건 hooks 해결
  • 루프에서 hooks

1. 조건 Hooks

우리의 컴포넌트를 분할해서 프로젝트를 진행합니다. 따라서 항상 정의하고 필요에 따라 컴포넌트를 불러 Hooks를 사용하게 합니다.

function LoggedInUserInfo ({ username }) {
  const info = useFetchUserInfo(username)
  return <div>{info}</div>
}
function UserInfo ({ username }) {
  if (username) {
      return <LoggedInUserInfo username={username} />
  }
  return <div>Not logged in</div>
}

위 코드에 대해서 설명하자면, 로그인 상태와 아닌 상태에 대해 개별 Component를 사용하였습니다.

이를 통해 조건부 Hook 없는 것이 문제가 되지 않습니다.

2. Hooks in Loop

우리는 간단하게 배열을 통해 해결할 수 있습니다.

function OnlineUsers ({ users }) {
 const [ userInfos, setUserInfos ] = useState([])
 // ... fetch & keep userInfos up to date ...
 return (
     <div>
         {users.map(username => {
             const user = userInfos.find(u => u.username === username)
             return <UserInfo {...user} />
         })}
	</div> )
}

 

문단 마지막 사용

useState을 global state 와 closures를 통해 재구성해보았습니다. 그런 다음 여러 Hook을 구현하려면 대신 state 배열을 사용해야 한다는 것을 배웠습니다. 그러나 state 배열을 사용함으로써 우리는 함수 호출에서 Hook 의 순서를 일관되게 유지해야 했습니다. 이 제한으로 인해 루프에서 조건부 Hooks 및 Hooks가 불가능했습니다. 마지막으로, Hooks의 한계에서 비롯된 일반적인 문제를 해결하는 방법을 배웠습니다.

이제 Hooks의 내부 작동 과 한계에 대해 확실히 이해할 수 있을 것입니다.

 

'Front-end > React' 카테고리의 다른 글

React Hook의 모든 것 (1)  (0) 2022.02.21
React 프로젝트 설정 및 확장성 이야기  (0) 2022.02.15
React Rendering optimization (최적화)  (0) 2022.02.10
React 랜더링 기본  (0) 2022.02.10