201008_TIL

18463 단어 TILTIL

Today I learned

  • React Hooks
    • Hooks
    • useState Hooks
    • useEffect Hooks
    • Hooks 사용규칙
    • Custom Hooks

@@ 오늘은 Recase.ly 스프린트(유투브 API 연동, React class 컴포넌트 구성)를 마무리 짓고, react의 Hooks 에 대해서 공식문서를 읽으며 정리하는 시간을 가졌다.

강의를 듣기 전에 독학할때 인강으로도 보고, 책도 읽고 했었지만 최적화 하는 부분에서 너무 어렵게만 느껴지고 굳이 이것을 써야하나? 클래스 컴포넌트가 더 나은 것 같은데.. 라고 생각하고는 했었는데 공식문서가 이토록 자세하고 친절한지 몰랐다. 공식문서에 대한 현기증으로.. 멀리만했었기 때문인 것 같다. 공식 문서 및 레슨을 들으면서 왜 Hooks가 필요한지, 클래스 컴포넌트는 어떤 문제가 있는지 살펴보니 (그러니까... 이유를 알고 배우니) 더 친절하게 느껴지고, 이해도 더 잘가는 기분이었다.

남은 시간은 recast.ly의 Advanced 부분을 진행해보려는 훅스에 도전해봐야겠다.

참, 그리고 #velog 의 화면 사이즈가 변할때마다 작성하던 글이 다 날라가는 게 너무 불편했었더니 오늘보니 수정되었는지, 해당 버그가 없어졌다. 😊 작은 문제이지만 은근히.. 불편했는데 작은것에 행복해지는.. 추가적으로 바람이 있다면, 접근성 규칙에 맞춰서 제목에서 탭으로 순서대로 이동할 수 있도록도 변경되면 좋겠다. (제목-> 태그-> 본문)
지금은... 왜그런지 모르겠는데 탭하면 순서가 이상하게 잡혀있는 것 같다. (이것도 사소한 불편함...)

Hooks

  • Hook은 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 “연동(hook into)“할 수 있게 해주는 함수
  • 새로 작성하는 컴포넌트부터는 Hook을 이용

useState Hooks

import React, { useState } from 'react';

function Example() {
  // 새로운 state 변수를 선언하고, 이것을 count라 부르겠습니다.
  const [count, setCount] = useState(0);
  • useState는 state를 함수 컴포넌트 안에서 사용할 수 있게 해준다.

  • state를 추가하고 싶을 때 클래스 컴포넌트로 바꾸지 않고 useState 훅을 사용하면 state를 사용할 수 있다.

  • 함수 컴포넌트는 this를 가질 수 없기 때문에 useState 훅을 직접 호출해야 한다.

  • useState를 호출하는 것은 state변수를 선언하는 것과 같다.

  • 선언한 변수의 이름은 사용자가 지을 수 있다.

  • 일반적으로 일반 변수는 함수가 종료될 때 사라지지만 state변수는 리액트에 의해 사라지지 않는다.

  • 구조 분해 할당으로 state 변수, 해당 변수를 갱신할 수 있는 함수를 받는다. 구조분해할당에 대한 코드의 의미는 아래의 코드와 같다.

var fruitStateVariable = useState('banana'); // 두 개의 아이템이 있는 쌍을 반환
var fruit = fruitStateVariable[0]; // 첫 번째 아이템
var setFruit = fruitStateVariable[1]; // 두 번째 아이템
  • useState에 들어가는 인자값은 state 변수의 초기값으로 할당된다.

  • 아래와 같은 방법으로 설정한 state값을 가져올 수 있다.

<p>You clicked {count} times</p>
  • state값 갱신 방법
<button onClick={() => setCount(count + 1)}>
    Click me
 </button>
  • useState 의 state갱신함수는 값을 병합하는 것이 아니라 대체한다.

useEffect Hooks

  • useEffect는 함수 컴포넌트 내에서 이런 side effects를 수행할 수 있게 해준다

  • React class의 componentDidMount 나 componentDidUpdate, componentWillUnmount와 같은 목적으로 제공되지만, 하나의 API로 통합

  • Effect를 “해제”할 필요가 있다면, 해제하는 함수를 반환

  • 이 반환되는 함수는 컴포넌트가 Unmount 될때, 그리고 재 렌더링이 일어나 effect를 재실행하기 전에 실행된다.

  • 클래스의 하나의 로직에 여러개 함수를 써야한다는 복잡성때문에 탄생하게 되었다.

  • useState와 마찬가지로 컴포넌트 내에서 여러 개의 effect를 사용할 수 있다.

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}
  • Hooks를 사용하면 생명주기 메서드가 아닌 코드 로직에 따라 처리를 할 수 있다.

