본문 바로가기
React Study

useState란 무엇일까 (feat. 클로저)

by KMS_99 2024. 3. 21.

 

 

useState는 함수형 컴포넌트에서 상태를 관리하기 위한  react hook

 

 

useState는 리액트 개발을 처음 배울 때 가장 먼저 배우는 개념이라고 할 수 있다.

이러한 useState에 대하여 더 자세하게 알아보면서 개발하면서 느꼈던 의문점들을 해결해보고자 한다. 

 

다음은 클래스형 컴포넌트의 구성 예시이다.

import {Component} from 'react'

interface State{
	count:number
}

class CountComponent extends Component<_,State> {
	private constructor(){
    	this.state={
        	count:1,
        }
    }
    
    private handleClick = () => {
    	const newValue = this.state.count + 1
        this.setState({count:newValue})
    }
    
    public render() {
    	const {state} = this
        
        return (
        	<h2>Count Example</h2>
            <span>{state.count}</span>
            <button type='button' onClick={this.handleClick}>증가</button>
        )
    }
    
}

 

기존 클래스형 컴포넌트에서 this를 통해 state를 선언해야했으며,

LifeCycle에 따라 state가 변경되는 조건이 다양하여 상태 변화를 추적하는 어려움이 있었다.

 

함수형 컴포넌트에서 사용되는 useState는 위와 같은 불편한 점을 해결해준다.

 

다음은 위에서 클래스형 컴포넌트로 구성한 카운터 예시코드를 함수형 컴포넌트 구조로 나타낸 코드이다.

import react, {useState} from 'react'

export default CountComponent () {

	const [count, setCount] = useState<number>(1);
    
    const handleClick=()=>{
    	setCount(prev=>prev+1)
    }
    
    return (
    	<h2>Counter Example</h2>
        <span>{count}</span>
        <button type='button' onClick={handleClick}>증가</button>
    )
}

 

가장 큰 변화는 코드의 길이이다. 

 

클래스형 컴포넌트보다 훨씬 코드 길이가 적어졌으며,

constructor, this 바인딩, private/public 등의 코드가 없기 때문에 가독성이 훨씬 좋아졌다고 할 수 있다.

 

이러한 장점은 결국 함수형 컴포넌트의 hook이 큰 역할을 할 수 있으며, 그 중에서 useState가 어떤식으로 구성되어 있는지 알아보자.

 


1. 클로저

useState의 구성을 알아보기 전 클로저에 대한 내용을 알고 있어야한다.

 

클로저는 함수레벨스코프인 자바스크립트의 특징으로 생성할 수 있다.

 

함수레벨스코프란 es6 이전 변수선언에 사용되던 var 키워드를 통해 변수선언 시 발생하는 특징으로,

함수 내부에서 선언된 변수/함수는 외부에서 참조가 불가능한 상태를 뜻한다.

즉, 변수/함수의 유효범위가 해당 변수/함수가 선언된 함수 내부라는 이야기이다.

function aFunc(){
	var a = 10;
    console.log(a) // 10
}

// (1) aFunc 내부의 변수 a에 접근 불가능
// (2) 전역(window/global)에 선언된 a 변수 확인
console.log(a) // ReferenctError

 

따라서 자바스크립트에서는 함수에서만 유효한 변수를 선언하고 해당 변수를 참조하여 반환(return) 하면서 함수의 실행컨텍스트가 종료된 상황에서도 내부 변수를 참조할 수 있는 일종의 눈속임을 만들 수 있다.

이러한 것을 클로저라고 한다.

 

코드 예시를 통해 다시 알아보자.

 

function counterCloser(){
	var counter = 0
    
    return {
    	increase:function(){
        	return ++counter
        },
    	decrease:function(){
        	return --counter
        },
    	counterState:function(){
        	return counter
        },
    }
}

var c = counterCloser();

console.log(c.counterState()) // 0
console.log(c.increase()) // 1
console.log(c.decrease()) // 0

 

counterCloser 함수 내부에는 counter라는 변수가 있고 return을 통해 3개의 기능을 가진 함수를 객체로 반환하고 있다.

각 기능 함수는 counterCloser 내부에서 선언되었기에 counter 변수를 참조가 가능하다.

 

var c = counterCloser()를 통해서 클로저 함수가 실행되었고 실행과 동시에 return을 통해 실행컨텍스트가 종료되고 기능함수들이 리턴되었다.

 

이런 상황에서 클로저함수 내부의 counter는 return을 통해 참조되고 있기 때문에 메모리상에서 공간이 제거되지 않고 유지된다. 따라서 increase, decrease, counterState 함수를 정상적으로 동작시킬 수는 있지만 직접적으로 변수에 접근은 불가능하다.

또한 이러한 변수는 메모리상에 기억되고 있기 떄문에 값이 유지된다.

 

즉, 클로저함수를 통해 내부 변수가 캡슐화되었다.


2. useState와 클로저

useState hook은 클로저를 사용하는 대표적인 hook이다.

 

다음 코드를 보자.

import react, {useState} from 'react'

export default function component() {
	const [count,setCount] = useState()
    
    //...
}

 

리액트의 함수형 컴포넌트에서 useState를 사용하는 예시이다.

이 예시에서 useState는 최초에만 호출된다.

하지만 우리는 이 코드를 통해 count라는 상태의 최신값을 계속 유지할 수 있다.

 

이 부분에서 클로저가 사용됨을 알 수 있다.

 

useState의 실행이 끝난 상황에서 count 변수의 메모리가 클로저에 의해서 메모리상에 남아있기 때문에 가능한 동작이다.

 

다음 예시코드를 보자

function createUseState() {
  let state; // 상태를 저장할 변수
  
  return (initialValue) => {
    state = state === undefined ? initialValue : state; // 초기 상태 설정
    const setState = (newValue) => {
      state = newValue;
    };
    return [state, setState]; // 현재 상태와 상태를 업데이트하는 함수 반환
  };
}

// 사용 예시
const useState = createUseState();

const [count, setCount] = useState(0);
console.log(count); // 0
setCount(1);

 

이 코드는 재랜더링 메커니즘과 조건을 고려하지 않았다.
또한 useState의 실제 내부코드와 100퍼센트 동일하지 않은 코드이다.
단지 어떤식으로 useState의 구성에 클로저가 사용되는지 알아보기 위한 코드이다.

 

useState 변수에 createUseState() 클로저함수를 실행시키면서 내부 변수인 state를 참조하는 함수를 반환하였다.

 

return 된 함수 내부에서는 입력받은 초기값(initialValue)과의 비교를 통해 클로저를 통해 접근가능한 state 변수에 값을 할당하며, 해당 state 변수를 변경시킬 수 있는 setState setter 함수를 선언한다.

마지막 return을 통해 [state, setState] 형식으로 내부 변수 state에 접근하고 변경시키는게 가능하다.

 

물론 위에서 언급한대로 재랜더링 메커니즘과 조건을 고려하지 않았기에 실제 useState 처럼 상태값의 변경이 바로 적용되지는 않을 것이다.

 

그렇지만 위 코드를 통해 useState가 클로저를 통해 캡슐화된 상태값을 이용할 수 있으며, 해당 상태 값이 클로저를 통해 유지되는 것을 알 수 있다.