React Hooks - useState

박정호·2022년 8월 30일
0

React Hook

목록 보기
1/12
post-thumbnail
post-custom-banner

🪝 Hook이란?

Hook을 통해 기존 Class 바탕의 코드를 작성할 필요 없이 상태값과 여러 React의 기능을 사용 가능하다.
함수형 컴포넌트는 렌더가 필요할 때마다 할때마다 함수를 다시 호출한다. 원하는 기능을 함수로 만든 후 필요한 곳에 넣어주기만하면 되기 때문에 로직의 재사용이 유연해진다. 따라서, 클래스형 컴포넌트가 가지고 있는 복잡성, 재사용성의 단점들이 해결된다.

함수형 컴포넌트 = props를 인자로 받아서 JSX문법에 맞는 리액트 컴포넌트를 리턴하는 것. (렌더링 = 함수호출)

Hook 도입 목적

  1. Hook을 활용하면 사앹 관련 로직을 추상화해 독립적인 테스트와 재사용이 가능해 레이어 변화 없이 재사용 가능
  2. 기존의 라이프사이클 메서드 기반이 아닌 로직 기반으로 나눌 수 있어서 컴포넌트를 함수 단위로 쪼갤 수 있다는 이점 존재

📎 useState

useState은 호출되면 비구조화할당을 통해 배열을 반환하고 배열 안에 첫번째 원소로 상태값, 두번째 원소로 상태를 업데이트하는 함수이다. setState를 return하면서 초기값을 설정하는 hook이다.

  • state: 렌더링을 일으키는 변수, 컴포넌트가 가질 수 있는 상태
  • setState: state를 변경시켜주는 함수
const [state, setState] = useState(초기값)

👉 useState & 클로저

클래스형 컴포넌트는 render()를 통해 상태 변경을 감지한다. 하지만, 함수형 컴포넌트는 렌더링이 발생하면 함수 자체가 다시 호출된다. 따라서 상태를 관리하려면 함수가 다시 호출되었을 때 이전 상태를 기억하고 있어야 한다.
따라서 useState는 이 점을 상태 관리 문제를 클로저를 통해 해결한다.
함수형 컴포넌트도 결국 함수이기 때문에, 클로저를 통해 선언되는 시점에 접근 가능했던 외부 상태값에 계속 접근하여 상태를 기억하는 것이다.

외부 상태값에 접근? Why?
함수는 호출이 끝나면 리턴되어 더 이상 변경할 수 없는 값이며, 다시 랜더링(=함수호출)하여도 이 전의 값을 참조할 수 없다. 그럼 setState로 상태값에 변화를 주고 싶어도 함수호출이 끝나면 다시 원래 상태로 돌아올 것이다.

그래서 어떻게? So?
state값을 몇가지 규칙하에 useState메서드 외부에 배열 형식으로 저장하는 방법을 선택한다. 다시말해 useState를 통해 생성한 상태를 접근하고 유지하기 위해서는 useSate메서드의 바깥에 state를 저장해야 한다. 따라서, useState함수안에서 선언되는 상태들은 외부의 배열에 '순서대로' 저장되며, 랜더링을 하더라도 외부의 변수에 접근이 가능한 것이다.

➡️ 😃 이와 같이 클로저의 개념을 사용하여 현재 상태를 유지하면서 최신 상태를 유지할 수 있다는 것을 알 수 있다.

잠깐) 몇가지 규칙?

최상위(at the Top Level)에서만 Hook을 호출해야 합니다
반복문, 조건문 혹은 중첩된 함수 내에서 Hook을 호출하지 마세요. 대신 early return이 실행되기 전에 항상 React 함수의 최상위(at the top level)에서 Hook을 호출해야 합니다. 이 규칙을 따르면 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장됩니다. 이러한 점은 React가 useState 와 useEffect 가 여러 번 호출되는 중에도 Hook의 상태를 올바르게 유지할 수 있도록 해줍니다.