effect가 업데이트 시마다 실행되는 이유

  • 클래스 컴포넌트에서 발생하는 버그를 해결하기 위해 effect 는 업데이트 시마다 실행된다.
  • 클래스 일때 생명주기 메소드 예시
  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }
  • 컴포넌트가 화면에 표시되어있는 동안 friend prop 이 변한다면 컴포넌트는 온라인 상태로 잘못 표시하게 되고, 언마운트 될 때 다른 사용자가 해지되는 등의 충돌이 일어날 수 있다. 그래서 이를 막기 위해 componentDidUpdate 메소드를 사용하는데 이 또한 버그 중의 하나로 제대로 작동 되지 않는다.

  • useEffect는 업데이트를 다루기 때문에 이런 버그 들을 해결할 수 있다.

  • 다음 effect를 적용하기 전 이전의 effect 는 정리 한다.

  • 코드로 나타나면 다음과 같다

성능 최적화

  • useEffect의 두번째 인수로 배열(useEffect 내에서 사용되는 데이터값)을 설정해주면 해당값이 변할때만 useEffect가 실행되게 함으로써 최적화를 꾀할 수 있다.

  • 두번째 인자는 빌드 시 변환에 의해 자동으로 추가될 수도 있다.

  • 다만 주의해야할 점은 이 최적화 방법을 사용할 때 컴포넌트 범위내에서 바뀌는 값들과 effect에 의해 사용되는 값들을 모두 포함해야한다는 것이다. 그렇지 않으면 이전 렌더링 값을 참고하게 된다.

  • 두번째 인수에 빈배열을 넘기면 effect를 실행하고 정리하는 과정을 한번씩만 실행하게 된다. (어떤 값에도 의존하지 않으며 재실행될 필요가 없음을 명시하는 것이다).
    빈 배열을 넘기게 되면 effect 안의 prop 과 state는 초깃값을 유지한다.

  • 리액트는 브라우저가 다 그려질때까지 useEffect의 실행을 지연한다.

  • eslint-plugin-react-hooks를 설치하여서 exhustive-dpes규칙을 설정하면 의존성이 바르게 되지 않을때 경고해주게 된다.

    설치방법

    $ npm install eslint-plugin-react-hooks --save-dev
    
    // ESLint 설정 파일
    {
      "plugins": [
        // ...
        "react-hooks"
      ],
      "rules": {
        // ...
        "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
        "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
      }
    }

Hooks 사용 규칙

  • 최상위(at the top level)에서만 Hook을 호출 (반복문, 조건문, 중첩 함수 내에서 훅을 실행하면 안된다)

  • 컴포넌트 내에서만 훅을 호출한다.

  • 직접 작성한 커스텀 훅 내에서는 훅을 호출할 수 있다.

  • 리액트는 어떻게 특정 stater가 어떤 useState 호출에 해당하는지 알 수 있을까?

    리액트가 호출되는 순서에 의존한다. 훅의 호출 순서는 같기 때문에 올바르게 동작한다. 그러나 만약 조건문 같은 경우로 훅이 건너뛰어진다면 리액트는 훅 호출에서 무엇을 반환할지 모르게 된다. 이는 버그를 발생시킨다. 조건부로 effect를 실행시키고 싶다면 useEffect내부에서 조건문을 설정해야 한다.

나만의 Hooks, Custom Hooks 작성

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

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
  • 커스텀 훅을 만들면 여러 컴포넌트에서 재사용 가능하다
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
  • 각 컴포넌트의 state는 완전히 독립적이다. Hook은 state 그 자체가 아니라, 상태 관련 로직을 재사용하는 방법이다.
  • 심지어는 한 컴포넌트 안에서 같은 custom Hook을 두 번 쓸 수도 있다.
  • 폼 핸들링, 애니메이션, 선언적 구독(declarative subscriptions), 타이머 등 많은 경우에 custom Hook을 사용할 수 있다

좋은 웹페이지 즐겨찾기