3 minute read


useCallback - Typescript Version

  • 주로 렌더링 성능을 최적화해야 될 때 사용합니다

  • useCallback을 사용하면 함수를 재사용 할 수 있습니다.

  • 작성했던 Average 컴포넌트에는 onChange,onInsert 함수가 있는데,
    컴포넌트가 리렌더링 될 때 마다 두 함수가 새로 만들어 집니다.

  • 컴포넌트의 렌더링이 매우 많을 경우, 이는 최적화가 필요하게 됩니다.

Average.tsx

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

const getAverage = (numbers : number[]) : number => {
    console.log('getAverage 실행');
    if(numbers.length === 0) return 0;
    const sum = number.reduce((a, b) => a + b);
    return sum / numbers.length;
};

const Average = () : JSX.Element => {
    const [list, setList] = useState<number[]>([]);
    const [number, setNumber] = useState('');

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

    const onInsert = useCallback(() => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
    }, [number, list]);
    // number or list가 바뀔 때만 함수 생성
    // 함수 내부에 state 변수가 있으면 무조건 넣어주세요!

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

    return (
        (...)
    )
}
  • useCallback에 대해 설명해 보겠습니다 - 책을 참고

Example => useCallback(callback, [])

  • 1번째 파라미터 : 생성할 함수를 넣습니다.

  • 2번째 파라미터 : 배열에 들어간 변수가 변할 때에 함수를 재생성

    • 함수 내부에 state값이 있다면 99% 배열 안에 넣어줍니다.
    • 배열이 비었다면, 처음 렌더링에만 생성하여 재사용합니다.

useRef - Typescript Version

  • ref 속성은 HTML DOM 속성을 직접 건드리기 위해 필요한 속성입니다.

  • useRef 속성은 함수 컴포넌트에서 ref를 쉽게 사용하게 만들어 줍니다.


Average.tsx

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

const getAverage = (numbers : number[]) : number => {
    console.log('getAverage execute');
    if(numbers.length === 0) return 0;
    const sum = numbers.reduce((a, b) => a + b);
    return sum / numbers.length;
};

const Average = () : JSX.Element => {
    const [list, setList] = useState<number[]>([]);
    const [number, setNumber] = useState('');
    // 아직 'ref'가 지정되지 않았으므로, 초기화를 위해 null을 넣어줍니다.
    // null을 넣지 않으면 JSX 문법에서 오류가 납니다.
    const inputEl = useRef<HTMLInputElement>(null);

    const onChange = useCallback((e : React.ChangeEvent<HTMLInputElement>) => {
        setNumber(e.target.value);
    }, []); // 첫 렌더링 시에만 생성한다는 의미

    const onInsert = useCallback(() => {
        const nextList = list.concat(parseInt(number));
        setList(nextList);
        setNumber('');
        // 1. 에서 설명
        if(inputEl.current){
            inputEl.current.focus();
        }
    }, [numbers, list]);
    // numbers 그리고 list가 변경 될 때에만 재생성

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

    return (
        <div>
            <input value={number} onChange={onChange} ref={inputEl}>
            (...)
        </div>
    )
}
    • if(inputEl.current)는 왜 선언되었을까요?

    • useRef()로 선언할 때, useRef(null) 하였기에,
      null 값도 동시에 가지고 있습니다.
    • 근데, null값은 current 값을 가질 수 없습니다.
    • javascript는 자동으로 타입을 추정하여 오류를 일으키지 않지만,
      typescript는 사용하려는 객체가 null이 아니라는 것을 명시해야 합니다.

커스텀 Hooks 만들기

  • 책에 쓰여져 있는 내용을 바탕으로 Typescript를 적용하고 있습니다.

  • 책에 쓰여진 커스텀 훅은 추후에 배울 리덕스의 알고리즘과 어느정도 비슷합니다!

  • 굳이 작성할 필요가 있을까 고민했지만 필요할 분이 있을 거라 생각해서
    작성하겠습니다.

  • 밑의 예제는 여러 개의 <input>을 관리하는 커스텀 Hook입니다.

useInput.ts

import React, {useReducer} from 'react';

interface State{
    name : string;
    nickname : string;
}

interface Action{
    name : string;
    value : string
}

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

// custom hook Export!
export default function useInputs(initialForm : State) {
    const [state, dispatch] = useDispatch(reducer, initialForm);

    const onChange = (e : React.ChangeEvent<HTMLInputElement>) => {
        dispatch(e.target);
    }

    return [state, onChange];
}
  • 이 파일의 확장자명은 .ts입니다!

  • JSX문법으로 만들어진 컴포넌트를 반환하는 것이 아니기 때문입니다.

  • State, Action 인터페이스를 따로 빼둔 모습입니다.

  • export default 설정 되 있으므로, 이 파일을 import 하면
    기본으로 useInputs를 바로 사용할 수 있습니다.

Info.tsx

import React from 'react';
import useInputs from './useInputs';

const Info = () : JSX.Element => {
    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>
    )
}
  • 컴포넌트 코드 내부에서 useReducer를 사용하지 않고,
    외부 파일에 맡긴 모습을 보여줍니다.

  • 기존의 사용 방식과 동일하게 선언하면 됩니다.


Updated: