유용하게 쓸 수 있는 React Hook 만들기 1

Hyuno Choi·2021년 2월 12일
0
post-thumbnail

소스 코드: https://github.com/soonitoon/React_Hooks

이 글은 Nomad Coder"실전형 리액트 Hooks 10개" 강의를 바탕으로 작성되었습니다.


2021년 2월 12일 밤

최근까지 트위터 클론 코딩 결과물 업데이트⬆️를 하다가 정말 오랜만에 새 강의를 수강하게 되었습니다. 이번에는 완성형 프로젝트가 아닌, 유용하게 쓸 수 있는 React Hook를 여러 개 만들어보는 내용입니다.

사실 리액트에 대한 기초 없이 쭉 프로젝트를 진행해왔기 때문에 제 머릿속 군데군데에 리액트에 대한 지식이 비어있는 부분이 많았습니다. 이번 강의를 듣고 리액트를 어떻게 하면 잘 활용할 수 있을지 배울 계획입니다!🎉

useState

리액트 후크를 만들기 전에 useState부터 다시 되짚어보고 가야겠습니다.

리액트 공식 문서에 따르면, useState는 state 변수, 해당 변수를 갱신할 수 있는 함수 이 두 가지 쌍을 반환합니다.

따라서 useState에서 반환하는 값은 배열로 받을 수 있습니다.

const [count, setCount] = useState(0);

배열이므로 만약 state에만 접근하고 싶다면

const item = useState(1)[0];

이러한 인덱싱으로도 접근이 가능합니다.

리액트 후크가 생긴 덕분에 클라스 컴포넌트에 비해 문법면에서도, 코드 길이 면에서도 정말 쓰기 편해졌습니다.😃

useInput

처음으로 만든 후크는 useInput입니다. 페이지를 만들다 보면 사용자로부터 입력값을 받아야 할 일이 많은데요, 그때마다 state를 만들고 input 태그의 onChange 속성과 연결할 함수를 만들기는 매우 귀찮습니다.😖

이 과정을 useInput 이라는 Hook를 만들어 캡슐화하려고 합니다(지금까지는 Carbon이라는 사이트에서 코드 사진을 만들어서 첨부했는데 지금보니까 마크다운 코드 블럭이 훨씬 깔끔한 것 같습니다.).

const useInput = (init) => {
  // state 세팅
  const [value, setValue] = useState(init);
  // 핸들러 함수 작성
  const onChange = (event) => {
    const {
      target: { value }
    } = event;
    setValue(value);
  };
  return {
    value,
    onChange
  };
};

useInput 함수입니다. init 매개변수로 초기값을 받아 value state를 세팅해줍니다.
그리고 input 태그의 onChange 속성과 연동할 핸들러 함수를 만들어줍니다. 사용자가 input 태그에 입력한 value를 구조 분해 할당으로 가져온 후, 변경된 내용을 useInput 내부의 state에 업데이트 해줍니다.

useInput 함수는 최종적으로 value, onChange를 return합니다.

useInput 확장

위에서 만든 useInput 함수에 유효성 검사 함수를 추가했습니다. 이렇게 되면 input에서 사용자가 입력하지 말아야 하는 형식이나 값을 사전에 방지할 수 있습니다.

const useInput = (init, verification) => {
  const [value, setValue] = useState(init);
  const onChange = (event) => {
    const {
      target: { value }
    } = event;
    // 사용자 입력값 반영 여부 설정
    let willUpdate = true;
    // 검증 함수에 value를 넣어서 true/false 반환 받음
    if (typeof verification === "function") {
      willUpdate = verification(value);
    }
    // true일 경우에만 state 업데이트
    if (willUpdate) {
      setValue(value);
    }
  };
  return {
    value,
    onChange
  };
};

원래의 useInput 함수에서 검증 함수를 verification 매개변수로 받습니다. 올바른 함수인지 검사한 후에 사용자 입력값을 검증 함수에 넣어 검증 결과를 wiiUpdate 변수에 저장함.
만약 willUpdate가 true라면(혹은 true로 변환할 수 있다면) 사용자 입력값을 세팅하고, 아니라면 무시합니다.
이 두 번째 매개변수 덕분에 useInput 사용시 필터링 하고싶은 입력값이나 입력값 길이 제한을 손쉽게 구현할 수 있습니다.

const App = () => {
  // input 검사 함수 작성
  const verifyName = (value) => value.length <= 10;
  // useInput에 인자를 넘겨주고 return 값 저장
  const name = useInput("Mr.", verifyName);
  return (
    <div className="App">
      <h1>{name.value}</h1>
      <input {...name}></input>
    </div>
  );
};

컴포넌트에서 useInput을 사용하는 예시입니다. h1 태그에서 name.value로 사용자 입력값을 실시간으로 표시하고, input 태그의 value, onChange 속성에 전개 구분으로 동명의 값을 할당해줍니다.

