Hook

songsong·2020년 4월 23일
0

React

목록 보기
10/11
post-thumbnail

📖Hook

1.Hook 정의

👉 Hook을 사용하면 함수형 컴포넌트에서도 state와 React의 여러 기능을 사용할 수 있다.
Hook을 사용하는 이유는 클래스 코드의 재사용과 코드 구성을 보다 어렵게 만들 뿐만 아니라,
React를 배우는데 큰 진입 장벽이라고 판단했기 때문이다.

2. Hook 사용 시 규칙

👉 React 함수형 컴포넌트 안에서만 사용.

👉 컴포넌트 안의 반복문, 조건문, 중첩된 함수 안에서 훅을 사용 할 수 없다.

3. useState

👉 useState()를 사용하면 함수형 컴포넌트에도 상태(state)를 설정할 수 있다.
👉 useState()는 state와 state를 업데이트 하는 함수를 쌍으로 제공한다.
👉 useState()는 전달 받는 인자로 state의 초기 값을 설정한다. 초기 값은 함수형 컴포넌트가 첫 렌더링 될 때 딱 한 번만 사용된다.

Syntax

const [state<, setState] = React.useState(initialValue);

Exmple

import React, { useState } from 'react';

const Counter = props => {
  // count 상태 값을 0으로 설정
  // setCount() 함수를 사용해 count 값 교체
  const [count, setCount] = useState(0);
  return (
    <div className="counter">
      <p>{count} 번 클릭했습니다.</p>
      <button 
        type="button" 
        onClick={() => setCount(count + 1)}
      >
        클릭
      </button>
    </div>
  )
}

3-1. 하나의 이상의 state 설정

👉 필요하다면 함수형 컴포넌트 안에서 1개 이상의 state를 설정해 사용할 수도 있다.

Exmple

const Counter = props => {
  const [count, setCount] = useState(0);
  const [userInfo, updateUserInfo] = useState(props.userInfo || null);
  return (...)
}

🧐클래스 컴포넌트 this.state와 동일한가 ?

👉 useState()로 설정된 함수형 컴포넌트의 state는 클래스 상태와 달리 1개 이상의 상태 마다 개별적으로 요구된다. 반면 클래스 컴포넌트는 상태 업데이트 시, 상태의 일부 데이터를 교체하고 합친다는 점이 다르다.

4. useEffect

👉 React 앱에서 데이터를 서버로부터 가져와 패치(Fetch) 해야 하거나, 실제 DOM 노드에 접근해 조작해야 하는 경우 클래스 컴포넌트는 라이프 사이클 훅을 사용해 해결했다. 이러한 동작을 사이드 이펙트(Side Effects, 부작용)라고 한다. 사이드 이펙트로 명칭한 이유는 이러한 일련의 행위가 다른 컴포넌트에 영향을 줄 수 있고, 컴포넌트 렌더링 과정에서 구현될 수 없는 것이기 때문이다.

함수형 컴포넌트의 useEffect() 훅은 클래스 컴포넌트의 다음의 라이프 사이클 훅을 하나의 API로 통합한 것 이다.

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

4-1. 컴포넌트 렌더링, 업데이트 이후 시점

👉 useEffect()는 전달 받은 함수를 DOM 업데이트 이후 시점에 실행한다. 설정된 함수는 컴포넌트 내부에 위치해 있어 컴포넌트의 state, props에 접근할 수 있다. 그리고 컴포넌트 렌더링 , 업데이트 이후 시점(componentDidMount, componentDidUpdate)에 빠짐없이 실행된다.

Exmple

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

function CountDown(props) {
  const [count, setCount] = useState(10);
  // [사이드 이펙트]
  useEffect(() => {
    // 실제 DOM 노드에 접근
    const docTitle = document.querySelector('title');
    // 문서 제목을 백업(backup)
    let originDocTitleContent = docTitle.innerText
    // 문서 제목(title) 변경: 예) '카운트 다운(9)'
    docTitle.innerText = `클릭! 카운트 다운 (${count})`
    // 3초 뒤, 문서 제목을 복구(repair)
    setTimeout(() => docTitle.innerText = originDocTitleContent, 3000);
  })
  return (
    <button type="button" onClick={() => setCount(count - 1)}>
      카운트 다운 ({count})
    </button>
  )
}

4-2. 컴포넌트 제거 이전 시점

👉 필요한 경우, 컴포넌트가 제거 되기 전(componentWillUnmount)에 특정 코드를 실행하도록 설정할 수 있다.

Exmple

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

function CountDown(props) {
  const [count, setCount] = useState(10)
  useEffect(() => {
    ...
    // 컴포넌트가 제거될 때(componentWillUnmount) 실행 될 함수
    return () => {
      clearTimeout(timeoutID)
    }
  })
  return (...)
}

4-3. 이펙트 함수 성능 이슈

👉 매번 렌더링, 업데이트 과정에서 이펙트 함수가 실행될 경우 성능에 악영향을 미친다. 비교 과정을 통해 변경 사항이 발생할 때만 이펙트 함수가 실행된다면 성능 문제를 해결할 수 있다. 아래 코드의 비교 조건 설정(라인 3)으로 count state 값이 변경될 때만 함수(라인 2)가 실행된다.

