> Frontend/React

TIL-2024.04.30 - React - 006. Hooks

Janku 2024. 5. 1. 21:07

 

 

 

질문

1. Class Component 에서는 Life Cycle Methods를 사용해서 수명주기 동안 다양한 작업을 수행할 수 있는데, Functional Component에서는 어떻게 할 수 있어? 

2. 각각 종류를 알려줘. 

 

 

Hooks

- 함수형 컴포넌트에서 상태(state)와 다른 React 기능들을 사용 가능.

- 클래스 컴포넌트에서만 사용할 수 있던 기능들을 함수형 컴포넌트에서도 사용할 수 있음.

 

 

Hooks 종류

  • useState
  • useEffect
  • useReducer
  • useMemo
  • useCallback
  • useRef
  • useContext (Context API시 논의)
  • 커스텀 Hooks

 

 

useState

- 함수형 컴포넌트에서 상태를 추가 

- useState를 사용하면 함수형 컴포넌트에서도 상태를 관리

 

예제: 

import React, {useState} from 'react';


const Info = () => {
    // useState: 변수명, 설정 함수 , 초기 값
    const [name, setName] = useState('');
    const [nickname, setNickname] = useState('');

    const onChangeName = e => {
        setName(e.target.value);
    };

    const onChangeNickname = e => {
        setNickname(e.target.value);
    };


    return (
        <div>
            <div>
                <input type="text" value={name} onChange={onChangeName} placeholder="이름"/>
                <input type="text" value={nickname} onChange={onChangeNickname} placeholder="닉네임"/>
            </div>
            <div>
                <div>
                    <b> 이름 : </b> {name}
                </div>
                <div>
                    <b> 닉네임 : </b> {nickname}
                </div>
            </div>

        </div>
    )

};

export default Info;

 

 

useEffect

- 함수형 컴포넌트에서 부수 효과(side effects)를 수행 

- 예를 들어 데이터를 가져오거나 구독을 설정하는 등의 작업을 수행.

 

예제: 

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


const Info = () => {
    // useState: 변수명, 설정 함수 , 초기 값
    const [name, setName] = useState('');
    const [nickname, setNickname] = useState('');

    // useEffect는 기본적으로 랜더링되고 난 직후마다 실행되며, 두 번째 파라미터 배열에 무엇을 넣는지에 따라 실행되는 조건이 다름.

    // 1.  랜더링 될때 (생성 및 업데이트)될때마다 실행 > 값이 변경되면 재실행
    useEffect(() => {
        console.log('기본 useEffect', name, nickname);
    });

    // 2. 빈 [] > 마운트 될때만 실행 값이 변경되어도 재실행 X >
    useEffect(() => {
        console.log('[] 사용한 useEffect', name, nickname);
    }, []);

    // 3. []안 명시 > 특정 값이 업데이트될때만 실행
    useEffect(() => {
        console.log('특정 값이 업데이트 될때만 실행되는 useEffect', name, nickname);
    }, [name]);

    // 4. cleanUp -1 > 빈 배열 사용한 경우 : unMounted 될 경우 호출
    useEffect(() => {
        // 컴포넌트가 unMounted / update 되기 직전에 어떠한 작업을 수행하고 싶을 경우 cleanUp 함수 반환
        return () => {
            console.log("cleanUp 함수 사용하여, unMounted 되기 전에 실행:: ");
        };
    }, []);

    // 5. cleanUp -2 > [] 안 특정 값 사용: unMounted 될 경우 호출
    useEffect(() => {
        // 컴포넌트가 unMounted / update 되기 직전에 어떠한 작업을 수행하고 싶을 경우 cleanUp 함수 반환
        return () => {
            console.log("cleanUp 함수 사용하여, name 이 update 되기 전에 실행::", name);
        };
    }, [name]);


    const onChangeName = e => setName(e.target.value);
    const onChangeNickname = e => setNickname(e.target.value);


    return (
        <div>
            <div>
                <input type="text" value={name} onChange={onChangeName} placeholder="이름"/>
                <input type="text" value={nickname} onChange={onChangeNickname} placeholder="닉네임"/>
            </div>
            <div>
                <div>
                    <b> 이름 : </b> {name}
                </div>
                <div>
                    <b> 닉네임 : </b> {nickname}
                </div>
            </div>

        </div>
    );

};

export default Info;

 

 

useReducer

- 함수형 컴포넌트에서 Redux와 비슷한 방식으로 상태를 관리.