useTabs

리액트에서 탭간 전환을 간편하게 구현할 수 있는 후크입니다. 버튼을 누르면 탭을 넘기는 등의 작업에 사용할 수 있습니다.

// 탭 데이터
const contents = [
  { context: 0, content: "Page 1" },
  { context: 1, content: "Page 2" }
];

const useTabs = (init, array) => {
  // 탭 정보가 들어있는 array 검사
  if (!array || !Array.isArray(array)) {
    return;
  }
  // 탭 인덱스 state 세팅
  const [currentTab, setCurrentTab] = useState(init);
  // 버튼을 눌렀을 때 인덱스 업데이트
  const onTabClick = (value) => {
    setCurrentTab(value);
  };
  return {
    currentContent: array[currentTab],
    onTabClick
  };
};

useTabs는 초기 탭 인덱스인 init, 탭 배열인 array를 매개변수로 받습니다. 함수 처음에 배열을 검사해서 배열이 없거나(혹은 비었거나) 배열이 아니라면 함수를 종료합니다.
탭 인덱스를 저장할 state를 만들고 인덱스는 init 값으로 초기화됩니다.

버튼 태그와 연동하기 위해서 onClick 속성과 연결할 onTabClick 함수를 만들어줍니다. 버튼으로부터 탭 인덱스 값을 인자로 받아 state를 업데이트 합니다.

useTabs 함수는 onClick함수와 currentContent를 최종적으로 반환합니다. currentContent는 매개변수로 받아 온 탭 array에서 현재의 탭번호로 인덱싱한 값입니다.

const App = () => {
  const { currentContent, onClick } = useTabs(0, contents);
  return (
    <div className="App">
      {contents.map((item, index) => {
        return <button onClick={() => onTabClick(index)}>{item.context}</button>;
      })}
      <div>{currentContent.content}</div>
    </div>
  );
};

컴포넌트 내에서 useTab()에 초기값으로 0, 탭 배열으로 content를 넘겨줍니다. 그리고 반환받은 객체에서 currentContent, onClick을 뽑아옵니다.
그리고 버튼 태그의 onClick 속성에 onTabClick 함수를 연결합니다.
이때 onTabClick 함수가 바로 호출되는 것을 막기 위해 화살표 함수로 map의 index 값을 인자로 넘겨줍니다.

본문에는 currentContent.content를 가져와서 현재 인덱스에 해당하는 탭을 가져옵니다.

useEffect

useEffect를 사용하는 후크를 만들기 전에 먼저 복습부터 해봅시다!👋 useEffect는 기존 클래스 컴포넌트의 componentDidMountcomponentWillUnMountcomponentWillUpdate가 합쳐진 것으로 볼 수 있습니다. 첫 번째 인자로는 side-effect로 실행할 콜백 함수를 받고, 두 번째 인자로는 배열을 받습니다.
빈 배열이라면 컴포넌트가 랜더링 된 직후 딱 한 번만 실행되고, 배열 안에 state를 넣어주면 그 state가 업데이트 될 때마다 실행하게 됩니다.

useTitle

페이지 내에서 이동이 있을 때 title을 쉽게 바꿀 수 있게 해주는 후크 입니다. 매번 HTML 의 title 요소에 접근할 필요 없이 간편하게 브라우저 창에 뜨는 타이틀을 바꿀 수 있습니다.

const useTitle = (init) => {
  const [title, setTitle] = useState(init);
  const updateTitle = () => {
    const HTML_title = document.querySelector("title");
    HTML_title.innerText = title;
  };
  // title이 업데이트 될 때마다 실행함.
  useEffect(updateTitle, [title]);
  return setTitle;
};

title state를 만들고 초기값을 매개변수 init으로 받습니다. 그리고 타이틀 업데이트를 위한 updateTitle함수를 만들어줍니다. querySelector로 HTML의 title을 가져오고 innerText를 통해 내부 텍스트를 title state로 바꿔줍니다.

이 함수를 useEffect에 넘겨줘서 side-effect로 실행될 수 있게 합니다. 이때 두 번째 인자로 title state를 넘겨줘서 setTitle 함수를 호출할 때마다 updateTitle이 실행되도록 해줍니다.

마지막으로 setTitle을 반환해서 컴포넌트 내에서 사용할 수 있게 해줍니다.

const changeTitle = useTitle("Loading...");
  setTimeout(() => changeTitle("Home"), 5000);

나중에 컴포넌트 내부에서 이런 식으로 사용하면 결과적으로 title이 업데이트 될 때마다 HTML title에 자동으로 반영됩니다.

useClick

HTML 요소와 클릭 시 동작 함수를 간편하게 연결해주는 후크입니다.🖱️(JSX 태그 속성 중 onClick이 있지만 함수형 프로그래밍을 연습도 할 겸 만들어 봤습니다.)

