위코드에서 공부하며 정리한 내용입니다.
모달 창이 떴을 때 마우스 스크롤을 움직이면 모달 창 뒤에 있는 UI 가 움직이지 않도록 막아주는 훅입니다.
import { useLayoutEffect } from 'react';
const useLockBodyScroll = () => {
useLayoutEffect(() => {
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = '';
};
}, []);
};
export default useLockBodyScroll;
useLockBodyScroll 훅이 호출되면서 문서의 body 태그 스타일 중 overflow 를 hidden 으로 해서 스크롤을 막아줍니다. useLayoutEffect 는 리액트 내장 훅으로 useEffect 와 다르게 렌더링 되기 전에 동작하는 훅입니다.
요소 밖을 클릭했을 때 특정 동작을 하게 만드는 훅입니다.
import { useState, useRef } from 'react';
import useOnClickOutside from './useOnClickOutside.js'
const App = () => {
const [isModalOpen, setModalOpen] = useState(false);
const ref = useRef();
// 활용 예시
useOnClickOutside(ref, () => setModalOpen(false));
return (
<div className="backGround">
{isModalOpen ? (
<div ref={ref} className="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;
위 코드를 풀어보면, 특정 DOM 을 선택할 수 있는 내장 훅 useRef 을 사용했습니다. 리액트는 가상돔을 이용하지만 특정 요소의 스크롤 위치가 필요하거나 특정 요소에 포커스를 주는 등의 상황에서 직접 DOM 을 선택할 때 useRef 를 사용합니다. useState 훅을 활용해 모달창을 열고 닫는 state 를 생성하고, ref 변수에 useRef 훅 반환값을 할당한 후 DOM 으로 조작할 요소에 연결합니다. 여기서는 모달창이 가진 데이터를 활용하기 위해 modal 선택자에게 ref 를 할당합니다. 그럼 ref 는 modal 선택자를 바라보게 됩니다. useOnClickOutside 훅은 두 개의 인자를 받습니다. 첫번째 인자는 ref 이고 두번째 인자는 함수입니다. modal 선택자를 바라보는 값을 첫번째 인자로 넣고, 모달창을 관리하는 state 를 false 로 변경하는 함수를 두번재 인자로 보내줍니다.
이제 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)) {
return;
}
handler();
};
document.addEventListener("mousedown", listener);
return () => {
document.removeEventListener("mousedown", listener);
};
},
[ref, handler]
);
};
export default useOnClickOutside;
우선 문서에 마우스를 누른 순간 listener 라는 이벤트가 발생하게 설정합니다. listener 함수 내부에서는 조건문을 통해 분기 처리를 해주는데 ref 의 current 값이 true 이거나 ref 의 current 에 event.target 이 있을 때 return 합니다. 즉 넘겨받은 ref 를 기준으로 모달창에서 이벤트가 발생하면 아무런 동작을 하지 않고 끝낸다는 의미입니다. 두가지 조건늘 모두 충족하지 않는다면 handler 함수를 호출해 모달창을 닫아줍니다.
서버와 데이터를 주고 받기 위해 사용하는 훅입니다. 아래 훅은 GET 요청만 처리하는 훅으로 작성한 것입니다.
// useFetch.js
import { useState, useEffect } from "react";
const useFetch = (url) => {
const [data, setData] = useState({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const fetchResult = async () => {
try {
const response = await fetch(url);
const json = await response.json();
setData(json);
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchResult();
}, [url]);
return { loading: loading, data: data, error: error };
};
export default useFetch;
useFetch 훅은 api 주소인 한개의 인자를 받습니다. 그리고 useFetch 훅을 호출했을 때 반환할 값을 state로 만들어줍니다. data 는 fetch 메서드 호출 뒤 반환되는 값을 담고, loading 은 데이터를 받아오는 중인지 아닌지 판단하는 값을 담고, error 는 데이터를 받아오는 과정에서 에러가 발생할 경우 에러 메시지를 담습니다. fetchResult 함수는 서버에 데이터를 요청하고 응답받는 로직을 처리하고 fetch 메서드의 인자값으로 useFetch 훅에서 매개변수로 받은 url 을 넣어줍니다. 이후 반환된 값을 response 에 담아주고 response를 json 형태로 변환한 후 data 에 담아줍니다.
useFetch 훅을 사용한 App 컴포넌트를 살펴보면,
import React from 'react';
import useFetch from './useFetch.js';
const App = () => {
const url = 'https://jsonplaceholder.typicode.com/posts';
const { loading, data, error } = useFetch(url);
return (
<div>
{data.map((list) => {
return (
<div key={list.id}>
<h2>{list.title}</h2>
<span>{list.body}</span>
</div>
);
})}
</div>
);
};
export default App;
useFetch 는 특정 url 에 대한 GET 요청만 하므로 url 인자만 넣어주고, useFetch 가 호출되면 loading, data, error 세 가지 값을 반환합니다. loading은 데이터를 요청 중일 때 true, 그 외 경우 false 를 반환하고, data 는 요청해서 응답받은 데이터 값으로 초기 값은 undefined, 완료 후 해당 값을 반환합니다. error 는 요청 도중 error 가 일어났을 때 에러 객체를 리턴하고 그 외 경우 false 를 반환합니다.
테스트 api 주소를 사용하고 싶다면 JSONplaceholder 를 사용하세요.
http://jsonplaceholder.typicode.com/