React - Hooks

타다닥·2024년 2월 3일
0
post-thumbnail

Hooks

Hooks 사용 규칙

  • 최상위 단계에서만 hooks 호출이 가능하다. (최상위 컴포넌트가 아니라!)
  • 반복문, 조건문, 중첩된 함수 내에서 hook을 호출하면 안된다.
    • 리액트의 hook은 호출되는 순서에 의존한다! 건너뛰는 일이 생겨 오류가 생길 수 있다.
  • 리액트 함수형 컴포넌트 안에서만 호출 가능하다.
  • customHook 이름은 “use”로 시작하는 것을 권장한다.

Hooks 종류

  • useState(): 상태 관리를 위한 가장 기본적인 훅

    • const [count, setCount] = useState(0);
    • 현재 상태를 나타내는 state, 이 상태를 변경해주는 setState를 가진다. useState를 통해 초기값을 설정한다.
  • useRef(): 참조(reference)를 생성하고 관리할 수 있는 훅 (DOM 접근, 변수 보존 등)

    • DOM을 직접적으로 건드려야 할 때 사용한다. (input에 focus를 주거나 scrollBox를 조작한다거나 등)
  • useEffect(): 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 수 있는 훅

    • useEffect(콜백함수, 의존성 배열) 의 형태이다. 배열에 뭘 넣느냐에 따라 앞의 함수가 언제 실행 될 지 다르게 동작된다. 넣어도 되고 안넣어도 된다.
  • useMemo(): 메모이제이션을 통해 함수의 리턴 값을 재사용할 수 있게 해주는 훅 (메모지에이션 된 값을 반환)

  • useCallback(): 함수를 메모이제이션하여 불필요한 렌더링을 줄이게 해주는 훅 (메모지에이션 된 함수를 반환)

  • useReducer(): 복잡한 컴포넌트 상태 로직을 리듀서 함수를 통해 관리할 수 있는 훅

  • useContext(): 리액트에서 전역적으로 접근 가능한 데이터나 함수를 관리하고, 필요한 컴포넌트에서 그 값을 바로 가져와 사용할 수 있게 도와주는 훅


  • 메모이제이션 : 기존에 수행한 결과 값을 어딘가에 저장 해 둔다. 그리고 동일한 입력이 들어오면 재활용 하는 프로그래밍 기법이다. 중복된 연산을 피할 수 있어 성능의 최적화에 좋다.

useMemo()

  • component가 렌더링 되면 함수를 호출하게 되고, 함수 내부의 모든 변수가 초기화 된다.
    • 하나만 바뀌면 되는데 전체가 다 다시 렌더링 되는 건 비 효율적! 이 때 사용한다.
    • 불필요한 연산을 방지해주는 훅이다. 의존성 배열을 등록해 해당 변수가 바뀌었을 때 연산하도록 해준다.

  • Rendering 과정에서 특정 값이 바뀌었을 때만 연산을 실행한다. const functionName= useMemo(callback, [dependency])
  • 두 번째 인자로 받은 [dependency] 가 바뀔 때 callback 실행.
    • 실행한 callback의 값을 return한다.
import { useMemo, useState } from "react";

export default function UseMemoEx() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  //불필요한 연산을 방지, 값을 기억을 한다.
  //calc 함수 자체는 [count]의 변경이 있을 때 만 다시 연산하여 calc에 담아둔다.
	//[count]는 useState로 배열로 반환되기 때문에 배열 형태로 넣어주면 된다.
  const calc = useMemo(() => { console.log("calc.....✅");
    return count * 2 }, [count]);

  return (
    <>
      <div>
        const: {count}
        <button onClick={() => setCount(count + 1)}> + 1 </button>
      </div>
			<div>위에서 선언한 {calc} 자체가 들어가게 된다.[count]의 변경이 있을 때 만!  </div>
      <div>calc : {calc} </div>
      <input type="text" value={text} onChange={(e) => setText(e.target.value)} />
    </>
  );
}

