React가 제공하는 기본 Hooks

sxxng_ju·2022년 7월 11일
0

Web

목록 보기
2/4

useState와 useEffect와 같이 기본적인 Hook외에 사용할만한 React 기본 제공 Hook들을 알아보았습니다.

useReducer

여러가지 하위값을 포함하는 복잡한 상태값을 관리할 때 useReducer를 사용하면 좋습니다. useReducer에는 크게 3가지가 필요합니다. reducer, dispatch, action이다. reducer는 state를 업데이트 하는 역할을 하고 dispatch는 state 업데이를 위한 요구이고 action은 요구의 내용입니다.

{
  name:"kim seung ju",
  hobby: ['coding','music','game'],
  friends: [
    {
    	name: "A"
      	age: 25
    },
    {
    	name: "B"
      	age: 24
    }
  ]
}
import {useState, useReducer} from "react"
import {ThemeContext} from "../context/ThemeContext"

const ACTION_TYPES = {
	deposit: "deposit",
  	withdraw: "withdraw",
}

const reducer = (state, action) => {
  switch(action.type) {
  	case ACTION_TYPES.deposit:
		return state + action.payload;
    case ACTION_TYPES.withdraw:
	    return state - action.payload;
    default:
      	return state;
  } 
}

const App = () => {
  	const [number, setNumber] = useState(0)
	const [money, dispatch] = useReducer(reducer, 0)
	return (
  		<div>
    		<p>{money}</p>
	    	<input type="number" value={number} onChange={(e) => setNumber(parseInt(e.target.value))} step="1000" />
		    <button onClick={() => {
            	dispatch({type: ACTION_TYPES.deposit, payload: number})
            }}>예금</button>
   			<button onClick={() => {
            	dispatch({type: ACTION_TYPES.withdraw, payload: number})
            }}>출금</button>
	    </div>
  	)
}

useContext

Context 는 App 안에서 전역적으로 사용되는 데이터들을 여러 컴포넌트들끼리 공유할 수 있는 방법을 제공합니다.(props로 일일이 넘겨주지 않아도 됩니다!)

보통 사용자 정보(User), 테마(Theme), 언어(Language)와 같은 전역적인 데이터를 전달할 때 사용합니다.

상위 컴포넌트로부터 props를 통해 데이터를 받아오는 경우 중간 컴포넌트의 하위 컴포넌트에서 데이터가 필요한데도 불구하고 중간 컴포넌트가 데이터를 받아서 전달해주어야 합니다. 이러한 경우 데이터를 넘기는 과정에서 오류가 발생하면 모든 컴포넌트를 확인해야하고 귀찮아집니다.

그렇다면 Context만 쓰면 되지 않을까요?
아닙니다. Context를 사용하면 컴포넌트를 재사용하기 어려워 질 수 있다는 단점이 있습니다. 꼭 필요할 때만 사용하는 것이 좋습니다.

UserContext.js

import {createContext} from "react"
export const ThemeContext = createContext(null)

App.js

import {useState} from "react"
import {ThemeContext} from "../context/ThemeContext"

const App = () => {
  const [data, setData] = useState(false)
  return (
  	<ThemeContext.Provider value={{data, setData}}>
    	<Page>
    </UserContext.Provider>
  )
}

Page.js

import {useState} from "react"
import {ThemeContext} from "../context/ThemeContext"

const Page = () => {
  const {isDark, setIsDark} = useContext(ThemeContext)
  return (
  	<>
    	<p style={{color: isDark ? 'black' : 'lightgray'}}>hi</p>	
    </>
  )
}

전역적으로 상태를 관리할 때 외부 라이브러리 설치없이 사용할 수 있어서 간단하게 사용할 때 좋을 것 같습니다.

useRef

useState를 사용하여 상태를 저장하면 state값이 변할 때마다 재렌더링이 일어나고 컴포넌트 내부의 변수들이 초기화됩니다.

하지만 useRef를 사용하면 렌더링이 일어나지 않기때문에 변수들의 값을 유지할 수 있습니다. => 불필요한 렌더링을 막을 수 있음

import React, {useRef} from "react"

const App = () => {
  console.log("렌더링")
  const myRef = useRef(0)
  // myRef = {current : 0}
  const increaseRef = () => {
  	myRef.current = myRef.current + 1
  }
  
	return (
  		<div>
      		<p>{myRef.current}</P>
      		<button onClick={increaseRef}>up</button>
      	</div>
    )
  
}

