useRef 에 대해 알아보자. 그리고 custom hook 에 대해 알아보자.

Jeong·2023년 9월 5일
0

React Hooks

목록 보기
4/5
post-thumbnail

키워드

  • useRef
  • Hook의 규칙

최종 목표

간단한 서버를 만들고, custom hook 을 사용하여 서버에서 데이터를 받아올 수 있다.

현재 목표

useRef 에 대해 알아보자. 그리고 custom hook 에 대해 알아보자.

useRef 란?

컴포넌트의 생애주기 전체에 걸쳐서 유지되는 객체이다. 즉, 컴포넌트가 없어질 때까지 동일한 객체가 유지된다.

useRef 의 특징은?

동일한 useRef 객체를 참조하면 이들은 동일한 객체를 공유한다.

객체 자체는 값은 아니고, 값을 참조하기 위한 객체이다. 그래서 값을 언제든지 변경할 수 있다.
만약, 여러 컴포넌트에서 동일한 useRef 객체를 참조하면 이들은 동일한 객체를 공유하게 된다.

아래 TimerControl 컴포넌트가 10번 호출된다면 ref.value 는 10이 된다.

const ref = { value: 1 }

function TimerControl() {
	// useEffect 문제로 한 번만 늘어나진 않지만, 일단 한 번만 늘어난다고 가정한다.
    ref.value += 1; 
  
 	return (...);
}

객체의 현재 값이 바뀌더라도 렌더링에 영향을 주지 않는다.

상태(state)가 변경되면 해당 컴포넌트와 하위 컴포넌트는 다시 렌더링한다. 하지만 레퍼런스 객체의 현재 값(current)는 바뀌더라도 렌더링에 영향을 주지 않는다.

다시 렌더링 되지 않기 때문에 UI 로는 바뀐 객체의 현재 값을 확인할 수 없다.

컴포넌트의 생애주기는 컴포넌트가 생기고 없어질 때까지이다. 컴포넌트가 없어지면 가상 돔으로 들어갔던 컴포넌트 요소가 빠진다.

useRef 는 언제 써야 할까?

컴포넌트가 사라질 때까지 동일한 값을 쓰고 싶다

input 태그의 id를 관리할 때 많이 쓴다.

input 태그를 가진 컴포넌트를 여러 곳에서 쓴다면, 각 컴포넌트 id는 달라야 하며 id가 생애주기 동안 유지가 되야 한다.


function TimerControl() {
	const ref = useRef(`input-${Math.random()}`);
  	
 	return (
    	<div>
        	<label htmlFor={id.current}>
       	 		Search
        	</label>
        	<input
             	id={id.current}
          		...
            />
      	</div>
    );
}

(특히 useEffect 등과 함께 쓰면서 만나게 되는) 비동기 상황에서 현재 값을 제대로 쓰고 싶다.

useEffect 등을 쓰면 Closure 문제를 만나게 된다. (Closure 문제는 우리가 useEffect 에서 변수가 Capture 되고 Bind 된다는 것에 대한 이해가 부족해서 일어나는 문제이다.)

Closure 문제에 대한 예를 들어보자. useEffect 가 실행되고 5초 뒤에 filterText 값을 console 에 출력하고 있다. 5초 이내에 filterText 의 값을 바꾸면 console 에는 바뀐 filterText 값이 찍힐까? 아니다. 처음에 Caputer, Bind 된 값인 '' 이 찍힌다.

const [filtetText, setFilterText] = useState('');

useEffect(() => {
	setTimeout(() => {
		console.log(filterText);
	}, 5_000);
}, []);

return <input onChange={(e) => setFilterText(e.target.value)} />;

바뀐 값을 출력하고 싶다면 다음과 같이 useRef 를 사용할 수 있다. query 가 값을 참조하는 객체라서 가능한 일이다.

const [filtetText, setFilterText] = useState("");
const query = useRef(""); // 추가

// 추가
useEffect(() => {
  query.current = filtetText;
}, [filtetText]);

useEffect(() => {
  setTimeout(() => {
    console.log(query.current);
  }, 5_000);
}, []);

return <input onChange={(e) => setFilterText(e.target.value)} />;

이렇게 코딩할 일은 절대 없긴 하다.

Custom Hooks 란?

로직을 재사용하기 위한 제일 쉬운 방법이다.

Custom Hooks 을 하는 방법은 너무 쉽다. Refactoring 으로 Extract Function 을 수행하면 된다.

컴포넌트가 PascalCase 로 이름을 붙였다면, Hook은 use 로 시작하는 camelCase로 이름을 붙이면 된다.

hooks/useFetchProducts.ts

export default function useFetchProducts() {
	const [products, setProducts] = useState<Product[]>([]);
	
	useEffect(() => {
		const fetchProducts = async () => {
			const url = 'http://localhost:3000/products';
			const response = await fetch(url);
			const data = await response.json();
			setProducts(data.products);
		};

		fetchProducts();
	}, []);

	return products;
}

이제 useFetchProducts 를 여기저기서 불러다가 쓰면 된다.

setProducts 는 useFetchProducts 에서만 쓰는 함수이다. 그래서 이렇게 관심사의 분리로 컴포넌트를 떨구면 setProducts 가 캡슐화돼서 setProducts 를 실수로 잘못 쓰는 문제를 해소시킨다. 컴포넌트가 명확해진 것이다. 부르는 컴포넌트에서는 setProducts 가 쓰이든 말든 관심사 밖이기 때문에 상관이 없다.

Custom Hook 을 잘 쓰게 되면 앞으로 테스트 코드를 작성할 때 Mocking 도 수월하게 할 수 있다. 그리고 Hook 자체를 유닛 테스트 할 수 있어서 편하다.

Hook 규칙은?

Hook 호출은 규칙이 있어서 단순하게 쓰도록 노력해야 한다.

  1. Function Component 바로 안쪽(함수 최상위)에서만 호출한다.
  2. Function Component 또는 Custom Hook 에서만 호출한다.

⚠️ 다음과 같이 콜백 함수나 조건문 안에서 Hook 을 호출하면 안된다. ⚠️

	if(playing) {
    	const products = useFetchProducts();
      	console.log(products);
    }

이런 형태로 특정 조건이 걸렸을 때 다시 부르고 싶다면, fetchProducts() 같은 re-load 하는 함수를 products 처럼 Return 해준다. 그리고 특정 조건에 fetchProducts() 를 호출한다.

아하! 포인트

컴포넌트의 깔끔함을 위해서 custom hook 을 쓰는 연습을 많이 해야겠다. useEffect 는 외부와 연결을 위해서 쓰는 hook 이니까 무조건 custom hook 으로 빼도 될 듯 싶다.

다음에는?

usehook-ts 라이브러리에 대해 알아보자.

profile
성장중입니다 🔥 / 나무위키처럼 끊임없이 글이 수정됩니다!

0개의 댓글