useCallback()

  • useMemo와 유사하다.
    • useMemo()에서는 값을 최적화하지만 다시 Redering 될 때 함수를 다시 불러오는 것을 막는다. (특정 값을 기억하여 불필요한 연산을 방지)
    • useCallback()에서는 메모지에이션 된 함수를 반환한다! (특정 함수를 기억하여, 불필요한 재선언을 방지)
  • useMemo()는 특정 결과값을 재 사용할 때 사용, useCallback()은 특정 함수를 새로 만들지 않고 재 사용하고 싶을 때 사용한다고 생각하면 된다. 즉 함수를 기억해 주는 것!
    • 컴포넌트가 Redering 될 때 함수도 다시 선언된다. 이것도 비 효율적이다.

  • const memoizedCallback = useCallback(function, [dependency]); 의 형태이다.
  • 첫 번째 인자로 넘어온 함수를, 두 번째 인자로 넘어온 배열 형태의 함수 실행 조건의 값이 변경될 때까지 저장 해놓고 재사용 할 수 있게 한다.
    • 컴포넌트가 Redering 되더라도 [dependency] 가 바뀌기 전까지 기존 함수를 재사용 하는것.
      const add = () => x + y;
      
      //useCallback을 적용하면 아래처럼 된다.
      const add = useCallback(() => x + y, [x, y]);
//--------------------예시 1
import { useCallback, useState } from "react";

export default function UseCallbackEx() {
  const [text, setText] = useState("");

  //의존성 배열이 빈 값인 경우, 처음 마운트 될 때 선언된 함수를 계속 기억하고 있는다.
  //컴포넌트가 update 될 때 다시 선언하지 않고 기억하고 있는 함수를 사용한다.
  //컴포넌트 내부에서 변경될 수 있는 값은 대표적으로 state, props가 있다.
  //아래 handleOnChange 함수에서는 UseCallbackEx컴포넌트에서 유일하게 변경될 수 있는 값인 text를 활용하고 있지 않다.
  //그러니 함수도 굳이 계속 선언 될 필요가 없게 된다.
  const handleOnChange = useCallback((e) => {
    setText(e.target.value);
  }, []);

  return (
    <>
      <h3>UseCallback 공부</h3>
      <input type="text" value={text} onChange={handleOnChange} />
    </>
  );
}

//--------------------예시 2

//--------------------app.js내용
//const [postId, setPostId] = useState(1);
//<UseCallbackEx2 postId={postId} />
//<button onClick={() => setPostId(postId + 1)}> +1 </button>
//------------------------------------------------------------

import { useCallback, useEffect, useState } from "react";

export default function UseCallbackEx2({ postId }) {
  const [post, setPost] = useState();
  const fetchData = useCallback(async () => {
    const res = await fetch(
      `https://jsonplaceholder.typicode.com/posts/${postId}`
    );
		//위에서 불러온 응답을 json형태로 변환
    //버튼 클릭마다 postId + 1 되게 되고
    //[postId]가 바뀔 때 마다 json데이터도 결국 바뀌게 되는 것.
    const post = await res.json();
    setPost(post);
  }, [postId]);

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

  return (
    <>
      <h3>useCallback 공부 2</h3>
      <div>조회한 포스트 ID: {postId}</div>

      {post && (
        <div>
          <div>id: {post.id}</div>
          <div>title: {post.title}</div>
          <div>body: {post.body}</div>
        </div>
      )}
    </>
  );
}

도움 받은 링크

useReducer()

  • Reducer 란?
    • 현재 상태와 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 상태를 반환하는 함수이다. useState() 기반이다.
    • 간단한 상태 로직이면 useState() , 복잡한 상태라면 useReducer() 로 사용하면 편리하다. (도움 받은 링크)
- 컴포넌트의 업데이트 로직을 컴포넌트 외부로 뺄 수 있다.
- `useState()`의 대체 함수로, 다양한 컴포넌트 상황에 따라 상태값을 설정할 수 있다.
  • const [state, dispatch] = useReducer(reducer, initialValue);의 형태이다.