위 코드에서 up버튼을 클릭해도 myRef 값은 계속해서 0으로 화면에 표시될 것입니다. 그 이유는 useRef는 값이 변해도 화면에 재렌더링을 하지 않기 때문입니다. 하지만 내부적으로는 myRef의 값은 버튼을 누를때마다 증가하고 있을 것입니다.

=> 컴포넌트에서 자주 바뀌는 값을 useRef로 사용함으로써 성능을 개선할 수 있습니다.

그렇다면 useRef와 그냥 변수를 선언하여 사용하는 것은 어떠한 차이가 있을까요??
먼저 useRef의 값은 렌더링이 될 경우 화면에 그려지게 됩니다. 하지만 변수로 선언한 값은 아무리 그 전에 값을 변경해도 컴포넌트가 재렌더링 될 때 선언된 값으로 다시 초기화가 되기때문에 화면에 바뀐 값을 그릴 수 없습니다.

결론 : useRef는 변화를 감지하지만 그 변화에 의해 재렌더링이 일어나는 것을 원치 않을 때 사용하면 좋다!
또한 useRef를 사용하면 DOM 요소에 접근할 수 있다. 예를들면 로그인 화면에서 input창에 focus 이벤트를 주지않아도 접근할 수 있다. 자바스크립트의 Document.querySelector()와 유사하다.

const inputRef = useRef()
useEffect(()=>{
	inputRef.current.focus();

}, [])
<input ref={inputRef} />

useMemo

useMemo에서 memo는 Memoization(메모이제이션)을 뜻합니다. 메모이제이션이란 동일한 값을 리턴하는 함수를 반복적으로 호출해야 한다면 처음 값을 계산할때 해당 값을 메모리에 저장해서 필요할 때마다 또 계산하지 않고 메모리에서 꺼내서 계산하는 기법입니다.

const value = useMemo(() => {
	return callItem()
},[item])

위 코드를 사용하면 item 상태값이 바뀔때만 함수를 호출하여 불필요한 반복적인 호출을 막을 수 있습니다.

주의 : useMemo는 꼭 필요할때만 사용해야 합니다. useMemo를 사용한다는 것은 값을 따로 저장하는 것이기 때문에 오히려 성능이 악화될 수 있습니다.

위의 상황보다 useMemo가 쓰이는 곳은 따로 있습니다.
바로 변수가 객체타입으로 선언됬을 경우입니다. 객체로 선언된 변수는 값을 바로 저장하지 않고 메모리 주소를 저장하여 그곳에 값을 저장하기 때문에 컴포넌트가 재렌더링 될 때 마다 다른 메모리 주소에 값을 저장하게 됩니다. 따라서 useEffect같은 hook을 사용할 때 두번째 인자에 객체로 선언된 변수가 들어있다면 계속해서 변수 변하는 것과 같습니다. 이것을 막기위해 useMemo를 쓰면 됩니다.

const [state, setState] = useState(true)
const data = useMemo(() => {
	return {
    	key: value ? '1' : '2',
    }
},[state])
useEffect(()=>{
  console.log("useEffect 호출")
  // 오래걸리는 작업이 예상된다면 useMemo를 활용해보는 것도 좋다.
}, [data])

useCallback

useCallback은 함수 객체가 할당된 변수가 계속해서 초기화되는 것을 막기 위해 사용합니다.

const [item, setItem] = useState("")
const calculate = useCallback((num) => {
	return num + 1;
},[item])
const [number, setNumber] = useState(0)
const newFunc1 = () => {
	console.log("함수 호출")
  	return;
}

// 메모이제이션 된 함수
const newFunc2 = useCallback(()=>{
	console.log("함수 호출")
	return;
}, [number])

useEffect(()=>{
	console.log("newFunc가 변경되었습니다.")
},[newFunc])

다른 관계없는 상태값이 변할 때도 함수가 계속 호출되는 경우에 useCallback함수를 사용하고 두번째 인자에 의존성을 지정해주면 좋습니다.

useMemo는 함수의 연산량이 많을때 이전 결과값을 재사용하는 목적이고, useCallback은 함수가 재생성 되는것을 방지하기 위한 목적입니다.

0개의 댓글