const useClick = (handleClick) => {
  // 인자로 받은 함수 검사
  if (typeof handleClick !== "function") {
    return;
  }
  const element = useRef();
  useEffect(() => {
    // 컴포넌트 렌더링 직후 실행할 함수.
    if (element.current) {
      element.current.addEventListener("click", handleClick);
    }
    return () => {
      // 컴포넌트가 언마운트 될 때 실행할 함수.
      if (element.current) {
        element.current.removeEventListener("click", handleClick);
      }
    };
  }, []); // 한 번만 실행한다.
  return element;
};

useClick 함수는 클릭 핸들러 함수를 인자로 받습니다. 처음에 함수를 검사해서 만약 함수가 잘못되었으면 return으로 함수를 종료합니다.
다음으로 useRef()를 사용해서 연결할 태그의 레퍼런스를 준비합니다. 이후 마지막에 이 레퍼런스를 반환해서 컴포넌트 내에서 레퍼런스를 태그의 ref 속성에 연결할 수 있도록 해줍니다.

useEffect 안에 핸들러 함수를 목표 태그의 레퍼런스에 이벤트 리스너로 등록합니다.

더이상 필요하지 않게 된다면 이벤트 리스너를 지워줘야 하는데요, 이것 역시 useEffect로 간단하게 구현할 수 있습니다. useEffect에 인자로 넘겨준 함수가 반환하는 함수는 compoenetWillUnMount 때 실행되는데, 여기에는 이벤트 리스너를 지워주는 동작을 실행하는 함수를 만들어줌으로써 자원 낭비를 막아줍니다.

이벤트 리스너를 계속해서 달아줄 필요는 없기 때문에 useEffect의 두 번째 인자로 빈 배열을 넘겨줘서 한 번만 실행되도록 합니다.

const App = () => {
  const sayHello = () => console.log("hello");
  const clickEvent = useClick(sayHello);
  return (
    <div className="App">
      <h1 ref={clickEvent}>Hello</h1>
    </div>
  );
};

위에서 만든 useClick 후크를 사용하는 예시입니다. 함수에 넘겨줄 sayHello 핸들러 함수를 만들어 줍니다.
그리고 clickEvent 변수에 useClick이 반환하는 레퍼런스를 저장해줍니다. 이후 해당 태그의 ref 속성에 저장해둔 레퍼런스를 연결해줍니다.

useConfirm

useConfirm과 뒤에 작성할 usePreventLeave는 리엑트 Hook는 아닙니다. 하지만 이 두 함수 역시 함수형 프로그래밍 연습을 위해 만들어보았습니다.

어떤 동작을 하기 전에 사용자의 확인을 받을 때 브라우저의 confirm으로 브라우저 확인창을 띄우고 결과에 따라 동작할 코드를 작성하는데요, 더욱 간편하게 사용자 확인과 실행 동작을 연결할 수 있는 후크(비슷한 것🙃)를 만들어보았습니다!

// 확인창에 띄울 메시지, OK 했을 때 실행할 함수, Cancel 했을 때 실행 함수.
const useConfirm = (message = "Are you sure?", onConfirm, onCancel) => {
  if (typeof onConfirm !== "function") {
    return;
  }
  if (typeof onCancel !== "function") {
    return;
  }
  const handleConfirm = () => {
    if (confirm(message)) {
      onConfirm();
    } else {
      onCancel();
    }
  };
  return handleConfirm;
};

useConfirm은 세 개의 인자를 받습니다.

  1. 확인 메시지
  2. 승인 시 실행 함수
  3. 거절 시 실행 함수

두 함수는 함수 처음에 값을 검사해서 제대로 된 함수가 아니면 함수를 종료합니다.
그리고 마지막에 반환해줄 handleConfirm 함수를 작성합니다. 브라우저 확인창을 띄워서 ok하면 onConfirm 함수를, no하면 onCancel 함수를 실행합니다. 이 함수를 반환하여 컴포넌트에서 사용할 수 있게 합니다.

const App = () => {
  const onConfirm = () => console.log("Deleting world...");
  const onCancel = () => console.log("Canceled");
  const deleteConfirm = useConfirm("sure?", onConfirm, onCancel);
  return (
    <div className="App">
      <button onClick={deleteConfirm}></button>
    </div>
  );
};

사용 예시입니다. onConfirm, onCancel 함수를 작성하고, deleteConfirmuseConfirm이 반환하는 함수를 저장합니다. 이 함수를 버튼 태그의 onClick 속성에 연결하기만 하면 간단하게 확인 절차를 구현할 수 있습니다!😉

나머지 후크들은 다음 포스팅에서 이어서 작성하겠습니다.

profile
프론트엔드 웹 개발자를 목표로 하고 있습니다.

0개의 댓글