React 공식문서 해석하며 공부하기 : Hooks at a Glance

배지로·2021년 9월 3일
post-thumbnail

해석하며 공부하는 것을 목적으로 하기 때문에 다수의 의역, 오역이 있음을 미리 밝힙니다.
원본 : https://reactjs.org/docs/hooks-overview.html

Hooks 한 눈에 보기

Hooks는 리액트 16.8에 새롭게 추가되었습니다. Hooks는 클래스를 사용하지 않고도 state와 다른 리액트 기능을 사용할 수 있게 도와줍니다.

Hooks는 역호환성을 가지고 있습니다. 이 페이지에서는 리액트 개발자를 위해 Hooks에 대한 개요를 제공합니다. 빠르고 간단하게 설명할 예정이므로 더 자세한 내용을 보고 싶다면 아래의 노란색 상자를 봐주세요:

자세한 설명
왜 리액트 훅을 도입하는지 알고 싶다면 동기에 대한 문서를 읽어주세요.

⬆️⬆️⬆️ 각 섹션은 위와 같이 노란색 상자로 끝납니다. 자세한 설명은 링크를 따라가세요.


📌 State Hook

아래의 예시는 카운터를 렌더링합니다. 버튼을 클릭하면 값이 증가합니다.

import React, { useState } from 'react';

function Example(){
  //새로운 state 변수를 선언하고, 그것을 "count"라고 이름 붙입니다
  const [count, setCount]=useState(0);
  
  return(
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()=>setCount(count+1)}>Click me</button>
    </div>
  );
}

여기, useState는 Hook입니다(이것이 무슨 의미인지는 곧 이야기하겠습니다). Hook을 호출해 함수 컴포넌트 안의 로컬 state를 추가해줬습니다. 리액트는 렌더링을 다시하는 과정에서 이 state가 보존되도록 합니다. useState는 한 쌍으로 반환합니다 : 현재 state 값과 그 state를 업데이트하게 도와주는 함수를 쌍으로 반환합니다. 이벤트 핸들러나 다른 곳에서 함수를 호출할 수 있습니다. 클래스의 this.setState와 비슷하지만 예전의 state와 새로운 state를 합치지 않는다는 점에서는 다릅니다. (State Hook을 이용하기 문서에서 useStatethis.state를 비교하는 예시를 보여드리겠습니다.)

useState의 유일한 매개변수는 state의 초기값입니다. 위의 예시에서 카운터는 0에서부터 숫자를 세기 때문에 초기값은 0입니다. 참고로, this.state와 다르게 여기서의 state는 객체일 필요가 없습니다. - 원한다면 할 수 객체로 만들 수 있지만요. 매개변수로 전달받는 state의 초기값은 오직 첫번째 렌더링에서만 사용됩니다.

여러 개의 state 변수를 선언하기

하나의 컴포넌트에서 State Hook를 여러 번 사용할 수 있습니다:

function ExampleWithManyStates(){
  //여러 개의 state 변수 선언!
  const [age, setAge]=useState(42);
  const [fruit, setFruit]=useState('banana');
  const [todos, setTodos]=useState([{text:'Learn Hooks'}]);
  //...
}

배열 분해 문법은 useState를 호출하여 선언한 state 변수에 다른 이름을 지정할 수 있도록 합니다. 이러한 이름들은 useState API와 관련이 없습니다. 대신, 리액트는 매 렌더링마다 같은 순서로 useState를 호출할 수 있도록 합니다. 왜 이렇게 동작하는지와 언제 이것이 유용한지 추후에 돌아와서 살펴보도록 하겠습니다.

그런데 Hook이 뭔가요?

Hooks는 리액트 state와 함수 컴포넌트의 생명주기 기능에 "연결하게" 도와주는 함수입니다. Hooks는 클래스 안에서는 동작하지 않습니다. - 클래스 없이 리액트를 사용할 수 있게 도와주죠.(밤새서 존재하는 컴포넌트를 다시 작성하는 것을 추천하지 않지만 새로운 컴포넌트를 작성할 때 Hooks를 사용하는 것을 시작해보면 어떨까요?)

리액트는 useState와 같은 몇몇 내장된 Hooks를 제공합니다. 서로다른 컴포넌트 사이에서 state 관련 로직을 재사용하기 위해 직접 Hooks를 만드는 것도 가능합니다. 일단 내장된 Hooks를 먼저 살펴봅시다.

자세한 설명
State Hook에 대해서 더 많은 정보를 얻고 싶다면 전용 페이지에서 살펴보세요:State Hook 사용하기를 읽어주세요.
___

⚡️ Effect Hook

