ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL-2024.04.30 - React - 006. Hooks
    > Frontend/React 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

     

     

     

    댓글

Designed by Tistory.