본문 바로가기
TIL

관심사의 분리 / Custom Hook

by 은지:) 2022. 8. 28.
728x90
반응형

 

 

개발을 하면서 마주하는 고민들

 

 

코드를 작성했는데 기획, 디자인이 변경되고 요청 사항들이 추가된다.

코드를 수정해야하는데 기존의 코드가 복잡해서 이해하고 수정하기 어렵다

지금까지 만들었던 모든 코드를 다시 엎어야 하나?

 

이의 해결책은 바로 관심사의 분리이다

 

 

 

 

 

 

 

관심사의 분리

 

 

 

 

 

관심사의 분리란?

 

 

각각 관심사에 따라 코드를 분리하는 기법

일상 생활에도 여러가지 고민과 걱정들을 한번에 처리하려면 어렵다

한번에 한 가지 걱정만 하면 쉽게 해결할 수 있음

 

이처럼 개발에서도 코드가 하나의 걱정만 하도록 단위를 나눠서, 단위별로 하나의 걱정만하게 하는 것

 

특정한 변화에 대해서 대응하기 위해서 읽고 이해하고 수정해야하는 코드의 단위가 줄어들게 됨 => 유지 보수에 용이

 

우리의 일상 생활에서도 분업이라는 의미로도 생각됨

 

 

 

 

 

관심사 분리의 특징

 

 

소프트웨어 개발 원칙  : KISS

Keep It Simple, Stupid

 

 

하나의 코드에서 여러 기능을 담당하면 코드가 복잡해지고 변화와 확장이 어렵다

-> 하나의 코드가 하나의 기능을 담당하도록 구성하는 것이 중요

-> 관심사의 분리를 통해 하나의 코드가 하나의 기능을 담당하도록 구성하는 것

-> 코드가 간결해지고 목적이 명확히 드러남

 

하나의 기능을 담당하기에 여러 역할이 혼재된 코드보다 단위별로 재사용하기가 쉬워짐

변경사항이 발생했을 때 해당 관심사는 제대로 동작하고 있는지 테스트하기 쉬워짐

 

 

소프트웨어 설계에는 결합도와 응집도라는 두 가지 개념이 있다

관심사의 분리가 잘 된 소프트웨어는 낮은 결합도와 높은 응집도라는 특징이 있음

 

낮은 결합도 : 코드가 얽혀 있지 않고 관심사별로 독립적으로 잘 분리되어 있는 것

높은 응집도 : 동일한 목적을 가진 코드끼리 잘 모여있는 것

 

 

 

 

 

관심사가 잘 분리된 코드의 특징

 

 

코드가 짧고 간단해진다

재사용하기 쉬워진다

유지보수가 용이해진다

테스트 코드를 작성하는 것이 쉬워진다

낮은 결합도와 높은 응집도를 갖고 있다

 

 

 

 

 

react에서의 관심사의 분리

 

 

hooks 등장 이전

 

로직-API 호출, State 변경, 이벤트 핸들링

UI - JSX

 

Presentational - Container 패턴

로직을 담당하는 컴포넌트(Container)와 UI를 담당하는 컴포넌트(Presentational)를 분리해서 관리

컴포넌트 단위로만 관심사 분리할 수 있음

 

 

 

hooks 등장 이후

 

Hooks 등장 이후로 Presentational - Container 패턴은 더 이상 권장되지 않음

Hooks를 이용해 함수 컴포넌트에서 로직을 작성할 수 있게 됨

하지만 여전히 함수 컴포넌트 내부에 로직과 UI가 뒤섞여서 존재

-> 컴포넌트 내부에 코드가 길어져 가독성이 떨어짐

-> 작성한 로직을 다른 컴포넌트에서도 동일하게 사용해야하는 경우 동일한 코드를 다른 컴포넌트에서도 똑같이 작성해야됨 -> 이는 중복

-> 이를 해결하기 위해 재사용 가능한 함수 단위로 로직을 분리하려는 Custom Hook 등장

 

 

 

 

 

Custom Hook 의 정의 및 특징

 

Custom Hook은 이름이 use로 시작하고 다른 Hooks를 호출하는 자바스크립트의 함수

Custom Hook을 사용하면 컴포넌트에 결합되어 있던 State 와 effect를 함수 단위로 추출할 수 있음

 

-> 로직의 복잡한 내부 구조를 숨기고 단순한 함수의 호출 형태로 사용할 수 있음

-> 함수를 여러 컴포넌트에서 호출하는 방식으로 로직을 재사용할 수 있게 됨

-> 사용시 이름 앞에 무조건 use를 붙여야함

 

 

 

 

 

 

 

 

Custom Hook 예제

 

 

 

 

 

 

 

 useLockBodyScroll

 

 

import { useLayoutEffect } from 'react';                    // 렌더링 되기 전에 동작하는 훅

const useLockBodyScroll = () => {
  useLayoutEffect(() => {
    document.body.style.overflow = 'hidden';                // useLockBodyScroll 훅이 호출되면 body 태그 스타일 중 overflow를 hidden으로 해 스크롤 안되게 끔함
    return () => {
      document.body.style.overflow = '';                    // 모달 창이 닫히면 스크롤이 가능해야하므로 cleanUp effect로 돌려놓음
    };
  }, []);
};