오직 React 함수 내에서 Hook을 호출해야 합니다
Hook을 일반적인 JavaScript 함수에서 호출하지 마세요. 대신 아래와 같이 호출할 수 있습니다.

✅ React 함수 컴포넌트에서 Hook을 호출하세요.
✅ Custom Hook에서 Hook을 호출하세요.
이 규칙을 지키면 컴포넌트의 모든 상태 관련 로직을 소스코드에서 명확하게 보이도록 할 수 있습니다.

출처: react 공식 사이트

💡 잠깐) 클로저란?
내부 함수에서 상위함수 스코프의 변수의 접근하는 개념
쉽게 말해 코드 순서가 바뀌어도 변수를 점깐 저장(capture)하였다가 필요한 곳에 전달해주는 것이다.

ex)
일반적으로 함수가 반환하고 나면 그 함수 내에 선언된 지역 변수들은 메모리 상에서 사라지지만, 위 예시에서 foo라는 지역 변수는 useFoo() 함수가 반환하고 나서도 클로저라는 별도의 메모리 공간에 남아있게 된다. 이는 useFoo() 함수가 반환하는 getFoo() 함수와 setFoo() 함수가 그 지역 변수를 사용하기 때문이다. 이러한 경우 foo라는 지역 변수가 getFoo() 함수와 setFoo() 함수에 의해 붙잡혔다고 표현하기도 한다.
참조: https://it-eldorado.tistory.com/155

function useFoo() {
    let foo = 0;

    function getFoo() {
        return foo;
    }

    function setFoo(value) {
        foo = value;
    }

    return [getFoo, setFoo];
}

const [getFoo, setFoo] = useFoo();

console.log(getFoo());  // print 0

setFoo(1);
console.log(getFoo());  // print 1

상세내용

👉 useState = Async

useState의 상태값은 다음 렌더링까지 변하지 않는다. 왜냐하면 렌더링은 상태값의 변경에 따라 일어나기 때문이다.

하지만 만약 수많은 상태값이 존재하여 각각 변경마다 화면이 리렌더링이되면 문제가 발생할 것이다. 따라서, 리액트에서는 setState를 연속으로 호출하면 setState를 모두 batch(취합)하여 한번에 렌더링하도록 한다.

다시 말해 setState가 비동기로 작동하는 이유는 렌더링 횟수를 줄여 더 빠르게 동작하기 위해서이다.(= 성능 최적화)

✏️ 비동기 처리가 된다는 것을 가장 대표적인 count 예시로 알아보자.

setState 비동기 처리👎

아래의 코드의 경우 count라는 상태값에 1이 더해지고 1.5가 더해지는 차례를 예상할 수 있지만, 그렇지 않다.
setState는 앞서 말했듯이 변경사항에 대해 일괄처리한다. 결국 마지막 명령만을 수행하는 것이다. 따라서, 1이 더해지는 것에 대한 랜더링은 일어나지 않고 1.5가 더해지는 것에 대한 랜더링만 일어나는 것이다.

const Counter1 = () => {
    const [count, setCount] = useState(0);

    const onClick = () => {
        setCount(count+1); //실행 X
        console.log(count);

        setCount(count+ 1.5); //실행 O
        console.log(count);
    }

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={onClick}>+</button>
        </div>
    );
}

✏️ 그렇다면 어떻게 실시간으로 setState 업데이트를 할 수 있을까? 방법은 2가지가 있다.

useEffect👍

useEffect의 deps([ ]) 빈 배열 안의 요소를 이용해 상태 값을 바로 업데이트하는 방법이다.

💡 useEffect?
Go -> https://velog.io/@pjh1011409/React-Hooks-useEffect

함수형 업데이트👍

값을 바로 전달하지 않고 함수를 통해서 전달하는 것이다.
여러번 전달받는 함수들은 큐에 저장되어 순서대로 실행하고, 큐에서
setState의 매개변수로는 update와 callback이 들어올 수 있다.

setState(updater, [callback])