리액트 컴포넌트에서 데이터 가져오기, 구독, 직접 DOM 조작하는 작업을 이전에도 해봤을 것입니다. 이러한 동작들은 다른 컴포넌트에 영향을 미치고 렌더링하는 중에 할 수 없기 때문에 "side effect"(짧게는 "effect")라고 부릅니다.

Effect Hook, useEffect는 함수 컴포넌트에서 side effects를 수행할 수 있는 능력을 가집니다. 이것은 리액트 클래스의 componentDidMount, componentDidUpdate 그리고 componentWillUnmount와 같은 목적으로 수행되지만 하나의 API로 통합되었다는 것이 다릅니다.(Effect Hook 사용하기문서에서 useEffect와 이 메소드를 비교하는 예시를 볼 수 있습니다.)

아래의 컴포넌트는 리액트가 DOM을 업데이트하고 난 후 문서 이름을 세팅하는 작업을 합니다:

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

function Example(){
  const [count, setCount]=useState(0);
  
  //componentDidMount와 componentDidUpdate와 비슷합니다
  useEffect(()=>{
    //브라우저 API를 사용해서 문서 제목을 업데이트합니다
    document.title=`You clicked ${count} times`;
  });
  
  return(
    <div>
      <p>You clicked {count} times</p>
      <button onClick={()=>setCount(count+1)}>
        Click me
      </button>
    </div>
  );
}

useEffect를 호출하면, 리액트는 DOM을 조작한 뒤에 "effect"함수를 작동시킬 것입니다. Effects는 컴포넌트 안에 선언되어 있기 때문에 props와 state에 접근할 수 있습니다. 리액트는 첫번째 렌더링을 포함해 매번 렌더링한 후에 effects를 작동합니다.(어떻게 이것들을 클래스 생명주기와 비교하는지에 대해서는 Effect Hook 사용하기문서에서 이야기 나눠보겠습니다.)

Effects는 함수를 반환하여 "정리(clean up)"하는 방법을 선택적으로 지정하는 것도 가능합니다. 예를 들어, 아래의 컴포넌트는 친구의 온라인 상태를 구독하는 effect를 사용하고 구독해제를 함으로서 정리합니다:

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