- `state` : 현재 상태
- `dispatch` : 액션을 발생시키는 함수. `reducer`를 실행시킨다.
- `reducer` : `state` 를 업데이트하는 함수. (**실질적으로 상태를 업데이트 하는 함수**이다. 액션을 발생  시키는 함수는 `dispatch` 이다. 결국 `**dispatch` 가 실행시키는 함수가 `reducer`** 인 것!)
- `initialValue` : initialState 상태의 초기값
  • 예시 1
    import React, {useReducer} from 'react';
    import './style.css';
    
    function reducer(state, action){
      // state값과 action의 type을 통해 reducer가 실행되기 때문에
      //현재 state 객체의 count에서 1을 빼고, 더하고의 값을 반환
      switch(action.type){
        case "decrement":
          return {count: state.count - 1};
      
        case "increment":
          return {count: state.count + 1};
      }
    }
    export default function App() {
      // useState기반이기 때문에 비슷한 형태.
      //[number, setNumber] 에서 결국 뒤의 함수가 세터 함수이고 상태 변경을 해준다.
      //그러니 뒤에 액션을 발생시키는 dispatch함수를를 작성해주면된다. 
      //그리고 useReducer로 상태를 관리하는거기 때문에 내부에 초기값 작성.
      const [number, dispatch] = useReducer(reducer, {count:0});
    
      return (
    <>
    {/* state의 number에 있는 count를 불러와야 하기 때문에 {number.count} */}
    <h1>Count: {number.count}</h1>
    {/* 클릭 시 dispatch함수를 실행한다. 지정된 type의. */}
    <button onClick={()=> dispatch({type:"decrement"})}> 빼기 </button>
    <button onClick={()=> dispatch({type:"increment"})}> 더하하기 </button>
    </>
      );
    }
  • 예시 2
    import { useReducer, useState, useCallback } from "react";
    
    const initialValue = { value: 0 };
    const reducer = (prevState, action) => {
      //---(3) 현재 state와 action값을 받아 새로운 state를 반환한다.
      //reducer는 매개변수 두개를 받는다. 1. 이전 state 2. 어떤 액션 할 지에 대한 정보
      //action값을 따라 state값을 변경해준다. 조건을 걸어서!
      switch (action.type) {
        case "PLUS":
          return { value: prevState.value + 1 };
        case "MINUS":
          return { value: prevState.value - 1 };
        case "RESET":
          return initialValue;
        case "MULTIFLY":
          return { value: prevState.value * action.number };
        case "DIVIDE":
          return { value: prevState.value / action.number };
        default:
          return { value: prevState.value };
      }
    };
    
    export default function UseReducer() {
      //---(1) reducer를 정의한다.
      //useReducer()내부에 있는 reducer, initialValue는 컴포넌트 외부로 뺀다. (상단에 선언)
      //dispatch를 통해 reducer가 실행된다. (액션을 발생시키는 함수)
      const [state, dispatch] = useReducer(reducer, initialValue);
      const [number, setNumber] = useState(0);
    
      const handleChangeNumber = useCallback((e) => setNumber(e.target.value), []);
    
      //---(2) dispatch를 통해 action값을 전달한다.
      //dispatch는 action값을 받아서 state와 함께 reducer에 전달한다.
      const plus = () => dispatch({ type: "PLUS" });
      const minus = () => dispatch({ type: "MINUS" });
      const reset = () => dispatch({ type: "RESET" });
      const multifly = () => dispatch({ type: "MULTIFLY", number: number });
      const divide = () => dispatch({ type: "DIVIDE", number: number });
    
      return (
        <>
          <div>
            {state.value}
            <button onClick={plus}>+1</button>
            <button onClick={minus}>-1</button>
            <button onClick={reset}>reset</button>
            <br />
            <input type="number" value={number} onChange={handleChangeNumber} />
            <button onClick={multifly}>곱하기</button>
            <button onClick={divide}>나누기</button>
          </div>
        </>
      );
    }
  • 왜 사용 할 까? useState() 도 있잖아.
    //만약에 곱하기 나누기 등 더 많은 연산을 이용한다고 하면? 또 컴포넌트 자체가 복잡해져서 코드가 길어진다면?
    //여기저기서 setState가 실행되고 호출된다 복잡해진다.
    //state의 변화를 추적하고 싶다면. 어떤 상황에 어떻게 state가 변하는가에 대해. setState를 일일이 찾아가면서 +1, -1이 되는구나~ 순차적으로 알 수 밖에 없다.
    
    //reducer를 사용한다면?
    //이 때 코드 효율성을 위해 사용한다. 더 찾기 편해진다. reducer만 보면 되니까.
    //결국 연산작업 자체를 한 곳에 몰아두는 것으로 생각하면 된다.
    
    const [state, setState] = useState(initialValue);
    const plus = ()=> setState({value : state.value + 1})
    const minus = ()=> setState({value : state.value - 1})
    const reset = ()=> setState(initialValue)
    }
