본문 바로가기
카테고리 없음

4단원 정리[1] Do It 리액트 모던 웹 개발 with 타입스크립트 (리액트) 공부

by ㅋ ㅅㅋ 2024. 11. 23.

4  -  1 처음만나는 리액트 훅

리액트 훅(react hook)과, 커스텀 훅(custom hook)에 관해 알아봅시다.

Clock컴포넌트 만들기

Date는 자바스크립트 엔진이 기본으로 제공하는 날짜오 시간 타입입니다.그리고 현재 시간과 날짜는 toLocaleTimeString()과 toLocaleDateString() 메서드를 호출하면 알 수 있습니다.

 

위에처럼 하면 시간이 나오긴 하는데 실시간으로 시간이 갱신되어 보여지지는 않습니다. 이제는 리액트 훅을 알아보면서 시간이 동작하는 시계를 만들어 보겠습니다.

 

[ 1 ] 리액트 훅이란 ?

리액트 프레임 워크는 2019년 2월 16.8.0 버전을 내놓으면서 리액트 훅이라는 혁신적인 기능을 선보였습니다.

리액트 훅은 다음 표와 같이 useState, useEffect등 'use'라는 접두사가 들어가는 함수 입니다. 리액트 훅 함수는 반드시 컴포넌트에서만 사용해야 합니다.

용도
컴포넌트 데이터 관리 useMemo
useCallback
useState
useReducer
컴포넌트 생명 주기 대응 useEffect
useLayoutEffect
컴포넌트 메서드 호출 useRef
useImperativeHandle
컴포넌트 간의 정보 공유 useContext

 

[ 2 ] 리액트 훅의 탄생 배경

리액트 버전 16.8.0 이전 버전에서 사용자 컴포넌트는 다음처럼 React.Component를 상속하고 render 메서드를 반드시 구현하는 클래스 기반 컴포넌트였습니다.

클래스 기반 컴포넌트 예

import React from 'react'
export default class MyComponent extends React.Component {
   render() { return <div/> }
}

 

그런데 많은 기능이 숨어있지만 코드가 직관적이지 않습니다. 다음코드에서 this.props것도 생소하고 this.state, this.props를 어떨때 써야하는지 혼란스럽습니다.

직관적이지 않는 코드

class MyComponent extends React.Component {
   state = {}
   render() {
        const state = this.state
        const props = this.props
        return <></>
   }
}
의미와 구현 방법을 알기 어려운 생명 주기 메서드들

class MyComponent extends React.Component {
   state = {}
   componentWillReceiveProps() {}
   componentWillMount() {}
   componentWillUnmount() {}
   componentWillMount() {}
   componentWillUpdate() {}
   render() {
        const state = this.state
        const props = this.props
        return <></>
   }
}

 

리액트 훅은 위에 코드들의 복잡함과 모호함을 극복할 목적으로 만들었습니다.

 

[ 3 ] 리액트 훅 코드 패턴과 의존성 목록

리액트 훅 함수는 여러가지 종류가 있지만 매개변수가 1개인것과 2개인것을 나눌 수 있습니다.

매개변수 개수 훅 함수
1개 useState, useRef, useImperativeHandle, useContext
2개 useMemo, useCallback, useReducer, useEffect, useLayoutEffect

 

매개변수가 1개인 훅 함수들은 다음과 같은 코드 패턴을 공통으로 사용합니다.

매개변수 1개인 훅함후 코드패턴
훅_함수<값의_타입>(값)

 

매개변수 1개인 useRef훅을 사용하는 예

useRef훅 사용 예
const today: Date = useRef<Date>(new Date)

// 타입스크립트의 타입 추론 기능을 활용 타입 부분 생략 예
const today = useRef(new Date)

 

만일 값이 null일 수 있을 때는 값 타입에 합집합을 나타내는 | 구문을 사용한 다음 패턴을 사용합니다.

null  허용 훅 함수 사용 패턴
훅_함수<값_타입 | null>(값)

ex) useRef<Date | null>(new Date)  << 이런식으로 사용하는것일 듯

 

매개변수 개수가 2개인 훅 함수들은 다음과 같은 코드 패턴을 공통으로 사용합니다. 여기서 의존성 목록은 콜백 함수에서 사용되는 변수나 함수의 값이 일정하지 않고 수시로 변할 수 있을때, 해당 변수나 함수를 아이템으로 갖는 배열을 의미합니다. 리액트 프레임워크는 의존성 목록에있는 아이템중 하나라도 변화가 있으면 콜백 함수를 새로 고침해 변한 값을 콜백 함수에 반영해 줍니다.

 

