[번역] useEffect에 관하여

Array.prototype·2022년 11월 25일
0

react

목록 보기
4/6

1. uesEffect를 바라보는 올바른 방법

클래스기반 컴포넌트에서 사용하는 라이프 사이클들이 있다.
componentWillmount, componentDidMount 등등
useEffect 훅의 아이디어는 이러한 라이프사이클 방법과 근본적으로 다르다.

우리는 함수형 컴포넌트에서 리액트 트리 바깥쪽에 있는 states와 props 변화를 동기화 하기 위해 useEffect를 사용한다.
그게 무슨 의미냐면
컴포넌트의 상태값이 변할 때, 리액트는 구성요소를 다시 렌더링하여 이 변경사항을 DOM과 동기화하려고 한다.
마찬가지로 useEffect훅은 리액트 트리 외부의 state, prop 변경을 동기화하는데 사용된다.
예를들어, 페이지네이션이 있는 테이블의 경우 페이지네이션을 클릭할 때마다 api호출을 해서 해당 데이터를 가져와야 하는데 활성화된 페이지네이션의 숫자를 상태로 만들어서 활성화된 상태값이 바뀔때마다 api호출을 할 수 있다. 이때 useEffect 훅은 백엔드에서 올바른 데이터를 가져와 리액트 트리 외부의 상태변경을 동기화 한다.

const TableView = () => {
    const [ activePage, setActivePage ] = useState(0);
    const [ data, setData ] = useState();
    
    useEffect(() => {
        setData(await getData(activePage);
    }, [ activePage ]);
    
    const onPageChange = (page) => {
        setActivePage(page);
    };
    
    return (
        <div>
            <Table data={ data } />
            <Pagination activePage={ activePage } onChange={ onPageChange } />
        </div>
    );
}

이 api요청은 컴포넌트 마운트 전인지 후인지 혹은 업데이트 후인지 신경쓰지않고 상태값이 변할 때 마다 요청한다.

달리 말하면 위 예제에서 상태값의 변경은 백엔드의 데이터와 동기화 된다고도 말할 수 있다.

이것이 uesEffect를 보는 올바른 방법이다.

컴포넌트가 마운트 되었을 때 api를 요청하는 것은 종속성 배열을 사용할 수 있다.
이 종속성 배열을 useEffect훅의 트리거로 생각하기 쉬운데 이는 잘못된 생각이다.

2. 의존성 배열은 useEffect의 트리거인가

클래스기반 컴포넌트에서 활성페이지 상태값이 변경되었을 경우 api요청은 componentDidUpdate에서 할 수 있다. 이전 활성페이지 상태값과 현재의 것과 비교해서 둘이 다르다면 api를 요청한다.
이런걸보면 의존성 배열은 활성페이지 상태가 변경될 때 리액트에게 useEffect훅을 트리거하도록 요청하는 방법으로 볼 수 있다. 이 시선이 정확하게 틀린건 아니지만 바람직하지않은 결과를 초래할 수 있다.
위의 예제에서는 잘 동작했지만 예제가 조금 바뀐다면 어떨까?

다음 페이지로 이동할 때, 가져올 수 있는 행 수를 지정할 수 있는 텍스트 상자가 있다고 가정해보자.
그리고 텍스트박스를 상태값으로 지정하자. api요청에 행 수를 지정해야 하므로 uesEffect훅 내부에서 이 상태를 사용해야한다. 그러나 활성페이지의 상태가 변경될 때만 후크를 트리거해야하기 때문에 종속성 배열에는 활성페이지 상태만 지정한다.

이것은 잘 동작하지만 exhaustive-deps eslint rule은 경고를 던진다.
왜냐하면 행 수에 대한 상태를 종속성 배열로 전달하지 않았기 때문이다.
잘 동작하는데 무슨문제야? 라고 할 수도 있지만 지금은 uesEffect훅을 남용하여 기능을 동작하게 한 것이다. 이는 해킹에 불과하고 적절한 해결책이 아니며 이러한 접근법은 버그코드를 생산한다.

3. 의존성 배열을 올바르게 보는 방법

어떡하면 의존성 배열을 올바르게 쓰는 것일까?
사실 의존성 배열은 기능적 이점을 제공하지 않는다. 오히려 최적화 기능이다.
앞서봤듯이 useEffect훅은 리액트 트리 외부의 state, prop의 변경에 대해 동기화하는데 사용된다. 동기화는 모든 렌더중에 발생한다.

즉, 리액트는 모든 렌더에 useEffect훅을 호출한다.

그러나 모든 렌더에 api를 요청하는 것은 낭비이며 불필요하다. 의존성배열은 이 문제를 해결하는데 사용된다.
의존성 배열은 useEffeect훅 내부에서 어떤 states, props가 쓰이는지 리액트에게 알려주며 그 결과 리액트는 이러한 states, props가 변경될 때만 useEffect훅을 실행해야한다.

그 결과, 우리는 useEffect내부에서 사용하는 states, props, functions, variables, useRefs를 포함하여 함수컴포넌트의 스코프에 있는 모든것을 종속배열로 전달해야한다.

예제로 돌아가서 활성페이지 상태값과 행 수 상태값은 useEffect훅에서 사용하는 것이기 때문에 종속성 배열로 전달해야한다. 그렇게 했다면 리액트는 두 상태값이 변경될 때만 이 훅을 호출해서 동기화하며 위에서 발생했던 exhaustive-deps경고문도 사라질 것이다.

onst TableView = () => {
    const [ activePage, setActivePage ] = useState(0);
    const [ numberOfRows, setNumberOfRows ] = useState(10);
    const [ data, setData ] = useState();
    
    const onPageChange = (page) => {
        setActivePage(page);
        
        // 이벤트에서 트리거되어야하는 요청은 이벤트 핸들러내에서 더 잘 처리된다.
        setData(await getData(page, numberOfRows);
    };
    
    const handleNumberOfRowsChange = (e) => {
        setNumberOfRows(e.target.value);
    };
    
    return (
        <div>
            <Table data={ data } />
            <input type="number" onChange={ handleNumberOfRowsChange } value = { numberOfRows } />
            <Pagination activePage={ activePage } onChange={ onPageChange } />
        </div>
    );
}

useEffect훅 남발하는게 능사가 아니라능...

4. 결론

useEffect훅은 리액트 트리 외부에 있는 함수컴포넌트의 스코프에 선언된 어떠한 것들(states, props, functions, variables, useRefs)의 변경사항을 동기활 할 때 사용된다.
우리는 useEffect훅 내부에서 사용하는 의존성들을 리액트에게 알려줄 때 의존성배열을 사용하며 이 의존성 배열들이 변경되었을 때 리액트는 이 훅을 호출해야한다. 이것이 최적화 기술이다.

profile
frontend developer

0개의 댓글