export default useLockBodyScroll;

useLockBodyScroll 훅은 모달창이 띄워졌을 때 마우스 스크롤을 움직이면 모달 창 뒤에 있는 UI가 움직이지 않도록 막아주는 기능을 함

 

 

 

 

 

 useOutsideClick

 

import { useState, useRef } from 'react';                              // 1. useRef 특정 DOM 선택 가능
import useOnClickOutside from './useOnClickOutside.js'

const App = () => {
  const [isModalOpen, setModalOpen] = useState(false);                 // 2. useState 활용, 모달창을 열고 닫을 수 있게 함
  const ref = useRef();                                                // 3. ref 변수에 useRef 반환값 할당

  // 활용 예시
  useOnClickOutside(ref, () => setModalOpen(false));                   // 5. useOnClickOutside 두개의 인자를 받음
  //첫번째 인자는 ref를 받고 두번째 인자는 함수를 받음, modal 선택자를 바라보는 값을 첫번째 인자로 모달창을 관리하는 state를 false로 변경하는 함수를 두번째 인자로 보내줌


  return (
    <div className="backGround">
      {isModalOpen ? (
        <div ref={ref} className="modal">                              // 4. 모달창을 의미하는 modal 선택자에게 ref 할당, ref는 modal 선택자를 바라보게 됨
          👋 Hey, I'm a modal. Click anywhere outside of me to close.
        </div>
      ) : (
        <button onClick={() => setModalOpen(true)}>Open Modal</button>
      )}
    </div>
  );
}

export default App;

// useOnClickOutside 어떤 요소의 밖을 클릭했을 때 동작을 하게 되는 훅

 

 

// Custom Hook이 Side Effect만 일으키고 return 값이 없으면
// 굳이 리턴 값을 정의해주지 않아도 됨!

import { useEffect } from 'react';

const useOnClickOutside = (ref, handler) => {                         
  useEffect(() => {
      const listener = (event) => {                                  
        if (!ref.current || ref.current.contains(event.target)) {     // 2. 조건문을 통해 분기처리. 모달창에서 이벤트가 발생하면 아무런 동작을 하지 않고 끝내겠다는 것
          return;
        }
        handler();                                                    // 3. 두 가지 조건에 모두 충족하지 않다면 handler 함수를 호출
      };

      document.addEventListener("mousedown", listener);               // 1. 마우스를 누른 순간 listener event 발동
      return () => {
        document.removeEventListener("mousedown", listener);         
      };
    },

    [ref, handler]
  );
};

export default useOnClickOutside;

 

 

 

 

 

 

 

useFetch

 

// useFetch.js // 데이터를 요청하는 커스텀 훅

import { useState, useEffect } from "react";

const useFetch = (url) => {                                         // 1. api 주소 들어와야 함
  const [data, setData] = useState({});                           ⌉ // data State 는 fetch 메서드를 호출한뒤 반환하는 값을 담는 state
  const [loading, setLoading] = useState(true);                   ⎮ // 2. loding data 는 데이터를 받아오고 있는 중인지 아닌지 판단한느 값을 담는 State
  const [error, setError] = useState("");                         ⌋ // error State 는 데이터를 받아오는 과정에서 에러가 발생할 경우 에러 메세지를 담는 state	
                    
  const fetchResult = async () => {                                 // 3. fetchResult 함수는 서버에 데이터를 요청하고 응답받는 로직을 처리
    try {
      const response = await fetch(url);                          ⌉ 
      const json = await response.json();                         ⎮ // 4. fetch 메서드 인자값으로 useFetch 훅에서 매개변수로 받은 url을 넣어줌, 이후 반환된 값을 response라는 변수에 담아줌 
      setData(json);                                              ⌋ // response를 제이슨 형태로 바꾸어줌 이후 반환된 값을 json 이라는 변수에 넣음
    } catch (e) {
      setError(e);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchResult();
  }, [url]);

  return { loading: loading, data: data, error: error };
};

export default useFetch;

 

import React from 'react';
import useFetch from './useFetch.js';

const App = () => {
  const url = 'https://jsonplaceholder.typicode.com/posts'; // get 요청 훅이기 때문에 Url만 넣어줌
  const { loading, data, error } = useFetch(url); // 3가지 값을 반환
	// loading은 데이터가 요청 중일 떄는 true 그 외는 false
    // data 는 서버에 데이터 요청 후 받아온 값 (초기값은 undefined, 완료 후에는 해당 값 반환)
    // error 는 요청 도중 error가 일어났을 때 에러 객체를 리턴하도록 하고 그 외는 false
  return (
    <div>
      {data.map((list) => {
        return (
          <div key={list.id}>
            <h2>{list.title}</h2>
            <span>{list.body}</span>
          </div>
        );
      })}
    </div>
  );
};

export default App;
// 서버에서 요청을 받은 데이터를 그려주는 로직

 

 

 

 

 

 

728x90
반응형

'TIL' 카테고리의 다른 글

Redux - Redux  (0) 2022.08.28
Redux - Design Patten  (0) 2022.08.28
aws 란?  (0) 2022.08.23
Cloud Computing Services  (0) 2022.08.23
쿼리스트링의 활용  (0) 2022.08.20

댓글