함수의 매개변수로는 기존의 값(count)가 들어가고, 함수의 결과값으로 변동될 요소를 넣어주면 실시간으로 state가 최신값을 유지하는 것이다. updater함수를 전달하면 updater함수 안에 존재하는 이전 state값에 접근 가능하기 때문이다.

 const onClick = () => {
        setCount((count) => count+1); //실행 O
        console.log(count);

        setCount((count) => count+ 1.5); //실행 O
        console.log(count);
    }

✏️ 함수형 업데이트의 동작 과정

  • 콜백함수(C)를 통해 값이 유지되어 누적되는 것을 확인 가능

A : 이번 렌더링 초기(시작) state 값 (=초기상태값)
B : A 또는 이번 렌더링에서 업데이트된 값 / 다음 렌더링에 사용될 값 (= updater)
C : 함수형 업데이트를 통해 생겨난 값 (= callback)

// A = 0
// B = A = 0
setValue(prev => prev+1)
// ①-1 : C = B + 1 => 1 
// ①-2 : C 를 B 에 저장

setValue(prev => prev+1)
// ②-1 : C = B + 1 => 2
// ②-2 : C 를 B 에 저장

setValue(prev => prev+1)
// ③-1 : C = B + 1 => 3
// ③-2 : C 를 B 에 저장

✏️ 서로 다른 useState에 대한 함수형 업데이트

💡 잠깐) Rendering?

컴포넌트가 현재 props와 state의 상태에 기초하여 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업을 의미

자세한 내용: https://yceffort.kr/2022/04/deep-dive-in-react-rendering

😸 정리

🗣 useState와 closure의 연관성에 대해서 설명?

👦: useState는 closure의 원리를 통해 최신 상태값을 유지할 수 있습니다.
useState는 함수형 컴포넌트에서 사용되는 hook이며, 함수형 컴포넌트는 결국 함수를 뜻하며 , 렌더링이 되어 함수호출이 이루어진 뒤에는 리턴되어 더 이상 변경할 수 없는 값이며, 다시 랜더링(=함수호출)하여도 이 전의 값을 참조할 수 없습니다.
따라서, useState은 closure을 통해 컴포넌트 내부에서 값을 변경하는 것이 아니라 외부에 있는 값을 변경시키기 때문에 외부 변수에 계속 접근해서 이전의 값을 참조(기억)하여 최신상태를 유지할 수 있는 것입니다.
그리고 한가지 주의할 점으로는 외부에 저장되는 상태 정보는 배열 형태로 저장되기 때문에 상태를 변화시키는 hook을 조건문이나 반복문 안에서 사용하면 잘못된 순서의 값을 참조하게 되므로 반복문, 조건문 혹은 중첩된 함수 내에서 hook 호출을 삼가해야합니다.

🗣 useState은 비동기인가, 동기인가?

👦: 비동기적으로 동작합니다. 왜냐하면 수많은 상태값의 변경으로 인한 setState의 수많은 호출을 막아 그 만큼의 렌더링을 방지해야하기 때문입니다. 이는 state의 변경사항을 즉시 반영하지 않고 대기열에 넣은 후 일곽적으로 batch를 함으로써 성능을 향상시킬 수 있습니다.

🗣 그렇다면 어떻게 최신 상태를 유지해야 할까?

👦: useEffect와 함수형 업데이트를 사용하여 최신 상태를 유지할 수 있습니다.
useEffect를 사용하여 컴포넌트가 렌더링될 때마다 setState가 실행되도록 할 수 있고, setState을 통해 바로 값을 전달하는 것이 아닌 함수를 통해 전달하는 것입니다. setState의 매개변수인 updater를 통해 이전 state값에 접근이 가능하기 때문입니다.

참조

쉬운설명: https://www.youtube.com/watch?v=G3qglTF-fFI&list=PLZ5oZ2KmQEYjwhSxjB_74PoU6pmFzgVMO&index=1

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)
post-custom-banner

0개의 댓글