게시글

강좌16 - React - 22일 전 등록 / 19일 전 수정

Hook의 제약사항과 custom hook(React 17버전)

feat. HOC
이 포스팅들은 오래되었습니다. 유튜브에서 최신 리액트 강좌 강좌를 보시는 것을 추천합니다.

이번 시간에는 Hook의 제약사항에 대해 알아보고, custom hook도 같이 살펴보겠습니다.

useRef, useMemo, useState같이 React가 기본적으로 제공하는 hook들을 사용할 때 에러가 나는 상황을 가끔 겪게 됩니다. 예를 들어 if문 안에 useState를 쓰거나, useCallback 안에 useRef를 선언하거나, return 다음에 hook을 사용하는 경우가 있죠.

if (조건) {
  const [state, setState] = useState({});
} // 에러 1
const onChange = useCallback(() => {
  const [state, setState] = useState({});
}, []); // 에러 2
if (조건) {
  return <div>예제</div>;
}
const [state, setState] = useState({}); // 에러 3

위와 같은 예제들에서 에러의 공통점을 찾을 수 있으신가요? 공통점은 바로 useState같은 hook이 확실히 실행된다는 게 보장되어 있지 않다는 것입니다. if문 안에 선언하면 조건이 true여야 실행되고, useCallback 안에 있으면 함수가 실행되어야 실행되고, return문보다 아래 있으면 조건이 false여야 실행됩니다.

이와 같은 상황을 React는 용납하지 않습니다. React는 모든 Hook들이 확실히, 매번 같은 순서로, 매번 같은 개수로 실행되기를 원합니다. 따라서 함수 안이나 조건문 안에서는 훅을 선언할 수 없는 것이죠. 매번 같은 개수는 무엇을 말하는 걸까요? 반복문 내부에서 실행되면 안 된다는 뜻입니다. React에서는 map을 사용하는 경우가 많은데 이 때 map안에 Hooks을 넣으면 안 됩니다.

Hook을 쓸 때는 이러한 제약사항들을 모두 지키면서 코딩하셔야 합니다. 단, 함수 안에서 Hook을 못 쓰는 것에는 한 가지 예외가 있습니다. 바로 여러분이 직접 Hook을 만들어 쓸 때입니다. React에서 기본 제공하는 Hook 외에 여러분이 직접 Hook을 만들 수도 있는데요.

아래 예제를 먼저 봅시다. 흔하게 사용하는 form + input 예제입니다.

const Input = () => {
  const [value, setValue] = useState('');
  const onChange = useCallback((e) => {
    setValue(e.target.value);
  }, []);

  const [value,2 setValue2] = useState('');
  const onChange2 = useCallback((e) => {
    setValue2(e.target.value);
  }, []);

  return (
    <form>
      <input value={value} onChange={onChange} />
      <input value={value2} onChange={onChange2} /> 
    </form>
  )
}

input마다 value와 onChange가 붙어서 이에 해당하는 Hook들도 뭔가 중복되는 모습입니다. 중복을 제거하고 싶다면 저것들을 하나로 묶는 Hook을 만들면 됩니다.

const useInput = (initialValue = '') => {
  const [value, setValue] = useState(initialValue);
  const onChange= useCallback((e) => {
    setValue(e.target.value);
  });
  return [value, onChange, setValue];
}

우리가 직접 만드는 Hook들도 이름을 use+이름 형식으로 짓습니다. 이 커스텀 훅 내부에는 다른 훅을 사용할 수 있습니다. 이렇게 useInput 훅을 만들고 이걸 import해서 사용하면 됩니다.

const Input = () => {
  const [value, onChange] = useInput('');
  const [value2, onChange2] = useInput('');

  return (
    <form>
      <input value={value} onChange={onChange} />
      <input value={value2} onChange={onChange2} /> 
    </form>
  )
}

코드가 엄청 짧아졌습니다. 중복도 사라졌고요. 커스텀 훅 내부의 useState가 바뀌면 컴포넌트도 똑같이 리렌더링됩니다.

커스텀 훅이 나오면서 기존 HOC(고차 컴포넌트)를 사용했던 코드들이 모두 Hook으로 옮겨갈 수 있게 되었습니다. HOC로부터 넘겨받던 props가 많이 줄어들어서 훨씬 코드가 읽기 좋아졌습니다.

리액트 라우터의 코드를 보여드리겠습니다.

const Page = ({ match, location, history }) => { return null };
export default withRouter(Page);

예전에는 withRouter(react-router에서 제공)하는 HOC가 있었다면 그걸로 컴포넌트를 감싸서 관련 데이터(match, location. history)를 props로 넘겨받았습니다. 이러한 라이브러리들도 이제는 Hooks를 제공합니다.

const Page = () => {
  const match = useRouteMatch();
  const location = useLocation();
  const history = useHistory();
  return null;
}
export default Page;

이런 식으로 props로 받는 대신 훅을 통해 직접 그 값에 접근할 수도 있습니다. 다다수의 라이브러리가 Hook을 제공합니다. react-redux는 connectMapToState나 connectMapToDispatch 대신에 useSelector, useDispatch를 제공하고 아폴로도 client.query 대신 useQuery같은 훅을 제공합니다.

다음 시간에는 useContext 훅에 대해 알아보겠습니다!

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

댓글

아직 댓글이 없습니다.