-  useReducer를 사용하여 복잡한 상태 로직을 더 쉽게 관리.

 

예제

import React, {useReducer} from "react";


/*
 * useReducer 는 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해 주고 싶을 때 사용.
 * reducer 는 현재 상태, 그리고 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 상태로 반환하는 함수.
 * reducer 에서 새로운 상태를 만들 때는 반드신 불변성을 지켜야 함.
 */


const reducer = (state, action) => {
    // action.type 에 따라 다른 작업 수행
    switch (action.type) {
        case 'INCREMENT':
            return {value: state.value + 1};
        case 'DECREMENT':
            return {value: state.value - 1};
        default:
            return state;
    }
};

const Counter = () => {
    /**
     *  useReducer(reducer 함수, 해당 리듀서의 기본값)을 parameter를 받아서, state & dispatch 함수를 받아옴.
     *  State > 현재 가리키고 있는 상태
     *  Dispatch >  Action 을 발생시키는 함수
     * */
    const [state, dispatch] = useReducer(reducer, {value: 0});

    return (
        <div>
            <p> 현재 카운터 값은 {state.value} 입니다</p>
            <button onClick={() => dispatch({type: 'INCREMENT'})}>+1</button>
            <button onClick={() => dispatch({type: 'DECREMENT'})}>-1</button>
        </div>
    )
};


export default Counter;

 

 

useMemo

- useMemo 훅은 계산 비용이 많은 연산의 결과를 기억하고, 의존성이 변경될 때만 재계산

- 주로 계산 비용이 많은 연산을 최적화하는 데 사용.

 

예제

import React, {useMemo, useState} from "react";


/*
 * useMemo > 내부에서 발생하는 연산을 최적화
 * useEffect 와 비슷하게, [] 안의 값을 줘서, 해당 값이 변경될 때만 수행
 */

const getAverage = (numbers) => {
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((a, b) => a + b);
    return sum / numbers.length;
};

const Average = () => {
    const [list, setList] = useState([]);
    const [number, setNumber] = useState('');
    const onChange = e => {
        setNumber(e.target.value)
    };
    const onClick = () => {
        const newList = [...list, parseInt(number)];
        setList(newList);
        setNumber('');
    };

    const listArr = list.map((item, index) => {
        return (<li key={index}>{item}</li>);
    });

    const avg = useMemo(() => getAverage(list), [list]);
    // []안의 내용이 바뀔 때만, getAverage 함수 수행. 여기서는 list 변할 때만, getAverage() 호출


    return (
        <div>
            <input type="number" value={number} onChange={onChange}/>
            <button onClick={onClick}>등록</button>
            <ul>{listArr}</ul>
            <div>
                {/*<b>평균값: </b> {getAverage(list)}*/}
                {/*이 상태로 사용되면, 인풋이 수정될 때마다 호출됨으로 메모리 낭비*/}
                {/* useMemo를 사용하여 최적화 작업 수행*/}
                <b>평균값: </b> {avg}
            </div>
        </div>
    );
};

export default Average;

 

 

useCallback

- useCallback 훅은 콜백 함수를 메모이제이션하여 의존성이 변경될 때만 새로운 콜백 함수를 생성.

- 주로 자식 컴포넌트에 콜백 함수를 전달할 때 사용되며, 불필요한 렌더링을 방지하여 성능을 최적화

 

예제

import React, {useMemo, useCallback, useState} from "react";


/*
 * useCallback > 랜더링 성능 최적화를 위해 사용되며, 컴포넌트가 리렌더링될 때마다, 새로 만들어진 함수를 사용해주는데 이를 막기 위해 사용되는 메모이제이션 기술 (함수의 반환 값을 캐시하여 동일한 인자가 입력될 때 이전에 계산된 값을 반환하는 기술)
 * 1번째 parameter > 생성하고 싶은 함수
 * 2번째 parameter > 배열을 넣어, 어떤 값이 바뀌었을 때 함수를 새로 생성하는지 명시
 * 빈 배열 [] 인 경우, 컴포넌트가 랜더링될 때 만들었던 함수를 계속 사용 (매 렌더링마다 새로 생성)
 * 배열안에 값을 넣을 경우 [number, list] 인 경우, number 혹은 list가 바뀔 때 새로 만들어진 함수를 사용
 */