✏️ **상황에 따라 적절한 hook 을 선택하자**
  • state가 단순하다면 useState()를 사용
  • state가 복잡하다면 useReducer()를 사용 (객체, 배열 같이 하위 요소가 많은 경우)

CustomHook

  • 컴포넌트에서 반복되는 로직이 많을 때 custom hooks을 이용하면 편하다.
  • 컴포넌트를 분할하는 것이 아니라, 컴포넌트의 로직 자체를 분할해서 재사용이 가능해진다!
  • 보통은 hooks/ 디렉토리 안에 커스텀 훅을 정의하여 사용
    • use로 시작하는 파일을 만드는 것 이 관례이다.
//--------------------customHook을 사용하는 파일

import { useState } from "react";

import useToggle from "../hooks/useToggle";

export default function CustomHookEx() {
  const [isPopup, togglePopup] = useToggle(false);

  return (
    <>
      <h3>CustomHook 공부</h3>
      {isPopup && <div>보인다</div>}
      <button onClick={togglePopup}>토글토글</button>
    </>
  );
}

//--------------------hooks 폴더 안에 작성한 커스텀훅 파일
import { useState } from "react";

//자주 쓰이는 toggle기능. true 이면 false로, false면 true로 변환시키는 상황.
//이에 따라 처리를 할 일이 자주 생긴다 라고 가정하고, 이 로직 자체를 훅으로 만드는 것!
export default function useToggle(initialValue) {
  const [value, setValue] = useState(initialValue);
  const toggle = () => {
    setValue(!value);
  };

  return [value, toggle];
}

🐱이럴 때 사용!

useCallback()

  • 특정 상황에만 함수가 실행되어야 할 때
  • 컴포넌트가 렌더링 될 때 마다 함수를 실행해야 하는 게 아니라면 useCallback 사용!
    • 컴포넌트가 Redering 되더라도 [dependency] 가 바뀌기 전까지 기존 함수를 재사용

useMemo()

  • 특정 상황에만 값이 return 되어야 할 때
  • 의존성 배열을 등록해 해당 변수가 바뀌었을 때 연산하도록 해준다.
    • 실행한 callback함수의 값을 return한다.

useRef()

  • DOM을 직접적으로 건드려야 할 때 사용. (input에 focus를 주거나 scrollBox를 조작한다거나 등)
  • 상태 변경을 추적할 필요가 있을 때

useContext():

  • 필요한 컴포넌트에서 그 값을 바로 가져와 사용해야 할 때!
    • 컴포넌트 - 컴포넌트로 props를 전달 할 필요가 없다.
  • Redux는 전역 상태 관리, Context는 컴포넌트에 전달만 해준다.

profile
프론트엔드 ㄱH발ㅈr ㅎH보ㅈr - ☆

0개의 댓글