매개변수 2개인 훅 함수 코드 패턴
훅_함수<값의_타입>(콜백_함수, 의존성_목록)

 

다음은 매개변수 개수가 2개인 useEffect 훅을 사용하는 예입니다. 이코드는 콜백 함수를 가장 간단한 형태인 '()=>{}'로 구현했고 의존성 목록은 배열이어야 하므로 값이 변하는 변수나 함수가 없다는 의미로 빈 배열 []을 사용했습니다. 

의존성이 빈 배열일 때 콜백 함수는 한 번만 실행 됩니다.

useEffect 훅 사용 예
useEffect(( ) => { }, [ ])

 

[ 4 ] setInterval API로 시계 만들기

자바스크립트는 setInterval 이라는 API를 기본으로 제공합니다. 이 API는 갱신 주기 때마다 콜백 함수를 계속 호출해 줍니다.

setInterval 사용법
const id = setInterval(콜백_함수, 갱신_주기)
콜백_함수 = ( ) => { }

 

여기서 주의할점은 더이상 setInterval은 시스템 메모리 자원을 사용하므로 사용하지 않으려면 clearInterval 함수를 호출하여 메모리 누수가 생기지 않도록 해야합니다.

setInterval 콜백 함수를 멈추게 하는 clearInterval()함수
clearInterval(id

 

setInterval()을 사용해 시계를 만드는 코드

let today = new Date()
const duration = 1000
const id = setInterval(() => {
   today = new Date()      //현재 시간 갱신
}, duration)

 

이렇게 코드를 만들면 App라는 함수가 랜더링 될때마다 매번 setInterval이 호출이 발생한다는 문제가 있습니다.

setInterval이 함수는 처음 랜더링 될때 한 번만 호출되어야 하는데, 이때 useEffect를 사용하는게 좋아보입니다.

export default function App() {
   let today = new Date()
   const id = setInterval(() => {
        today = new Date()    //현재시간 갱신
        fourceUpdate()          // 갱신된 시각이 웹화면에 반영 되도록 App을 다시 렌더링하는 함수가 필요합니다.
   }, 1000)
}

fourceUpdate()  이런개념의 함수가 필요하다는 것이지 실제로 저런함수가 있는것이 아닙니다.

 

[ 5 ] useEffect 훅 사용하기

useEffect 훅을 사용하는 법을 배워봅시다

import { useEffect } from  'react'

 

위 코드를 추가해 주고 사용법은 밑에 글을 참고하면 됩니다.

useEffect(콜백_함수, 의존성목록)
콜백함수 = () => {}

 

useEffect는 의존성 목록에 잇는 조건 중 어느 하나라도 충족이 되면 그때마다 콜백함수를 다시 실행합니다.

컴포넌트가 실행될때 useEffect를 한번만 실행하게 하려면 의존성 목록을 []으로 빈값으로 만들면 됩니다. 그러면 콜백함수를 한번만 실행합니다.

한 번만 실행하는 useEffect 훅 사용법
useEffect(() => {}, [])
한 번만 호출하는 useEffect 훅 사용법
export default function App() {
   let time = new Date()
   useEffect(() =>{
      const id = setInterval(() => {
         time = new Date()   // 현재 시각 갱신
      }, 1000)
   },[])
return <></>
}

 

그런데 useEffect는 다음처럼 함수를 반환할 수 있습니다.

함수를 반환하는 useEffect
useEffect( () => {
// 컴포넌트가 생성될 때 실행
   return () => {}  // 컴포넌트가 소멸할 때 한번 실행
}, [])

 

다음은 useEffect훅을 사용하는 처음 코드입니다.

 

화면갱신이 버그가 있는 버전

import { useEffect, useState } from 'react';
import './App.css';
import Clock from './pages/Clock'
function App() { 
   let today = new Date(); 
   useEffect(() => {   
     console.log('useEffect called.')   
     const duration = 1000   
     const id = setInterval(() => {     
        today = new Date()     
        console.log('today', today.toLocaleTimeString())   
     }, duration)   
     return () => clearInterval(id) 
  },[]) 
return (    <Clock today={today}></Clock>  );
}

export default App;

위코드를 실행시키면 이렇게 에러가 나오고 화면이 실시간으로 갱신이 안된다.

Line 11:15: Assignments to the 'today' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect react-hooks/exhaustive-deps

위 경고 에러는 무언가 리액트가 의도한 바대로 구현되지 않았음을 알려 줍니다.

setInterval 콜백 함수안에 time변수가 정상으로 갱신되고 있는데도 웹 브라우저 화면은 이를 반영하지 못하고 있습니다.

로그보면 초단위로 실행되고있다

리액트의 경고 메세지 대로 useRef훅을 사용해보겠습니다.


[ 6 ] useRef 훅 사용하기

today변숫값을 useRef 훅으로 설정해보겠습니다.


import { useEffect, useRef } from 'react';
import './App.css';
import Clock from './pages/Clock'
function App() { 
   let today = useRef(new Date()); 
   useEffect(() => {   
     console.log('useEffect called.')   
     const duration = 1000   
     const id = setInterval(() => {     
        today.current = new Date()     
        console.log('today', today.current.toLocaleTimeString())   
  }, duration)   
return () => clearInterval(id)  },[]

return (   
     <Clock today={today.current}></Clock> 
  )
}

export default App;

이렇게 코드를 수정하면 이제 경고와 에러는 없어지는데 실행화면은 여전히 갱신이 안된걸 볼 수 있다.

이제는 useRef와 유사하지만 컴포넌트를 다시 렌더링해 주는 useState훅을 사용해보겠습니다.

 

[ 7 ] useState 훅 사용하기

useState 임포트 하는법
import { useState } from 'react'

 

useState가 반환하는 세터는 현재 값이 변경되면 자동으로 해당 컴포넌트를 다시 렌더링하는 기능이 있습니다.

useState 임포트 사용법
const [현재_값, 세터] = useState(초깃값)
세터 = (새로운_값) => void

 

다음코드에서 setToday(new Date())를 호출해 현재 시각 today값을 변경하고 있습니다.

이처럼 useState가 반환한 setToday() 함수는 현재 값인 today가 변경되면 컴포넌트를 자동으로 다시 렌더링하는 기능이 있습니다.

시계 앱 완성

 

[ 8 ] 커스텀 훅이란?

리액트 훅은 여러 훅 함수를 조합해 마치 새로운 훅 함수가 있는 것처럼 만들 수 있는데, 이렇게 조합한 새로운 훅 함수를 커스텀 훅 라고 합니다.

커스텀 훅 함수는 '훅'이라는 의미를 강조하고자 함수 이름에 'use'라는 접두어를 붙여서 만듭니다. 이제 useInterval과 useClock이라는 2개의 커스텀 훅을 만들어 보겠습니다.

코드가 매우 간결해진걸 볼 수 있습니다.

 

[ 9 ] 리액트 훅 함수의 특징?

 

1. 같은 리액트 훅을 여러 번 호출 할 수 있다.

2. 함수 몸통이 아닌 몸통 안 복합 실행문의 { } 안에서 호출할 수 없다.

3. 비동기 함수를 콜백 함수로 사용할 수 없다.

 

다음 코드는 훅을 여러번 호출 하는 예시 코드입니다.

export default function App() {
   const [ x, setX ] = useState(0)
   const [ y, setY ] = useState(0)

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

 

다음 코드는 { } 블록안에서 useState 훅을 호출하는 코드인데 지역변수가 { } 안에서만 적용되므로 이렇게 작성 하면 안됩니다.

지역 변수를 블록 안에서 훅 호출 예
export default function App() {
   {
        const [x, setX] = useState<number>(0)   // 이렇게 구현하면 안됨
}

 

또 if문 for문 두개다 { } 안에서 호출하는 것이기 때문에 이렇게 작성하면 안됩니다.

if블록 안에서 훅 호출 예
export default function App() {
    if(true) {
        const [x, setX] = useState<number>(0)   // 이렇게 구현하면 안됨
    }
}

 

마지막으로 리액트 훅 함수는 비동기 콜백 함수를 입력 받을 수 없습니다.

비동기 콜백 함수를 입력받는 훅 예
export default function App() {
     useEffect(async () => {        // 이렇게 하면안됨
        await Promise.resolve(1)
   }, [])                                          
}