[React] Custom Hook

juno·2022년 9월 6일
0

React

목록 보기
4/5

1. 관심사의 분리(SoC, Separation of Concerns)

Aspiring Craftsman

핵심 키워드

  • 관심사 분리의 원칙

    관심사 분리 원칙은 시스템 요소가 베타성과 목적의 특이성을 가져야 한다고 말합니다.
    즉, 어떤 요소도 다른 요소의 책임을 공유하거나 관련 없는 책임을 포함해서는 안 됩니다.

  • 관심사 분리의 가치
    • 개별 구성 요소의 중복 및 단일 목적의 부족으로 인해 전체 시스템을 유지 관리하기가 더 쉽습니다.
    • 시스템 전체가 유지보수성 증가의 부산물로 더욱 안정적이 됩니다.
    • 각 구성 요소가 단일 집합의 응집력 있는 책임에만 관련되도록 하는 데 필요한 전략은 종종 자연스러운 확장성 지점을 초래합니다.
    • 구성 요소가 단일 목적에 집중하도록 요구함으로써 발생하는 디커플링은 구성 요소를 다른 시스템에서 더 쉽게 재사용하거나 동일한 시스템 내에서 다른 컨텍스트로 이어집니다.
    • 유지보수성 및 확장성의 증가는 시스템의 시장성 및 채택률에 큰 영향을 미칠 수 있다.

관심사의 분리 예



const loginForm = document.getElementsByClassName('loginForm')[0];
const loginBtn = document.getElementById('loginBtn');

const handleInput = () => {
  const idValue = document.getElementById('id').value;
  const pwValue = document.getElementById('pw').value;

  const isIdValid = idValue.length > 1;
  const isPasswordValid = pwValue.length > 1;
  const isLoginInputValid = isIdValid && isPasswordValid;

  loginBtn.disabled = !isLoginInputValid;
  loginBtn.style.opacity = isLoginInputValid ? 1 : 0.3;
  loginBtn.style.cursor = isLoginInputValid ? 'pointer' : 'default';
};

const init = () => {
  loginForm.addEventListener('input', handleInput);
};

init();

현재 위의 코드를 보면 handleInput은 id와 pw의 값이 있는지 없는지 유효성 검사를 진행하고, 버튼의 스타일을 변경하는 로직이다.

handleInput의 기능을 관심사별로 분리해 보겠다.

const loginForm = document.getElementsByClassName('loginForm')[0];
const loginBtn = document.getElementById('loginBtn');

// 유효성 검사 
const validateForm = () => {                                               
const idValue = document.getElementById('id').value;
const pwValue = document.getElementById('pw').value;

const isIdValid = idValue.length > 1;
const isPasswordValid = pwValue.length > 1;

return isIdValid && isPasswordValid;
}

// 버튼 활성화,비화성화 스타일
const handleButtonActive = (isButtonActive) => {                           
loginBtn.disabled = !isButtonActive;
loginBtn.style.opacity = isButtonActive ? 1 : 0.3;
loginBtn.style.cursor = isButtonActive ? 'pointer' : 'default';
}

// validateForm, handleButtonActive 실행 
const handleLoginInput = () => {                                           
const isFormValid = validateForm();
handleButtonActive(isFormValid);
}

const init = () => {
loginForm.addEventListener('input', handleLoginInput);
};

init();

위처럼 하나의 함수가 하나의 기능을 담당하도록 구현하면, 유호성 검사 조건을 변경해야 한다면
'validateForm'를 수정해주면되고, 버튼 스타일을 변경해야된다면 'handleButtonActive'를 찾아 수정해주면 된다.
이처럼 관심사를 분리하면 변경해야 하는 상황이 발생했을때, 해당 부분을 담당하는 함수만 찾아서
코드를 변경해주면 되기 때문에 유지보수에도 편하고 간단하게 수정 할 수 있다.

2. Custom Hooks

커스텀 훅(Custom Hook)은 이름이 use로 시작하는 자바스크립트 함수입니다.
커스텀 훅을 사용하면 지금까지 컴포넌트 내부에 한 덩이로 결합하여 사용했던 State와 Effect를 분리하여 사용할 수 있습니다.
마치 여러 벽돌을 끼워 맞춰 건물을 만들듯이 React 컴포넌트를 여러 Hook을 조합하는 방식으로 개발할 수 있게 됩니다.
또한 로직을 독립적인 함수로 분리함으로써 하나의 로직을 여러 곳에서 반복적으로 재사용할 수 있게 됩니다.

사용 예시

분리 전

import React, { useState } from 'react';