const getAverage = (numbers) => {
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((a, b) => a + b);
    return sum / numbers.length;
};

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

    // 빈 배열을 사용하는 경우, 컴포넌트가 처음 렌더링될 때만 함수 생성
    const onChange = useCallback(e => {
        setNumber(e.target.value);
    }, []);

    // 차 있는 배열을 사용하는 경우,  number | list 가 변경될 때만,  새로 생성됨
    const onClick = useCallback(() => {
        const newList = [...list, parseInt(number)];
        setList(newList);
        setNumber('');
    }, [number, list]); // number | list 가 변했을 때만 수행

    const listArr = list.map((item, index) => {
        return (<li key={index}>{item}</li>);
    });

    const avg = useMemo(() => getAverage(list), [list]);
    // []안의 내용이 바뀔 때만, getAverage 함수 수행. 여기서는 list 변할 때만, getAverage() 호출

    return (
        <div>
            <input type="number" value={number} onChange={onChange}/>
            <button onClick={onClick}>등록</button>
            <ul>{listArr}</ul>
            <div>
                {/*<b>평균값: </b> {getAverage(list)}*/}
                {/*이 상태로 사용되면, 인풋이 수정될 때마다 호출됨으로 메모리 낭비*/}
                {/* useMemo를 사용하여 최적화 작업 수행*/}
                <b>평균값: </b> {avg}
            </div>
        </div>
    );
};

export default Average;

 

 

useRef

- useRef 훅은 컴포넌트의 렌더링과 상관없이 변수를 유지하고자 할 때 사용

- 주로 DOM 요소의 참조를 보관하거나, 이전 값을 유지하는 데 사용

 

예제

import React, {useMemo, useState, useCallback, useRef} from "react";


/*
 * useRef > 함수형 컴포넌트에서 ref를 더 쉽게 사용할 수 있도록 함.
 * ref 안의 값이 바뀌어도, 컴포넌트가 렌더링되지 않음.
 * 목표: 등록 버튼을 클릭한 경우, 포커스가 인풋쪽으로 넘어가도록 함
 */


const getAverage = (numbers) => {
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((a, b) => a + b);
    return sum / numbers.length;
};

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

    // 빈 배열을 사용하는 경우, 컴포넌트가 처음 렌더링될 때만 함수 생성
    const onChange = useCallback(e => {
        setNumber(e.target.value);
    }, []);

    // 차 있는 배열을 사용하는 경우,  number | list 가 변경될 때만,  새로 생성됨
    const onClick = useCallback(() => {
        const newList = [...list, parseInt(number)];
        setList(newList);
        setNumber('');
        inputEl.current.focus(); // 객체 안의 current 값이 실제 element를 가리켜, 포커스가 인풋쪽으로 넘어감.
    }, [number, list]); // number | list 가 변했을 때만 수행

    // []안의 내용이 바뀔 때만, getAverage 함수 수행. 여기서는 list 변할 때만, getAverage() 호출
    const avg = useMemo(() => getAverage(list), [list]);


    const listArr = list.map((item, index) => {
        return (<li key={index}>{item}</li>);
    });

    return (
        <div>
            <input type="number" value={number} onChange={onChange} ref={inputEl}/>
            <button onClick={onClick}>등록</button>
            <ul>{listArr}</ul>
            <div>
                {/*<b>평균값: </b> {getAverage(list)}*/}
                {/*이 상태로 사용되면, 인풋이 수정될 때마다 호출됨으로 메모리 낭비*/}
                {/* useMemo를 사용하여 최적화 작업 수행*/}
                <b>평균값: </b> {avg}
            </div>
        </div>
    );
};

export default Average;

 

 

custom Hooks (using useReducer)

 

예제

import React, {useReducer} from "react";

/*
 * customHooks > 스스로 만든 Hooks
 * 목표: 여러 개의 인풋을 관리하기 위해 useReducer로 작성했던 로직을 useInputs라는 Hook으로 분리
 */


const reducer = (state, action) => {
    return {
        ...state,
        [action.name]: action.value
    };
};


const useInputs = (initialForms) => {
    const [state, dispatch] = useReducer(reducer, initialForms);
    const onChange = e => dispatch(e.target);
    return [state, onChange];
};

export default useInputs;

 

 

 

답변

1. Class Component 에서는 Life Cycle Methods를 사용해서 수명주기 동안 다양한 작업을 수행할 수 있는데, Functional Component에서는 어떻게 할 수 있어? 
- Hooks를 통해 Class Component의 Life Cycle Methods 처럼 사용할 수 있음. 

2. 각각 종류를 알려줘. 

- useState, useEffectm useReducer, useMemo, useCallback, useRef, custom Hooks