본문 바로가기
JavaScript/React

React) Hooks - useCallback, useRef, 커스텀 Hooks 만들기

by 박채니 2022. 11. 15.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.

 

Hooks - useCallback, useRef, 커스텀 Hooks 만들기

 

useCallback

- 렌더링 성능을 최적화할 때 사용

- 함수 재사용

 

Average.js

import { useState, useMemo } from "react";

const getAverage = numbers => {
    console.log('평균값 계산 중...');
    
    if(numbers.length === 0) return 0;
    const sum = numbers.reduce((agg, num) => agg + num);
    return sum / numbers.length;
}

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

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

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

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

    return (
        <div>
            <input value={number} onChange={onChange} />
            <button onClick={onInsert}>등록</button>
            <ul>
                {
                    list.map((value, index) => (
                        <li key={index}>{value}</li>
                    ))
                }
            </ul>
            <div>
                <b>평균값 : </b> {avg}
            </div>
        </div>
        
    )
}

export default Average;

현재 onChange와 onInsert 함수를 선언해 주었습니다.

하지만 이렇게 선언하면 컴포넌트가 리렌더링될 때마다 새로 만들어진 함수를 사용하게 됩니다.

추후 렌더링이 자주 발생하거나 컴포넌트 개수가 많아진다면 useCallback을 이용하여 최적화 해주는 것이 좋습니다.

 

Average.js - onChange, onInsert 

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

    const onInsert = useCallback(() => {
        const nextList = list.concat(parseInt(Number(number)));
        setList(nextList);
        setNumber('');
    }, [number, list]); // number 혹은 list가 바뀌었을 때만 함수 생성

useCallback의 첫 번째 파라미터에는 함수를 넣고, 두 번째 파라미터에는 배열을 넣어줍니다.

해당 배열을 통해 어떤 값이 바뀌었을 때 함수를 새로 생성해줄 지 결정되고, 빈 배열을 넣는다면 처음 렌더링될 때만 함수를 생성해줍니다.

 


useRef

- 함수 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해줌

 

Average 컴포넌트에서 '등록' 버튼 클릭 시, 포커스가 인풋으로 넘어가도록 해보겠습니다.

 

Average.js

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

const getAverage = numbers => {
    console.log('평균값 계산 중...');
    
    if(numbers.length === 0) return 0;
    const sum = numbers.reduce((agg, num) => agg + num);
    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);
    }, []); // 컴포넌트가 처음 렌더링될 때만 함수 생성

    const onInsert = useCallback(() => {
        const nextList = list.concat(parseInt(Number(number)));
        setList(nextList);
        setNumber('');
        inputEl.current.focus();
    }, [number, list]); // number 혹은 list가 바뀌었을 때만 함수 생성

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

    return (
        <div>
            <input value={number} onChange={onChange} ref={inputEl} />
            <button onClick={onInsert}>등록</button>
            <ul>
                {
                    list.map((value, index) => (
                        <li key={index}>{value}</li>
                    ))
                }
            </ul>
            <div>
                <b>평균값 : </b> {avg}
            </div>
        </div>
        
    )
}

export default Average;

등록 처리 후 포커스가 인풋 박스에 가있는 것을 확인할 수 있습니다.

이렇듯, useRef를 사용해 ref를 설정하면 useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킵니다.

 

로컬 변수 사용하기

 

로컬변수란?

렌더링 상관없이 바뀔 수 있는 값을 의미합니다.

 

클래스 형태로 작성된 컴포넌트의 경우, 로컬 변수를 사용해야할 때 아래와 같이 작성합니다.

import { Component } from "react";

class MyComponent extends Component {
    id = 1;
    setId = (n) => {
        this.id = n;
    }
    printId = () => {
        console.log(this.id);
    }

    render() {
        return (
            <div>
                MyComponent
            </div>
        )
    }
}

export default MyComponent;

 

이를 함수 컴포넌트로 작성해보겠습니다.

import { useRef } from "react";

const RefSample = () => {
    const id = useRef(1);

    const setId = (n) => {
        id.current = n;
    }
    const printId = () => {
        console.log(id.current);
    }
    return (
        <div>
            refsample
        </div>
    )
}

export default RefSample;

ref 안의 값이 변경되어도 컴포넌트가 렌더링 되지 않으므로, 렌더링과 관련되지 않은 값을 관리할 때만 위 방법을 사용해줍니다.

 


커스텀 Hooks 만들기

여러 컴포넌트에서 비슷한 기능을 공유할 경우, 커스텀 Hook으로 작성하여 로직을 재사용할 수도 있습니다.

 

useInputs.js

import { useReducer } from "react";

function reducer(state, action) {
    return {
        ...state,
        [action.name]: [action.value]
    }
}

export default function useInputs(initialForm) {
    const [state, dispatch] = useReducer(reducer, initialForm);

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

 

Info.js

import useInputs from "./useInputs";

const Info = () => {
    const [state, onChange] = useInputs({ name: '', nickname: '' });
    const { name, nickname } = state;

    return (
        <div>
            <div>
                <input name="name"  value={name} onChange={onChange} />
                <input name="nickname" value={nickname} onChange={onChange} />
            </div>
            <div>
                <div>
                    <b>이름 : </b> {name}
                </div>
                <div>
                    <b>닉네임 : </b> {nickname}
                </div>
            </div>
        </div>
    )
};

export default Info;

이처럼 커스텀 Hook을 이용하여 코드가 깔끔해진 것을 확인할 수 있습니다.