[React] useCallback의 onChangeEvent handler로 사용 최적화

2022. 8. 19. 16:20React

 

대부분의 웹페이지에 input box 하나는 있잖아요? 그쵸?

 

안녕하세요.

뭐 질질 끌어야 되나요? 그냥 바로 해보죠.

 

1. useCallback에 대해서

 

Hooks API Reference – React

A JavaScript library for building user interfaces

ko.reactjs.org

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

 

useCallback은 메모이제이션 된 콜백을 반환합니다.

 

엥? 그러면 그냥 함수랑 무슨 차인가요?

useCallback을 사용하면 불필요한 렌더링을 방지할 수 있습니다. 특히 onChangeEvent와 결합했을 때 더 심한데요.

e.target.value값이 변할 때 마다 state가 바뀌기 대문에 그 state가 쓰이는 부분이 계속 다시 렌더링되는 문제들이 생깁니다.

 

주의깊게 보실 것은 두 번째 인자로 있는 depth 배열입니다. ([a, b])

useRef로 사이드 이펙트 관리할 때 처럼 연관되어있는 요소를 depth에 넣어줘야하는데요.

만약 빈 배열로 넣게된다면 state 값이 최신으로 갱신되지 않습니다.

 

이런 onChangeHandler는 입력창의 갯수대로 만들어줘야하는데요. 같은 기능을 하는 함수를 매번 만드는 건 그냥 코드 길이만 늘릴 뿐입니다. 이럴 때 쓰라고 custom Hook이 있죠!

 

2. input의 onChangeHandler가 포함된 나만의 custom Hooks

import {useState, useCallback, Dispatch, SetStateAction, ChangeEvent} from "react";

const useInput = <T = any>(initialData: T): [T, (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void, Dispatch<SetStateAction<T>>] => {
  const [value, setValue] = useState(initialData);
  const handler = useCallback((e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
    setValue(e.target.value as unknown as T);
  }, []);
  return [value, handler, setValue];
};

export default useInput;

 

Generic으로 형성해서 원시 자료형이라면 다 공유할 수 있습니다.

다만, 따로 input에서 입력 제한이 없기 때문에 원하는 형태의 자료만 받는다던가 하는 조작이 필요해보이긴 하네요.

 

호출할 땐 원하는 곳에서 useState 부르듯이 이렇게 호출하면 됩니다.

 

import useInput from "../../hooks/useInput";

const Comp = () => {
    const [inputVal, onChangeInputVal, setInputVal] = useInput("");
    return <></>;
}

export default Comp;

 

사용자가 작성을 모두 마치고 버튼을 눌러서 서버로 POST 요청을 한다고 생각해볼까요?

역시 useCallback을 이용해야합니다.

여기에서는 depth에서 꼭 inputVal(의존성 있는 state)을 넣어주도록 합시다.

 

3. POST 요청을 보내는 useCallback

const onCreateWorkspace = useCallback((e: React.FormEvent) => {
    e.preventDefault();
    if (!inputVal || !inputVal.trim()) return;
    axios.post('YOUR_API_ADDRESS', {
      data: inputVal,
    }, {
      withCredentials: true,
    })
      .then((res) => {
        console.log(res.data);
        setInputVal('');
      }).catch((e) => {
      console.dir(e);
      toast.error(e.response?.data, {position: 'bottom-center'});
    })
  }, [inputVal]);

 

depth에 우리가 custom한 state가 들어가줬다면, onChange로 바뀐 값이 inputVal에 들어간게 확인 될겁니다.

혹시 비어있거나 안들어가있는 경우를 대비해

(혹은 스페이스바 등처럼 대충 치고 넘어가는 경우를 막기 위해)

한번 체크를 해줍니다.

 

또, 성공적으로 post에 성공했다면, 관련 state를 꼭 초기화 시켜줍시다. => setInputVal('') 이거요.

보냈는데 별다른 인터렉션 없이 input이 계속 채워져 있다면 사용자가 느끼기엔 안갔다고 느껴질 수도 있고,

혹시 이런 작업들이 modal에서 진행되었을 경우, 다시 모달을 호출했을 때, useCallback을 통해 재렌더링을 막아놨기 때문에 이전에 작성했던 state값이 그대로 노출될 수 있습니다.

 

 

오늘은 기초적인 onChange handling에 대해 알아봤습니다.

 

그럼 이만!