본문 바로가기

Web Develop/React

React Hook에 대해 알아보자.(1)

리액트 훅이란 무엇일까?


간단하게 말하면, 함수 컴포넌트에서 state(상태)를 가질 수 있도록 해주는 것이다.

그럼 Hook을 사용하지 않고는 함수 컴포넌트에서 state(상태)를 다룰 수 없을까? Hooks가 나오기 전까지, 함수 컴포넌트에서는 state를 다룰 수 없었다. state(상태)를 다루기 위해서는 클래스 컴포넌트를 사용하여 didmount, didupdate 등 컴포넌트의 생명 주기를 관리해야 했다. 하지만 React Hooks가 나오게 되면서 클래스 컴포넌트에서만 가능했던 것을 함수 컴포넌트에서도 똑같이 가능하도록 된 것이다. 또한 훅을 사용하여 함수형 프로그래밍을 하게 되면서 짧고 가독성도 더 좋은 코드를 작성할 수 있게 되었다.

 

훅의 역사


recompose 라는 라이브러리에서 시작되었다. 함수형 프로그래밍이면서 state를 준다는 점에서 아이디어가 현재의 Hooks와 비슷했다. 이는 이후에 React 팀에 의해 인수되어 recompose와 react가 결합된 react Hook이 만들어진 것이다.

recompose 사용 예시

const enhance = withState('counter', 'setCounter', 0)
const Counter = enhance(({ counter, setCounter }) =>
  <div>
    Count: {counter}
    <button onClick={() => setCounter(n => n + 1)}>Increment</button>
    <button onClick={() => setCounter(n => n - 1)}>Decrement</button>
  </div>
)

※ recompose에 대해 더 자세히 알고싶다면?
https://github.com/acdlite/recompose

 

훅의 종류


기본적인 훅은 React에 내장되어 있다. 아래는 대표적인 훅의 예시이다.

  • State Hooks - 컴포넌트가 사용자 입력과 같은 정보를 기억할 수 있도록 해준다.
    • useState: 직접적으로 업데이트 할 state 변수를 선언한다.
    • useReducer: reducer 함수 안에서 업데이트 로직을 작성한 state 변수를 선언한다.
  • Context Hooks - 컴포넌트가 props로 전달하는 것이 아닌 멀리 떨어진 부모로부터 정보를 받을 수 있다.
    • useContext: context를 읽고 구독한다.
  • Ref Hooks - 컴포넌트가 DOM node 또는 timeout ID와 같은 렌더링에 사용되지 않는 일부 정보를 보유하게 해준다.
    • useRef: ref를 선언한다.
  • Effect Hooks - 컴포넌트가 외부 시스템과 연결하고 동기화할 수 있게 해준다. 네트워크, 브라우저 DOM, 애니메이션, 위젯 등을 다룬다.
    • useEffect: 외부시스템에 컴포넌트를 연결한다.

이 뿐만 아니라 더 다양한 훅이 있으며, 필요에 따라 내장된 훅들을 사용하여 우리가 직접 훅을 만들어 사용할 수도 있고 다른 사람들이 만들어 놓은 훅을 가져다 쓸 수도 있다.

 

훅 사용해보기


1. UseState

const [index, setIndex] = useState(0);

useState는 Array(배열)을 리턴한다. 첫 번째 인덱스는 값을 리턴하고 두 번째 인덱스는 값을 변경하는 함수를 리턴한다. 값이 화면 상에서 변경된다는 것은 재렌더링이 된다는 뜻이다. setIndex를 통해 값이 변경될 때마다 재렌더링하여 화면에 보여준다. 또한 useState 뒤에 0은 초기화 값을 적는다. 참고로 index와 setIndex라는 이름은 자유롭게 지을 수 있다. (일반적으로 두 번째 인덱스에는 set을 붙이고 단어의 첫 번째 알파벳은 대문자로 적는다.)

아래의 예시는 사용자가 숫자 증가 버튼과 감소 버튼을 누를 때 숫자가 화면에서 변경되도록 구현한 코드이다.

일단 1로 초기화한 다음 increment 함수는 setItem을 통해 item이 +1씩 되도록 하였고, decrement 함수는 item이 -1씩 되도록 하였다.

import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const [item, setItem] = useState(1);
  const incrementItem = () => setItem(item + 1);
  const decrementItem = () => setItem(item - 1);

  return (
    <div className="App">
      <h1>Hello {item}</h1>
      <button onClick={incrementItem}>IncrementItem</button>
      <button onClick={decrementItem}>DecrementItem</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

2. UseInput

input 태그에 사용자가 값을 입력할 때 값이 입력될 때마다 그 값을 보여주기 위해 재렌더링을 해야 한다. 이를 useState 훅을 사용하여 useInput이라는 새로운 훅을 직접 만들어 사용하기 편리하게 만들 수 있다. 또한 이렇게 만드는 것의 장점은 단순히 값이 입력되는 값을 보여주는 것뿐만 아니라 유효성 검사와 같은 다른 조건들을 추가하더라도 코드가 복잡해지지 않는다는 점이다.

import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const useInput = (initialValue, validator) => {
    const [value, setValue] = useState(initialValue);
    const onChange = (event) => {
      let willUpdate = true;

      if (typeof validator === "function") {
        willUpdate = validator(event.target.value);
      }
      if (willUpdate) {
        setValue(event.target.value);
      }
    };
    return { value, onChange };
  };

  const maxLen = value => value.length <= 10;
  const name = useInput("Mr.", maxLen);

  return (
    <div className="App">
      <h1>Hello</h1>
      <input placeholder="Name" {...name} />
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

🌟 {…name} 은 무엇을 뜻할까?

{…name}을 풀어서 쓰면 value={name.value} onChange={name.onChange}이다.
…은 Spread syntax라는 문법이다. 이는 배열이나 객체의 모든 요소들을 새로운 배열이나 객체에 포함해야하거나 인수 목록에 하나씩 적용해야할 때 이 문법을 적용하면 간편하다.

3. UseTabs

버튼을 눌렀을 때 그 버튼에 해당하는 내용이 화면에 나타나도록 하는 것을 useState를 사용하여 새로운 훅 useTabs를 만들었다. useTabs의 매개변수는 현재 상태와 데이터 2가지로 구성되며 먼저 데이터가 있는지, 배열인지 확인한 후 현재 인덱스와 상태를 변경시켜주는 setCurrentIndex를 객체로 리턴해준다.

import React, { useState } from "react";
import "./styles.css";

const contents = [
  {
    tab: "section 1",
    content: "I'm the content of Section 1"
  },
  {
    tab: "section 2",
    content: "I'm the content of Section 2"
  }
];

const useTabs = (initialTab, allTabs) => {
  if (!allTabs || !Array.isArray(allTabs)) {
    return;
  }
  const [currentIndex, setCurrentIndex] = useState(initialTab);
  return {
    currentItem: allTabs[currentIndex],
    changeItem: setCurrentIndex
  };
};

export default function App() {
  const { currentItem, changeItem } = useTabs(0, contents);

  return (
    <div className="App">
      <h1>Hello</h1>
      {contents.map((section, index) => (
        <button onClick={() => changeItem(index)}>{section.tab}</button>
      ))}
      <div>{currentItem.content}</div>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

결과는 아래와 같다.
section 1 버튼을 누르면 I'm the content of Section 1이라는 내용이 나오고, section 2 버튼을 누르면 I'm the content of Section 2이라는 내용이 나온다.

참고

Built-in React Hooks – React
https://nomadcoders.co/react-hooks-introduction/lobby

'Web Develop > React' 카테고리의 다른 글

React Hooks에 대해 알아보자. (2)  (0) 2023.07.14
[React] 참고하면 좋은 사이트  (0) 2022.05.10