function FriendStatus(props){
  const [isOnline, setIsOnline]=useState(null);
  
  function handleStatusChange(status){
    setIsOnline(status.isOnline);
  }
  
  useEffect(()=>{
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return()=>{
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  
  if(isOnlien===null){
    return 'Loading...';
  }
  return isOnline?'Online':'Offline';
}

위의 예시에서, 컴포넌트가 언마운트될 때와 이후의 렌더링때문에 effect를 다시 실행시키기 전에 리액트는 ChatAPI를 통해 구독할 수 있습니다. (ChatAPI로 전달한 props.friend.id가 변하지 않았다면 리액트에게 다시 구독하는 것을 건너뛰겠다고 말하는 방법도 있습니다.)

useState와 마찬가지로, 컴포넌트에서 한 개 이상의 effect를 사용할 수 있습니다:

function FriendStatusWithCounter(props){
  const [count,setCount]=useState(0);
  useEffect(()=>{
    document.title=`You clicked ${count} times`;
  });
  
  const [isOnline,setIsOline]=useState(null);
  useEffect(()=>{
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  
  function handleStatusChange(status){
    setIsOnline(status.isOline);
  }
  //...

Hooks는 생명주기 메소드처럼 분리시키기보다는 관련있는 것들끼리 묶어서(구독을 추가하고 제거하는 것과 같이 관련있는 것들) 컴포넌트에서 side effect를 정리할 수 있게 합니다.

자세한 설명
useEffect에 대해서 더 알고 싶으시다면 전용 페이지에서 살펴보세요:Effect Hook 사용하기를 읽어주세요.
___

✌️ Hooks의 규칙

Hooks는 자바스크립트 함수이지만 두 개의 규칙이 더 있습니다:

  • 오직 최상위에서만 Hooks를 호출할 수 있습니다. 반복문, 조건문, 중첩된 함수에서는 Hooks를 호출하지 마세요.
  • 리액트 함수 컴포넌트에서만 Hooks를 호출할 수 있습니다. 자바스크립트 정규 함수에서 Hooks를 호출하지 마세요.(Hooks를 호출할 수 있는 유효한 장소가 하나 더 있습니다. - 당신의 custom Hooks에서. 곧 custom Hooks에 대해서 배울 예정입니다.)

이러한 규칙들이 자동으로 적용되게 하는 linter plugin을 제공합니다. 규칙들은 처음에는 제한적으로 보이거나 혼란스러울 수 있다는 것을 이해하지만 이것들은 Hooks가 더 잘 동작할 수 있도록 만들기 위해 꼭 필요한 것입니다.

자세한 설명
이러한 규칙들에 대해서 더 알고 싶으시다면 전용 페이지에서 살펴보세요:Hooks의 규칙를 읽어주세요.
___

💡 나만의 Hooks 만들기

때때로, 컴포넌트 사이에서 몇몇 state 로직을 재사용하고 싶을 때가 있을 것입니다. 전통적으로, 이러한 문제를 해결할 수 있는 유명한 2가지 해결책이 있습니다: 하이어오더 컴포넌트props 렌더링하기. Custom Hooks는 트리에 컴포넌트를 추가하지 않으면서도 이것을 가능하게 합니다.

페이지의 앞 부분에서, 친구의 접속 상태를 구독하기 위해서 useStateuseEffect Hooks를 호출하는FriendStatus 컴포넌트를 확인했었습니다. 이 로직을 다른 컴포넌트에서 한 번 더 사용해봅시다.

먼저, 이 로직을 추출해서 useFriendStatus라고 불리는 custom Hook에 넣어봅시다:

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

function useFriendStatus(friendID){
  const [isOnline, setIsOnline]=useState(null);
  
  function handleStatusChange(status){
    setIsOnline(status.isOnline);
  }
  
  useEffect(()=>{
    ChatAPI.subscribeToFrinedStatus(friendID,handleStatusChange);
    return()=>{
      ChatAPI.unsubscribeFromFriendStatus(friendID,handleStatusChange);
    }
  });
  
  return isOnline;
}

위의 컴포넌트는 friendID를 매개변수로 받으며 친구가 온라인에 접속해 있는지를 반환합니다.

이제 여러 컴포넌트에서 userFriendStatus를 사용할 수 있습니다.

function FriendStatus(props){
  const isOnline=userFriendStatus(props.friend.id);
  
  if(isOnline===null){
    return 'Loading...';
  }
  return isOnlie?'Online':'Offline';
}
function FriendListItem(props){
  const isOnline=userFriendStatus(props.friend.id);
  
  return(
    <li style={{color:isOline?'green':'black'}}>
      {props.friend.name}
    </li>
  );
}

각 컴포넌트의 state는 완전히 독립적입니다. Hooks는 state 로직의 재사용을 위한 방법이지, state 그 자체를 재사용하기 위한 방법은 아닙니다. 사실은 Hook에 대한 각각의 호출은 state와 완전히 분리되어 있습니다. - 그래서 같은 custom Hook을 한 컴포넌트 내에서 두번 사용할 수 있습니다.

Custom Hook은 기능이라기보다는 관습에 가깝습니다. 함수의 이름이 "use"로 시작되고 다른 것들을 Hooks라고 부른다면, 우리는 그 함수를 custom Hook이라고 부릅니다. useSomething 이름붙이기 관습은 linter plugin이 Hooks를 사용해서 코드의 버그를 어떻게 찾아내는지를 보여줍니다.

폼 핸들링, 애니메이션, 선언적 구독, 타이머 등 우리가 미처 생각하지 못한 것 까지도 custom Hook를 사용할 수 있습니다. 리액트 커뮤니티가 고안해낼 custom Hook에 대해서 기대가 됩니다.

자세한 설명
Custom Hook에 대해서 더 알고 싶으시다면 전용 페이지에서 살펴보세요:나만의 Hook 만들기를 읽어주세요.
___

🔌 다른 Hooks

찾아보면 유용하지만 덜 알려진 내장 함수들이 몇 개 있습니다. 예를 들면 useContext는 중첩하지 않고도 리액트 컨텍스트를 구독할 수 있게 합니다.

function Example(){
  const locale=useContext(LocaleContext);
  const theme=useContext(ThemeContext);
  //...
}

그리고 useReducer은 reducer로 복잡한 컴포넌트의 로컬 state를 관리할 수 있게 합니다.

function Todos(){
  const [todos, dispatch]=useReducer(todosReducer);
  //...
자세한 설명
모든 내장 Hooks에 대해서 더 알고 싶으시다면 전용 페이지에서 살펴보세요:Hooks API Reference를 읽어주세요.
___

다음 단계

후, 너무 빨랐죠! 완전히 이해되지 않는게 있거나 더 자세히 배우고 싶다면 State Hook 문서부터 시작해서 다음 페이지들을 읽을 수 있습니다.

또한 Hooks API referenceHooks FAQ도 확인하실 수 있습니다.

마지막으로, 왜 Hooks가 도입되었는지와 코드를 재작성하지 않아도 클래스와 나란히 사용하여 시작하는 방법을 설명하는 소개 페이지도 놓치지 마세요.

profile
웹 프론트엔드 새싹🌱

0개의 댓글