Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
리액트의 새로운 기능, Hooks 알아보기 (OLD)

React Hooks 는 v16.8 에 도입된 개념으로서, 함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 그리고 렌더링 직후 작업을 설정하는 useEffect 등의 기능을 제공합니다. 이에 대하여 한번 자세히 알아봅시다.

React Hook 이 2019년 2월 6일 v16.8 버전에 정식 탑재되었습니다!

프로젝트 준비

지금은 이 기능을 사용하시려면 리액트 v16.8 이상 버전을 사용하셔야 합니다. 이번 튜토리얼에서는 CRA 를 통해서 리액트 프로젝트를 생성하겠습니다.

$ npx create-react-app react-hooks-sample
$ cd react-hooks-sample

혹은, 다음 샌드박스를 쓰셔도 됩니다:

Edit react-hooks

State Hook: useState

State Hook 은 함수형 컴포넌트에서 변화 할 수 있는 상태를 사용 할 수 있게 해줍니다.

카운터 구현하기

한번 useState 라는 Hook 함수를 사용해서 카운터를 함수형 컴포넌트로 구현해보겠습니다.

Counter.js 라는 파일을 만들어서 다음 코드를 작성해보세요.

src/Counter.js

import React, { useState } from 'react';

const Counter = () => {
  const [value, setValue] = useState(0);

  return (
    <div>
      <p>
        <b>{value}</b>번 누르셨습니다.
      </p>
      <button onClick={() => setValue(value + 1)}>+1</button>
      <button onClick={() => setValue(value - 1)}>-1</button>
    </div>
  );
};

export default Counter;

useState 함수의 파라미터로는 사용하고 싶은 상태의 기본값을 넣어줍니다. 우리는 현재 0을 기본값으로 사용하고 있습니다. useState 를 호출하면 배열을 반환하는데, 이 배열의 첫번째 원소는 현재 상태 값과, 두번째 원소는 이 값을 설정해주는 setter 함수입니다.

이제 App 에서 렌더링해볼까요?

src/App.js

import React, { Component } from 'react';
import Counter from './Counter';

class App extends Component {
  render() {
    return (
      <div>
        <Counter />
      </div>
    );
  }
}

export default App;

image.png

Edit react-hooks

폼 구현하기

이번에는 useState 를 사용해서 여러개의 상태를 관리해야 할 땐 어떻게 해야 하는지 볼까요?

Form.js 컴포넌트를 다음과 같이 만들어보세요:

Form.js

import React, { useState } from 'react';

const Form = () => {
  const [name, setName] = useState('');
  const [description, setDescription] = useState('');

  const onSubmit = e => {
    e.preventDefault();
    alert(`${name} (${description})`);
    setName('');
    setDescription('');
  };

  return (
    <form onSubmit={onSubmit}>
      <input
        placeholder="이름"
        value={name}
        onChange={e => setName(e.target.value)}
      />
      <input
        placeholder="설명"
        value={description}
        onChange={e => setDescription(e.target.value)}
      />
      <button type="submit">확인</button>
    </form>
  );
};

export default Form;

다 만드셨으면 App.js 에서 렌더링 하세요.

import React, { Component } from 'react';
import Form from './Form';

class App extends Component {
  render() {
    return (
      <div>
        <Form />
      </div>
    );
  }
}

export default App;

image.png

Edit react-hooks

여러 유용한 HOC 함수들이 들어있는 recompose 의 withState 를 사용해본 분이시라면, 사용방식이 매우 유사함을 느끼셨을 것 입니다. HOC 를 사용해서 구현하는 것과 비교했을때 주요 차이점은 완전히 함수형 컴포넌트 라는 것 입니다. HOC 를 사용할땐 결국 HOC 에서 클래스형 컴포넌트를 생성했죠. 하지만, Hooks 는 클래스형 컴포넌트로 한번 감싸는게 아니라, 온전한 함수형 컴포넌트에 기능을 부여해줍니다.

Effect Hook: useEffect

이번엔 useEffect 라는 함수에 대하여 알아봅시다. 이 함수는, 컴포넌트가 마운트 되거나 리렌더링이 마치고 나서 실행됩니다. componentDidMountcomponentDidUpdate 와 비슷하다고 생각하시면 됩니다.

아까 전에 만들었떤 Counter 컴포넌트에서 useEffect 함수를 사용해보겠습니다:

src/Counter.js

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

const Counter = () => {
  const [value, setValue] = useState(0);

  useEffect(() => {
    // 이 함수는 render 가 마치고 난 다음에 실행됩니다!
    console.log('rendered:', value);
  });

  console.log('rendering: ', value);
  return (
    <div>
      <p>
        <b>{value}</b>번 누르셨습니다.
      </p>
      <button onClick={() => setValue(value + 1)}>+1</button>
      <button onClick={() => setValue(value - 1)}>-1</button>
    </div>
  );
};

export default Counter;

src/App.js

다시, Counter 를 App 에서 렌더링하세요:

import React, { Component } from 'react';
import Counter from './Counter';

class App extends Component {
  render() {
    return (
      <div>
        <Counter />
      </div>
    );
  }
}

export default App;

image.pngEdit react-hooks

잘 알아두세요. useEffect 에 넣은 함수는 컴포넌트가 render 를 마친 다음에 실행됩니다. 그러면, 이걸 사용해서 어떤 작업을 할 수 있을까요?

가끔씩은, 우리가 똑같은 작업을 componentDidMount 와 componentDidUpdate 에서 구현해야 할 때가 있습니다. 예를들어서 velog 에서 포스트 하단에 뜨는 다른 포스트를 누르면 같은 종류의 페이지에서 url 만 바뀌게 됩니다.

image.png

Before: /@velopert/eslint-and-prettier-in-react
After: /@velopert/react-component-styling

때문에 컴포넌트가 언마운트 -> 마운트를 거치는 것이 아니라, 주소를 가르키는 props 만 업데이트가 되는데요, 이에 따라 포스트를 새로 불러와야 하기에, 이런 로직이 구현되어있습니다:

  componentDidMount() {
    this.initialize();
    const { hash } = this.props.location;
    if (hash !== '') {
      PostsActions.activateHeading(decodeURI(hash.split('#')[1]));
    }
  }
  
  componentDidUpdate(prevProps, prevState) {
    if (prevProps.urlSlug !== this.props.urlSlug) {
      PostsActions.unloadPost();
      this.initialize();
    }
  }

컴포넌트가 새로 마운트 될 때에도 this.initialize 를 호출하고, 업데이트 될 때에도 urlSlug 부분이 바뀌면 this.initialize 를 호출하고있죠. useEffect 를 사용하면 이러한 중복 로직을 해결 해줄 수 있습니다. 물론, 함수형 컴포넌트여야 사용 할 수 있지만요.

useEffect 를 사용 하실 때 주의하실 점은 우리가 설정해준 함수가 render가 될 때마다 실행된다는 점 입니다. 즉, props 나 state 가 바뀌지 않고 부모컴포넌트가 리렌더링 될 때에도 호출이 됩니다. 만약에 특정 상황에만 이 함수가 실행되게끔 하고 싶다면, useEffect 의 두번째 파라미터로 주시하고 싶은 값들을 배열 형태로 전달해주면 됩니다.

  useEffect(() => {
    // 이 함수는 render 가 마치고 난 다음에 실행됩니다!
    console.log('rendered:', value);
  }, [value]);

이렇게 하면, value 값이 바뀔 때만 useEffect 가 호출됩니다.

Hooks 의 사용 규칙

리액트 매뉴얼에서는 Hooks 사용에 있어서 두가지 준수해야 할 규칙을 규정하였습니다.

1. Hooks 를 컴포넌트의 Top-level 에서만 사용 할 것

Hooks 는 반복문이나, 조건문이나, 감싸진 함수에선 사용하면 안됩니다.

2. 리액트 함수에서만 사용 할 것

Hooks 는 리액트 함수형 컴포넌트 내부에서만 사용하셔야 합니다. 일반 JavaScript 함수에서는 사용하면 안됩니다. 하지만, Custom Hook 에서는 괜찮습니다. (이에 대해선 아래 섹션에서 알아보게 됩니다.)

위 규칙을 준수하기 위해서 ESLint 플러그인 도 만들어졌습니다. 아마 이 플러그인은 Hooks 가 공식 릴리즈가 된다면 CRA 로 만든 프로젝트에 자동으로 적용 될 것으로 보입니다.

Custom Hook 만들기

