React - Hooks (useState, useEffect)

Leo·2021년 2월 28일
1

React

목록 보기
1/5
post-thumbnail

React Hooks

Hook이란?

Hook은 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 “연동(hook into)“할 수 있게 해주는 함수이다. Hook은 class 안에서는 동작하지 않는 대신에 class 없이 React를 사용할 수 있게 해주는 것이다.

Hook 사용 규칙

최상위(at the Top Level)에서만 Hook을 호출

import React, { useState } from "react"

function Component(props) {
  const [state, setState] = useState(init);			// (o)
  if (...) { const [state, setState] = useState(init); }	// (x)
}

컴포넌트가 렌더링될 때마다 항상 동일한 순서로 호출되는 것을 보장하기 위해서 조건문이나 반복문, 중첩된 함수내에서 Hook을 호출하면 안된다. 그 대신에 컴포넌트 내 최상단에서만 Hook을 호출하도록 하자.

오직 React 함수 내에서 Hook을 호출

Hook을 일반적인 javascript 함수 내에서 호출하면 안된다. 그 대신 다음 2가지 경우에는 가능하다.

  • React 함수 컴포넌트에서 호출 가능
  • Custom Hook에서 호출 가능

State Hook (useState)

useState는 현재 state 값과 이를 업데이트하는 함수를 쌍으로 제공한다. 이 함수는 event handler나 다른 곳에서 호출할 수 있다. useState는 React classsetState와 거의 동일하지만 이전 state와 새로운 state가 합쳐지지 않는 차이가 있다.

import React, { useState } from 'react';

function App(props) {
  const [state, setState] = useState(initialState);
  const [count, setCount] = useState(0);	// ex. 초기값이 0인 state의 count 변수
  return (...);
}

useState의 인자(initialState)는 최초 렌더링 과정에서 state의 초기값으로 지정된다. setStatestate를 갱신할 때 사용되고 새 state 값을 받아서 컴포넌트의 리렌더링을 큐에 등록한다.

Q. useState는 어떻게 상태를 저장하는가?
A. 일단 함수는 실행이 완료되면 함수 내에서 사용했던 메모리를 정리(GC)하기 때문에 상태를 저장하지 못한다. 이 때문에 React Hooks 이전에는 클래스 컴포넌트를 사용할 수 밖에 없었다.
함수형 컴포넌트는 Hooks를 사용하면서 상태 관리가 가능해졌다. Hooks에서 state를 저장하려면 useState()를 사용한다. useState 역시 함수이며 클로저를 이용해 변수를 저장한다.

// 간략한 useState의 내부 구조
const useState = (init = undefined) => {
  let value = init
  const getter = () => value
  const setter = next => (value = next)
  return [getter, setter]
}
const [state, setState] = useState('클로저')

초기값을 받아 내부의 지역변수 value에 할당한다. 내부 함수 getter()는 지역변수 init을 바라보고 있다. 또 다른 내부 함수 setter()next라는 인자를 받아 value의 값을 수정한다. 이후 다시 getter()를 호출하게 되면 변경된 value의 값을 호출하게 된다.
두 함수는 배열 형태로 리턴되고 useState를 사용할 때는 배열 구조분해 할당 형태로 많이 사용하게 된다. 일반적인 함수라면 return과 함께 실행이 종료되고 나서 내부의 데이터들이 가비지 컬렉팅 되어야 하겠지만, 이 경우 내부 함수가 지역 변수를 참조하고 있으므로 사라지지 않는다.
또한 외부로 노출된 getter, setter 함수를 통해 내부 변수에 지속적으로 접근하며 호출/재할당을 할 수 있다. 이는 클래스형 컴포넌트에서 state가 해온 역할과 동일하다.

Effect Hook (useEffect)

useEffect는 함수 컴포넌트 내에서 이런 side effects를 수행할 수 있게 해준다. React classcomponentDidMountcomponentDidUpdate, componentWillUnmount와 같은 목적으로 제공되지만, 하나의 API로 통합된 것이다.

import React, { useEffect } from 'react';

function App(props) {
  useEffect(() => {
    // effect content
    return (() => {
    // clean up할 content === React class의 componentWillUnmount
    });
  }, [...]);	// 두 번째 인자인 [] 내부에 작성된 state가 변경될 때만 함수(첫 번째 인자)를 실행
  return (...);
}

React는 우리가 넘긴 함수를 기억했다가(이 함수를 ‘effect’라고 부른다.) DOM 업데이트를 수행한 후에 불러낸다. useEffect는 기본적으로 최초 렌더링과 업데이트할 때마다 수행되는데 effect를 필요에 따라 수정하는 방식으로 다양한 조건부 동작을 수행하도록 변경할 수 있다. 또한 useEffect 내부에서 return 하는 함수는 해당 컴포넌트가 종료되고나서 실행된다. 이는 React class 컴포넌트의 API인 componentWillUnmount와 동일한 기능이다.

기존 LifeCycle API와 비교

class 컴포넌트

import React from "react";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  componentDidMount() {
    console.log(`componentDidMount`);
  }
  componentWillUpdate() {	// Deprecated이지만 비교를 위해서 사용
    console.log(`componentWillUpdate`);
  }
  componentDidUpdate() {
    console.log(`componentDidUpdate`);
  }
  componentWillUnmount() {
    console.log(`componentWillUnmount`)
  }

  render() {
    return (
      <div>
        <h1>Count: {this.state.count}</h1>
        <button onClick={() => {this.setState({ count: this.state.count + 1 })}}>
          Click me
        </button>
      </div>
    );
  }
}

export default App;

function 컴포넌트

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

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(`componentDidMount`);
    return ( () => {
      console.log(`componentWillUnmount`);
    })
  }, []);	// 빈 배열을 두 번째로 넘기면 최초 mount에만 effect 실행

  useEffect(() => {
    console.log(`componentWillUpdate`);
    return ( () => {
      console.log(`componentDidUpdate`);
    })		// 두 번째 인자를 넘기지 않으면 최초 mount와 state가 변경될 때마다 effect 실행
  });
  
  useEffect(() => {
    console.log(`only count componentWillUpdate`);
    return ( () => {
      console.log(`only count componentDidUpdate`);
    })
  }, [count]);	// count가 변경될 때만 effect 재실행
  
  return (
    <div className="App">
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>
        Click me!!
      </button>
    </div>
  );
}

export default App;

참조


0개의 댓글