리액트 훅 사용 주의 사항

Dae-Hee·2022년 7월 10일
3
post-thumbnail

코드 리뷰 후 중요하다고 알게된
React Hook 순서 ! 제대로 알고 쓰자 🙂


▪︎ 호출 순서 의존


하나의 컴포넌트에서 훅을사용하는 순서는 항상 같아야하고,

최상위(at the Top Level)에서만 Hook을 호출해야 합니다.

▪︎ if 블럭 안에서 쓸지 말 것
 - 조건에 따라 실행이 안 될수도 있어서

▪︎ for loop 블럭 안에서 쓰지 말 것
 - 실행 횟수가 달라지고 실행이 안될수도 있어서

▪︎ Function 안에서 쓰지 말 것
 - 함수의 실행에 의존성을 가지면 안된다.
 

리액트 훅은 사용된 위치 기반으로 훅 데이터를 관리!

이 규칙을 따르면 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장됩니다.


  • 근데 왜 호출 순서가 보장 받아야 하지 ? 🤔
function Test() {
  const [name, setName] = useState('Jade');
  
  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });
  
  const [age, setAge] = useState(29);
  
  useEffect(function updateTitle() {
    document.title = name + '/' + age;
  });
  
  ...
}
  

// 첫 번째 렌더링
useState('Jade')        // 1. 'Jade'라는 name state 변수를 선언합니다.
useEffect(persistForm)  // 2. 폼 데이터를 저장하기 위한 effect를 추가합니다.
useState(29)            // 3. 29 라는 age state 변수를 선언합니다.
useEffect(updateTitle)  // 4. 제목을 업데이트하기 위한 effect를 추가합니다.

// 두 번째 렌더링
useState('Jade')        // 1. name state 변수를 읽습니다.(인자는 무시됩니다)
useEffect(persistForm)  // 2. 폼 데이터를 저장하기 위한 effect가 대체됩니다.
useState(29)            // 3. age state 변수를 읽습니다.(인자는 무시됩니다)
useEffect(updateTitle)  // 4. 제목을 업데이트하기 위한 effect가 대체됩니다.
위와 같이 한 컴포넌트에서 훅을 여러 개 사용할 수 있습니다.
React는 특정 state가 어떤 useState 호출에 해당하는지 알기 위해 호출 순서에 의존 합니다.

const [name, setName] = useState('Jade');

if (name !== '') {
  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });
}

...

const resetName = () => {
  setName('');
};
위와 같이 useEffect을 조건문 안에서 작성 한다면
최초 렌더링 시에 useEffect을 감싸고 있는 조건이 성립되기 때문에 훅이 동작 합니다.

하지만 그 후 사용자가 resetName 함수를 호출하면, 
리렌더링이 일어나고 useEffect를 감싼 조건이 false 때문에
useEffect 훅을 건너뛰어 훅 호출 순서는 달라지게 됩니다!

React는 이전 렌더링 때처럼 컴포넌트 내에서 두 번째 Hook 호출이
persistForm effect와 일치할 것이라 예상했지만 그렇지 않기 때문에
건너뛴 Hook 다음에 호출되는 Hook이 순서가 하나씩 밀리면서 버그가 발생 됩니다!

▪︎ useEffect 주의사항


useEffect는 React에서 *Side-Effect를 처리하기 위한 Hook으로,

*Side-Effect?
  함수가 실행되면서 함수 외부에 존재하는 값이나 상태를 변경시키는 등의 행위
  React에서는 컴포넌트가 화면에 렌더링된 이후에 비동기로 처리되어야 하는 
  부수적인 효과들을 흔히 Side Effect라고 말한다.

컴포넌트 라이프사이클과 관련이 있는 Side-Effect을 담당하지만

내가 언제 Effect를 다시 실행해야 할지 지정 하고싶을 때가 있습니다.

그럴때 useEffect는 의존성 배열을 두번째 인수로 받습니다.

useEffect(() => {
  ...
}, [여기에 의존성 배열 작성])

React에서 의존성 배열 변경을 감지하기 위해 Object.js() 메서드를 사용하는데,

참조형 데이터를 비교할 때 렌더링이 될 때 마다 참조 메모리 주소가 다르기 때문에 매번 실행 됩니다.

그래서 참조형 데이터인 경우 useMemo를 사용하여 동일한 객체가 잘 전달될 수 있도록 처리해야 합니다.

또한, 의존성 배열은 Effect에서 쓰이는 값 전부를 알려줘야 합니다.

만약 구현하고자 하는 동작이 이 조건을 못 지킨다면, 의존성을 적게 요구하도록 잘 설계해야 합니다.


▪︎ useEffect에서 async / await 사용


async / await 함수는 Promise 객체를 반환하기 때문에 useEffect에서는 내부에 함수를 만들어 호출합니다.

useEffect(async () => {
  const res = await fetchUser(id);
  ...
}, [id]); // XXX

// 아래 처럼 함수를 만들어 사용  
  
useEffect(() => {
  async function fetchUserFnc() {
    const res = await fetchUser(id);
    ...
  }
    
  fetchUserFnc();
}, [id]);

useEffect 밖에서도 위 fetch 함수가 필요한 경우에는?

단순히 의존성 배열에 함수를 추가해준다면 렌더링할 때마다 갱신되므로!
결과적으로 해당 함수는 렌더링 할 때마다 호출됩니다.

이 문제를 해결하기 위해서는 useCallBack을 사용해서 함수가 필요할 때만 갱신되도록 해야합니다!

const fetchUserFnc = useCallback(async () => {
  const res = fetchUser(id);
  ...
}, [id]);

useEffect(() => {
  fetchUserFnc();
}, [fetchUserFnc]);


Reference

0개의 댓글