우리가 방금 배운 useState 와 useEffect 를 활용하면, 정말 다양한 작업들을 할 수 있습니다. 그리고 재사용 되는 로직들은 우리가 따로 Custom Hook 으로 만들어서 우리들만의 Hook 을 만들어서 사용 할 수있습니다.

한번 연습삼아 axios 로 웹 요청을 하는 Hook 을 만들어볼까요?

우선 axios 를 설치하세요.

$ yarn add axios

그리고 src 디렉토리에 hooks 디렉토리를 만들어서 우리들의 Custom Hook useRequest 를 작성해보겠습니다. 주석들을 꼼꼼히 읽어보면서 Hook 을 만들어보세요!

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

function useRequest(url) {
  // loading, response, error 값을 다루는 hooks
  const [loading, setLoading] = useState(false);
  const [response, setResponse] = useState(null);
  const [error, setError] = useState(null);

  // 렌더링 될 때, 그리고 url 이 바뀔때만 실행됨
  useEffect(
    async () => {
      setError(null); // 에러 null 처리
      try {
        setLoading(true); // 로딩중
        const res = await axios.get(url); // 실제 요청
        setResponse(res); // response 설정
      } catch (e) {
        setError(e); // error 설정
      }
      setLoading(false); // 로딩 끝
    },
    [url] // url 이 바뀔때만 실행됨
  );
  return [response, loading, error]; // 현재 값들을 배열로 반환
}

export default useRequest;

이제 useRequest 를 사용하는 컴포넌트를 작성해볼까요? 연습용 API 는 JSONPlaceholder 에서 제공되는 API 들을 사용하겠습니다.

src/Post.js

import React from 'react';
import useRequest from './hooks/useRequest';

const Post = () => {
  const [response, loading, error] = useRequest(
    'https://jsonplaceholder.typicode.com/posts/1'
  );

  if (loading) {
    return <div>로딩중..</div>;
  }

  if (error) {
    return <div>에러 발생!</div>;
  }

  /*
    컴포넌트가 가장 처음 마운트 되는 시점은, Request 가 시작되지 않았으므로
    loading 이 false 이면서 response 도 null 이기에
    response null 체킹 필요 
  */
  if (!response) return null;

  const { title, body } = response.data;

  return (
    <div>
      <h1>{title}</h1>
      <p>{body}</p>
    </div>
  );
};

export default Post;

loaindg.gif

Edit react-hooks

와우~ HOC 나 Render Props 로 할 수 있는 것들을, Hook 으로도 할 수 있게 되었습니다. Hook 을 사용하면 정말 다양한 것들을 할 수 있습니다.

그 외의 React 내장 Hooks

리액트 라이브러리에는 방금 봤던 useState, useEffect 말고도 다른 Hooks 들이 내장되어있습니다.

내장 Hooks 에 대한 상세한 정보는 여기 에서 확인 할 수있습니다.

그 중에서 정말 유용할 것 같은 Hook 몇가지만 소개하고 나머지는 추후에 정식 릴리즈가 되면 다시 정리하도록 하겠습니다.

useContext

const context = useContext(Context);

useContext 는 Context API 를 Hook 을 통해 사용 할 수 있게 해줍니다. Render Props 보다, HOC 를 사용하는 것 보다, 혹은 contextType 을 사용하는 것 보다 훨씬 편하게 느껴지는 군요!

Hooks 가 정식 릴리즈가 되면 아마 저는 앞으로 Context 를 사용 할 떄 Hook 으로 사용 할 것 같습니다.

useReducer

useReducer 는 리덕스에서 리듀서를 사용하는 것과 유사한 방식으로 컴포넌트 상태 관리를 할 수 있게 해줍니다. 그렇다고 해서 이 Hook을 사용하기위해 사전에 리덕스를 설치해야 하는건 아닙니다. 그냥 컴포넌트내부에서 사용 할 수 있는 리듀서라고 생각하시면 됩니다.

useRef

useRef 는 함수형 컴포넌트에서도 ref 를 사용 할 수 있게 해주는 Hook 입니다.

정리

React 의 Hooks 가 정식 릴리즈가 되면 다시 한번 리액트 생태계에 큰 바람이 불게 될 것 같습니다. 재사용 되는 로직을 따로 분리시키는 방법이 앞으로 구현 방식에 있어서 선택사항이 정말 많아졌네요. HOC, Render Props, 그리고 대망의 Hooks!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.