Exmple

useEffect(
  () => {...}, 
  [count]
)

5. useRef

👉 useRef()는 실제 DOM 노드를 참조(Ref.)할 경우 사용하며, 참조 대상의 변경이 필요할 경우 .current 속성을 사용한다.

❗ useRef()를 사용해 실제 DOM 노드를 조작한 경우, 컴포넌트가 다시 그려지지 않아서 주의해야된다. (state, props가 변경되어야 업데이트)

Exmple

import React, { useRef } from 'react';

function FileInput(props) {
  // 실제 DOM 노드 참조(Ref.)
  const domFileInputEl = useRef(null);
  const domButtonEl = useRef(null);
  // 이벤트 리스너
  function handleSubmit(e) {
    e.preventDefault();
    console.log(`선택된 파일: ${domFileInputEl.current.files[0].name}`);
    domButtonEl.current.setAttribute('disabled', 'disabled');
    domButtonEl.current.innerText = '전송 됨';
  }
  // 렌더링
  return (
    <form onSubmit={handleSubmit}>
      <label>
        업로드:
        <input type="file" ref={domFileInputEl} />
      </label>
      <br />
      <button type="submit" ref={domButtonEl}>전송</button>
    </form>
  );
}

5-1. useCallback()

👉 실제 DOM 노드 변경을 감지해 특정 코드를 수행하고자 한다면, useCallback() 훅을 사용 하면 된다. useCallback() 훅을 사용해 참조 된 실제 DOM 노드의 width 값이 변경되면 컴포넌트 state 업데이트 하여 컴포넌트를 다시 그리게 된다.

Exmple

// 컴포넌트 state
const [width, setWidth] = useState(0);
// 실제 DOM 노드를 콜백 참조(Callback Ref.)하여 변경 감지 시, 컴포넌트 리렌더링
const domPanelEl = useCallback((node) => {
  if (node !== null) {
    setWidth(node.getBoundingClientRect().width)
  }
}, [])

6. useContext

👉 useContext() 훅은 컨텍스트 객체의 현재 값(value)을 소비(Consumer)할 수 있도록 설정한다.

👉 useContext() 훅은 클래스 컴포넌트의 static contextType 또는 <Context.Consumer>와 동일하며, 컨텍스트를 읽고, 변경사항을 구독하는 것만 가능하다. 컨텍스트의 값(value)을 변경하려면 <Context.Provider>가 필요하다.

Exmple

import React, { useContext } from 'react';
import AuthContext from '../context/AuthContext';

function SignIn(props) {
  const authContext = useContext(AuthContext);
  const { isAuth, signIn } = authContext;
  return (
    {
      isAuth ? 
        <div className="signed">로그인 됨</div> : 
        <button type="button" onClick={() => signIn}>로그인</button>
    }
  )
}

7. 커스텀 훅

👉 필요할 경우 사용자가 직접 훅을 만들어 사용할 수도 있다. 예를 들어 카운트 다운 상태를 재사용 하는 여러 컴포넌트의 경우가 그러하다. 카운트 다운 상태를 반환하는 훅을 만들어 이 로직을 다른 컴포넌트에서 재사용 할 수 있다.

👉 훅은 기능이라기 보다는 컨벤션(Convention, 협의)에 가깝다. 훅은 use로 시작하는 이름을 갖는 약속된 함수이다.

Exmple - 사용자 정의 훅

import React, { useState } form 'react';

// 사용자 정의(custom) 훅
function useCountDownStatus(count) {
  const [countStatus, setCountStatus] = useState('완료 전');
  if (count === 0) {
    setCountStatus('완료');
  }
  return countStatus;
}

export default useCountDownStatus;

👉 위에 정의한 커스텀 훅은 여러 컴포넌트에서 재사용 할 수 있다.

Exmple

// CountDownStatusMessage 컴포넌트
import useCountDownStatus from '../hooks/useCountDownStatus';
const CountDownStatusMessage = (props) => {
  const countStatus = useCountDownStatus(props.count);
  return countStatus === '완료' ? 
    <div>완료 됨</div> :
    <div>카운트 다운 중...<div>
}

Exmple

// CountDownDisplay 컴포넌트
import useCountDownStatus from '../hooks/useCountDownStatus';
const CountDownDisplay = (props) => {
  const countStatus = useCountDownStatus(props.count)
  return countStatus === '완료 전' ? 
    <div>{props.count}</div> :
    null
}

👉 정의한 커스텀 훅은 CountDown 컴포넌트 렌더를 사용하여 count 상태에 따라 각 컴포넌트가 작동하도록 만들 수 있다.

Exmple

import React, { Fragment } from 'react';
import CountDownStatusMessage from './CountDownStatusMessage';
import CountDownDisplay from './CountDownDisplay';

const CountDown = () => {
  const [count, setCount] = useState(10);
  return (
    <Fragment>
      <button type="button" onClick={() => setCount(count - 1)}>
        카운트 다운 ({count})
      </button>
      <CountDownStatusMessage count={count}/>
      <CountDownDisplay count={count}/>
    </Fragment>
  );
}

0개의 댓글