본문 바로가기

프론트엔드(Front-End)/React

[REACT] 리액트와 훅(HOOK)

리액트의 훅(Hook)이란?

  • 리액트의 함수형 컴포넌트는 클래스형 컴포넌트와 같은 문법으로 state를 생성, 작성, 수정 등이 어렵습니다.
  • 그래서 함수형 컴포넌트에서도 클래스형 컴포넌트처럼 state나 여러 값들 그리고 각종 가변적인 상황에 대해 대응할 수 있도록 Hook이라는 함수를 통해 값을 다룰 수 있도록 지원합니다.

useState

  • state를 관리하기 위한 함수입니다.
  • 자바스크립트의 destructuring 문법으로 변수, set 함수를 할당하여 사용합니다.
import { useState } from "react";

const MyUseStateComp = () => {
  // 카운터
  const [value, setValue] = useState(0);
  // 입력창
  const [name, setName] = useState("");
  const [nickName, setNickName] = useState("");
  // 함수 선언
  const handleChangeName = (e) => {
    setName(e.target.value);
  };
  const handleChangeNickName = (e) => {
    setNickName(e.target.value);
  };
  const handleNameKeyDown = (e) => {
    if (e.key === "Enter") {
      alert(`name : ${name}`);
      setName("");
    }
  };
  const handleNickNameKeyDown = (e) => {
    if (e.key === "Enter") {
      alert(`nick-name : ${nickName}`);
      setNickName("");
    }
  };

  return (
    <>
      <div className="comp func-comp hook-comp">
        <h1> useState </h1>
        <p>
          {" "}
          <b>value</b> : {value}{" "}
        </p>
        <button
          onClick={() => {
            setValue(value + 1);
          }}
        >
          +1
        </button>
        <button
          onClick={() => {
            setValue(value - 1);
          }}
        >
          -1
        </button>
        <br />
        <input value={name} onChange={handleChangeName} onKeyDown={handleNameKeyDown} />
        <input value={nickName} onChange={handleChangeNickName} onKeyDown={handleNickNameKeyDown} />
        <p>name : {name}</p>
        <p>nickName : {nickName}</p>
      </div>
    </>
  );
};

export default MyUseStateComp;
  • 렌더링된 페이지


usetEffect

  • 컴포넌트 내부의 값이 변경될 때, 새롭게 화면을 렌더링 하는 데에 사용됩니다.
  • 이벤트 리스너와 같은 용도로 사용가능합니다.
  • 컴포넌트 생성 시 단 한번 초기화 하고 동작하지 않는 초기화 함수 용으로 사용 가능합니다.
import {useState, useEffect} from "react";

const MyUseEffectComp = () => {
    const [name, setName] = useState('');

    useEffect(()=>{
        console.log('렌더링 완료!');
    });

    // 마운트 시에만 사용하고 싶을 때
    useEffect(()=>{
        console.log('렌더링 완료!');
    },[]);

    // 일반적인 용례    
    useEffect(()=>{
        console.log('렌더링 완료!');
    },[name]); // 배열안에 들어가는 값이 변할때!

    const handleKeyDown = e => {
        if(e.key==='Enter'){
            alert(`name : ${name}`);
            setName('');
        }
    }

    return(
        <>
            <div className="comp func-comp hook-comp">
                <h1>useEffect</h1>
                <input value={name} 
                onChange={(e)=>{setName(e.target.value)}}
                onKeyDown={handleKeyDown}
                />
                <p> name : {name} </p>
            </div>
        </>
    )
}

export default MyUseEffectComp;
  • 렌더링 된 페이지

  • 입력 값이 바뀌어 name 값이 바뀔대마다 콘솔에 로그가 찍히는 모습

 


 

useReducer

  • 첫번째 매개변수로 state와 action을 매개변수로 받는 함수를 줍니다.
  • 이 함수에서 action은 type 프로퍼티를 가지므로 이를 참조하여 조건을 분기 할 수도 있습니다.
  • 클래스형 컴포넌트에서 이벤트 핸들러 하나로 특정 이벤트에 대해 대응하는 식으로 소프트코딩을 하고 싶을 때 함수형 컴포넌트에서는 useReducer를 통해 구현 가능합니다.
import {useState, useReducer} from "react";

function reducerFunc(state, action){
    // action의 type에 따라 분기
    switch(action.type){
        case 'INC':
            return {value : state.value +1};
        case 'DEC':
            return {value : state.value -1};
        default:
            return state;
    }
}

// 이벤트 핸들러 하나로 처리듯이 하는 문법
function reducerFunc2(state, action){
    return{
        ...state,
        [action.name]:action.value
    };
}

const MyUseReducer = () => {
    // const [state, dispatch] = useReducer(reducerFunc, {value:0});
    const [state, dispatch] = useReducer(reducerFunc2, {
        name:'',
        nickname:'',
    });

    const {name, nickname} = state;
    const handleOnChange = e => {
        dispatch(e.target);
    }
    

    return(
        <>
            <div className="comp func-comp hook-comp">
                <h1>Reducer</h1>
                <p>count : <b>{state.value}</b></p>
                <button onClick={()=>{dispatch({type:'INC'})}}>+1</button>
                <button onClick={()=>{dispatch({type:'DEC'})}}>-1</button>
                <hr></hr>                
                <input name="name" value={name} onChange={handleOnChange} />
                <input name="nickname" value={nickname} onChange={handleOnChange} />
                <p>name : {name}</p>
                <p>nickname : {nickname}</p>
            </div>
            
        </>
    )
}

export default MyUseReducer;
  • 렌더링 된 페이지

 


 

useMemo

  • state 값이나 어떤 변수, 배열의 값이 변하지 않았을 경우 이미 계산된 데이터를 참조하여 렌더링 성능을 올려주는 Hook 함수입니다.
  • 가령 전체 리스트의 평균이 변화되거나, 숫자를 입력하고 평균이나 배열 내부 값의 변화가 있었을 때에만 데이터를 갱신하게 됩니다.
import {useState, useMemo} from "react";

const getAvg = numbers => {
    console.log('평균 계산중....');
    if(numbers.length === 0) return 0;
    const sum = numbers.reduce((a,b)=> a+b);
    return sum / numbers.length;
}

const MyUseMemo = () =>{
    const [list, setList] = useState([]);
    const [number, setNumber] = useState('');

    const handleOnChange = e => {
        setNumber(e.target.value);
    };

    const onInsert = () => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
    }

    // useMemo
    const avg = useMemo(()=>getAvg(list), [list]); 
    // 이렇게 해두면 렌더링 될때마다 함수가 호출되지 않는다.

    return(
        <>
            <div className="comp func-comp hook-comp">
                <h1>useMemo</h1>
                <input 
                value={number} 
                onChange={handleOnChange}
                />
                <button onClick={onInsert}>등록</button>
                <p>평균 : <b>{avg}</b></p>
                <ul>
                    {list.map((value, index)=>(
                        <li key={index}>{value}</li>
                    ))}
                </ul>
            </div>
        </>
    )
}

export default MyUseMemo;
  • 렌더링 된 페이지

 


 

 

useCallback

  • useMemo + useEffect를 합쳐놓은 듯한 Enhanced Hook라고 볼 수 있습니다.
  • useEffect 처럼 컴포넌트 생성 시 생성자처럼 state를 초기화하는 데 사용하거나
  • useMemo처럼 state 값에 변화가 있을 때에만 작동하는 함수를 작성하는 데 응용 가능합니다.
import {useState, useMemo, useCallback} from "react";

const getAvg = numbers => {
    console.log('평균 계산중....');
    if(numbers.length === 0) return 0;
    const sum = numbers.reduce((a,b)=> a+b);
    return sum / numbers.length;
}

const MyUseCallback = () => {
    const [list, setList] = useState([]);
    const [number, setNumber] = useState('');
    
    // useCallback
    const handleOnChange = useCallback(e=>{
        setNumber(e.target.value);
    }, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성

    const onInsert = useCallback(()=>{
        if(isNaN(number) || number=='' || number == null){
            alert('잘못 값을 입력하셨습니다.');
            setNumber('');
            return;
        }
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
    }, [number, list]); // number 또는 list가 바뀌었을 때만 작동!

    const avg = useMemo(()=>getAvg(list), [list]);

    return(
        <>
            <div className="comp func-comp hook-comp">
                <h1>useCallback</h1>
                <input 
                value={number} 
                onChange={handleOnChange}
                />
                <button onClick={onInsert}>등록</button>
                <p>평균 : <b>{avg}</b></p>
                <ul>
                    {list.map((value, index)=>(
                        <li key={index}>{value}</li>
                    ))}
                </ul>
            </div>
        </>
    )
}

export default MyUseCallback;
  • 렌더링 된 페이지

 


 

useRef

  • ref를 관리하기 위한 함수입니다.
  • 내부 변수를 선언하는 데에도 응용될 수 있습니다.
import {useState, useMemo, useCallback, useRef, useEffect} from "react";

const getAvg = numbers => {
    console.log('평균 계산중....');
    if(numbers.length === 0) return 0;
    const sum = numbers.reduce((a,b)=> a+b);
    return sum / numbers.length;
}

const MyUseRef = () => {
    const [list, setList] = useState([]);
    const [number, setNumber] = useState('');
    const inputEl = useRef(null);

    // useCallback
    const handleOnChange = useCallback(e=>{
        setNumber(e.target.value);
    }, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성

    const onInsert = useCallback(()=>{
        if(isNaN(number) || number=='' || number == null){
            alert('잘못 값을 입력하셨습니다.');
            setNumber('');
            return;
        }
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
        inputEl.current.focus(); // useRef로 추가된 부분!;
    }, [number, list]); // number 또는 list가 바뀌었을 때만 작동!

    const avg = useMemo(()=>getAvg(list), [list]);

    // useRef를 사용하면 로컬변수를 사용할 수 있습니다.
    // 단, 로컬 변수는 바뀌더라도 리렌더링 되지 않으므로
    // 렌더링이 필요 없는 변수에 한해서 사용해야 합니다.
    // 변수에 useRef를 통해 할당한 후 current를 통해 접근하는 식으로 사용합니다!
    const id = useRef(1);
    const setId = (n) => {
        this.id = n;
    }
    const printId = () => {
        console.log(`id : ${id.current}`); 
    }

    useEffect(()=>{
        printId();
    }, [list]);

    return(
        <>
            <div className="comp func-comp hook-comp">
                <h1>MyUseRef</h1>
                <input
                ref={inputEl} 
                value={number} 
                onChange={handleOnChange}
                />
                <button onClick={onInsert}>등록</button>
                <p>평균 : <b>{avg}</b></p>
                <ul>
                    {list.map((value, index)=>(
                        <li key={index}>{value}</li>
                    ))}
                </ul>

            </div>
        </>
    )
}

export default MyUseRef;
  • 렌더링 된 페이지