const UserUI = () => {
  const [isActive, setIsActive] = useState(false);

  const handleToggle = () => {
    setIsActive(prev => !prev);
  };

  return (
    <>
      <h1>현재 사용자의 상태입니다.</h1>
      <span>{isActive ? '사용 중' : '사용 안 함'}</span>
      <button onClick={handleToggle}>사용 상태 변경</button>
    </>
  );
};

export default UserStatus;

로직과 UI의 분리

import React, { useState } from 'react';

const useToggle = (initialValue = false) => {                     
  const [state, setState] = useState(initialValue);               

  const handleToggle = () => {                                    
    setState((prev) => !prev);
  };

  return [state, handleToggle];                                   
};


const UserUI = () => {                                      
  const [isActive, changeStatus] = useToggle();                   

  return (
    <>
      <h1>현재 사용자의 상태입니다.</h1>
      <span>{isActive ? '사용 중' : '사용 안 함'}</span>              
      <button onClick={changeStatus}>사용 상태 변경</button>
    </>
  );
};

export default UserStatus;

실전 적용

분리전

//Instructor.js

import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import * as S from './Styled.Instructor';
import useInsturctor from './useInstructor';

const Instructor = () => {
  
  const [instructorList, setInstructorList] = useState();

  useEffect(() => {
    const getInstructor = async () => {
      const gets = await axios('data/instructors/instructors.json');   // 분리 할 부분
      setInstructorList(gets.data);
    };
    getInstructor();
  }, []);

  return (
    <>
      <S.InstructorTitle>INSTRUCTORS</S.InstructorTitle>
      <S.SubTitle>안무가</S.SubTitle>
      <S.InstructorFlex>
        {instructorList?.map(({ id, url, name }) => (
          <Link to={`/instructorDetail/${id}`} key={id}>
            <S.Square>
              <S.ImgBox color={id}>
                <S.Img src={url} alt={name} />
              </S.ImgBox>
              <S.TestBox>
                <S.TestLine>{name}</S.TestLine>
                <S.TestNoLine />
                <S.TestLine></S.TestLine>
              </S.TestBox>
            </S.Square>
          </Link>
        ))}
      </S.InstructorFlex>
    </>
  );
};
export default Instructor;

출처 클론코딩 프로젝트
리턴 위에서 사용한 위 코드는 데이터를 get 하기위해 쓰이는 로직이다.
심지어 다른 파일에서도 이와 비슷한 로직을 많이 볼 수 있는데,
이를 Custom Hook으로 분리해서 관리 해보았다.

UI 부분


//Instructor.js

import { Link } from 'react-router-dom';
import * as S from './Styled.Instructor';
import useGetData from './useInstructor';  //분리한 로직 import

const Instructor = () => {

  const instructorList = useInsturctor('data/instructors/instructors.json');

  return (
    <>
      <S.InstructorTitle>INSTRUCTORS</S.InstructorTitle>
      <S.SubTitle>안무가</S.SubTitle>
      <S.InstructorFlex>
        {instructorList?.map(({ id, url, name }) => (
          <Link to={`/instructorDetail/${id}`} key={id}>
            <S.Square>
              <S.ImgBox color={id}>
                <S.Img src={url} alt={name} />
              </S.ImgBox>
              <S.TestBox>
                <S.TestLine>{name}</S.TestLine>
                <S.TestNoLine />
                <S.TestLine></S.TestLine>
              </S.TestBox>
            </S.Square>
          </Link>
        ))}
      </S.InstructorFlex>
    </>
  );
};
export default Instructor;

Data get 부분

//useGetData

import  { useEffect, useState } from 'react';
import axios from 'axios';

const useGetData = url => {
  const [instructorList, setInstructorList] = useState();

  useEffect(() => {
    const getData = async () => {
      const gets = await axios(url);
      setInstructorList(gets.data);
    };
    getData();
  }, [url]);

  return useGetData;
};

export default useInsturctor;

useGetData는 데이터 통신에서 가져오기를 구현한 로직이다.
이 함수를 호출할때, 'useInsturctor('data/instructors/instructors.json')' 이렇게 url만 바꿔주면 다른 파일에서도 한줄로 8줄을 대체하는 효과를 얻을 수 있다.

매듭 짓기

React에서 Hook의 동작을 처리하는 내부적인 규칙과도 관련이 되어 있고, 공식적인 컨벤션이기 때문에 커스텀 훅을 작성하실 때는 꼭 use- 로 시작하는 이름을 지어주셔야 한다고 한다.

간단한 예시였지만, 이처럼 자주 중복되는 코드의 규칙을 찾아 커스텀 훅으로 분리하면,
코드가 길어 질수록 가독성, 유지보수에 도움이 되지않을까 싶어서 작성하게 되었다.

profile
안녕하세요 인터랙션한 웹 개발을 지향하는 프론트엔드 개발자 입니다. https://kimjunho97.tistory.com => 블로그 이전 중

0개의 댓글