v19.2Latest

Effect가 필요하지 않을 수도 있습니다

Effect는 React 패러다임에서 벗어나는 탈출구입니다. Effect를 사용하면 React "외부로 나가서" 컴포넌트를 비 React 위젯, 네트워크, 브라우저 DOM과 같은 외부 시스템과 동기화할 수 있습니다. 외부 시스템이 관련되지 않은 경우(예: 일부 props나 state가 변경될 때 컴포넌트의 state를 업데이트하려는 경우) Effect가 필요하지 않습니다. 불필요한 Effect를 제거하면 코드를 더 쉽게 이해할 수 있고, 실행 속도가 빨라지며, 오류 발생 가능성이 줄어듭니다.

배울 내용
  • 컴포넌트에서 불필요한 Effect를 제거하는 이유와 방법
  • Effect 없이 비용이 많이 드는 계산을 캐시하는 방법
  • Effect 없이 컴포넌트 state를 재설정하고 조정하는 방법
  • 이벤트 핸들러 간에 로직을 공유하는 방법
  • 어떤 로직을 이벤트 핸들러로 옮겨야 하는지
  • 변경 사항을 부모 컴포넌트에 알리는 방법

불필요한 Effect를 제거하는 방법

Effect가 필요하지 않은 일반적인 경우는 두 가지입니다:

  • 렌더링을 위한 데이터 변환에는 Effect가 필요하지 않습니다.예를 들어, 목록을 표시하기 전에 필터링하려 한다고 가정해 보겠습니다. 목록이 변경될 때 state 변수를 업데이트하는 Effect를 작성하고 싶을 수 있습니다. 그러나 이는 비효율적입니다. state를 업데이트하면 React는 먼저 화면에 무엇이 표시되어야 하는지 계산하기 위해 컴포넌트 함수를 호출합니다. 그런 다음 React는 이러한 변경 사항을 DOM에 "커밋"하여 화면을 업데이트합니다. 그런 다음 React는 Effect를 실행합니다. 만약 Effect가또한즉시 state를 업데이트하면 전체 프로세스가 처음부터 다시 시작됩니다! 불필요한 렌더링 패스를 피하려면 컴포넌트의 최상위 레벨에서 모든 데이터를 변환하세요. 해당 코드는 props나 state가 변경될 때마다 자동으로 다시 실행됩니다.
  • 사용자 이벤트를 처리하는 데 Effect가 필요하지 않습니다.예를 들어, 사용자가 제품을 구매할 때/api/buyPOST 요청을 보내고 알림을 표시하려 한다고 가정해 보겠습니다. 구매 버튼 클릭 이벤트 핸들러에서는 정확히 무슨 일이 일어났는지 알 수 있습니다. Effect가 실행될 때는 사용자가무엇을했는지(예: 어떤 버튼이 클릭되었는지) 알 수 없습니다. 이것이 일반적으로 해당 이벤트 핸들러에서 사용자 이벤트를 처리하는 이유입니다.

외부 시스템과필요합니다. 예를 들어, jQuery 위젯을 React state와 동기화 상태로 유지하는 Effect를 작성할 수 있습니다. 또한 Effect로 데이터를 가져올 수도 있습니다: 예를 들어, 검색 결과를 현재 검색어와 동기화할 수 있습니다. 최신 동기화하려면 Effect가 프레임워크는 컴포넌트에서 직접 Effect를 작성하는 것보다 더 효율적인 내장 데이터 가져오기 메커니즘을 제공한다는 점을 기억하세요.

올바른 직관을 얻을 수 있도록 몇 가지 일반적인 구체적인 예를 살펴보겠습니다!

props 또는 state를 기반으로 state 업데이트하기

두 개의 state 변수, firstNamelastName을 가진 컴포넌트가 있다고 가정해 보겠습니다. 이들을 연결하여fullName을 계산하려 합니다. 또한 fullNamefirstName또는lastName이 변경될 때마다 업데이트되기를 원합니다. 첫 번째 직감은fullNamestate 변수를 추가하고 Effect에서 업데이트하는 것일 수 있습니다:

이는 필요 이상으로 복잡합니다. 또한 비효율적입니다: fullName에 대해 오래된 값으로 전체 렌더링 패스를 수행한 다음, 즉시 업데이트된 값으로 다시 렌더링합니다. state 변수와 Effect를 제거하세요:

기존 props나 state에서 계산할 수 있는 것이 있다면,state에 넣지 마세요.대신 렌더링 중에 계산하세요.이렇게 하면 코드가 더 빠르고(추가적인 "연쇄" 업데이트를 피함), 더 간단해지며(일부 코드를 제거함), 오류 발생 가능성이 줄어듭니다(서로 동기화되지 않은 다른 state 변수로 인한 버그를 피함). 이 접근 방식이 새롭게 느껴진다면,React 사고방식에서 state에 무엇을 넣어야 하는지 설명합니다.

비용이 많이 드는 계산 캐싱하기

이 컴포넌트는 props로 받은todos를 취하고filter prop에 따라 필터링하여 visibleTodos를 계산합니다. 결과를 state에 저장하고 Effect에서 업데이트하고 싶을 수 있습니다:

이전 예제와 마찬가지로, 이는 불필요하고 비효율적입니다. 먼저 상태와 Effect를 제거하세요:

보통 이 코드는 괜찮습니다! 하지만getFilteredTodos()가 느리거나 todos가 많은 경우에는 getFilteredTodos()newTodo와 같은 관련 없는 상태 변수가 변경될 때마다 다시 계산하고 싶지 않을 것입니다.

비용이 많이 드는 계산을 캐시(또는"메모이제이션")하려면 useMemoHook으로 감싸면 됩니다:

참고

React Compiler는 비용이 많이 드는 계산을 자동으로 메모이제이션하여 많은 경우에 수동으로useMemo를 사용할 필요를 없앨 수 있습니다.

또는 한 줄로 작성하면:

이는 React에게 todosfilter가 변경되지 않는 한 내부 함수를 다시 실행하지 않도록 지시합니다.React는 초기 렌더링 중에getFilteredTodos()의 반환값을 기억합니다. 다음 렌더링에서는todosfilter가 다른지 확인합니다. 만약 지난번과 같다면,useMemo는 저장된 마지막 결과를 반환합니다. 하지만 다르다면, React는 내부 함수를 다시 호출하고(그 결과를 저장합니다).

당신이 useMemo로 감싼 함수는 렌더링 중에 실행되므로, 이는순수 계산에만 작동합니다.

Deep Dive
계산이 비용이 많이 드는지 어떻게 알 수 있나요?

prop이 변경될 때 모든 state 재설정하기

ProfilePage 컴포넌트는 userIdprop을 받습니다. 페이지에는 댓글 입력 필드가 있으며, 그 값을 보관하기 위해commentstate 변수를 사용합니다. 어느 날 문제를 발견했습니다: 한 프로필에서 다른 프로필로 이동할 때commentstate가 재설정되지 않습니다. 결과적으로 잘못된 사용자의 프로필에 실수로 댓글을 게시하기 쉽습니다. 이 문제를 해결하려면userId가 변경될 때마다commentstate 변수를 비우고 싶습니다:

이는 비효율적입니다. 왜냐하면ProfilePage와 그 자식 컴포넌트들이 먼저 오래된 값으로 렌더링된 다음, 다시 렌더링되기 때문입니다. 또한모든컴포넌트에서 이 작업을 수행해야 하므로 복잡해집니다. 예를 들어, 댓글 UI가 중첩되어 있다면 중첩된 댓글 상태도 지워야 합니다.

대신, 명시적인 키를 부여하여 각 사용자의 프로필이 개념적으로다른프로필임을 React에 알릴 수 있습니다. 컴포넌트를 둘로 나누고 외부 컴포넌트에서 내부 컴포넌트로key 속성을 전달하세요:

일반적으로 React는 동일한 컴포넌트가 동일한 위치에 렌더링될 때 상태를 보존합니다.하지만 userIdProfile컴포넌트의key로 전달함으로써, 서로 다른userId를 가진 두 Profile컴포넌트를 상태를 공유하지 않는 별개의 컴포넌트로 취급하도록 React에 요청하는 것입니다. 키(여기서는 userId로 설정됨)가 변경될 때마다 React는 DOM을 다시 생성하고상태를 초기화합니다. 이제 프로필 간에 이동할 때comment필드가 자동으로 지워집니다.

이 예제에서는 외부의 ProfilePage컴포넌트만 내보내져 프로젝트의 다른 파일에서 볼 수 있습니다.ProfilePage를 렌더링하는 컴포넌트들은 키를 전달할 필요가 없습니다. 그들은userId를 일반적인 prop으로 전달합니다.ProfilePage가 이를 내부Profile컴포넌트에key로 전달하는 것은 구현 세부사항입니다.

prop이 변경될 때 일부 상태 조정하기

때로는 prop이 변경될 때 상태의 일부를 초기화하거나 조정하고 싶지만, 전체 상태를 초기화하고 싶지는 않을 수 있습니다.

List컴포넌트는 prop으로items 목록을 받고, 선택된 항목을 selection상태 변수에 유지합니다.itemsprop이 다른 배열을 받을 때마다selectionnull로 초기화하고 싶습니다:

이것 역시 이상적이지 않습니다.items가 변경될 때마다,List와 그 자식 컴포넌트들은 먼저 오래된selection값으로 렌더링됩니다. 그런 다음 React는 DOM을 업데이트하고 Effect를 실행합니다. 마지막으로setSelection(null) 호출은 List와 그 자식 컴포넌트들의 다시 렌더링을 일으켜 이 전체 과정을 다시 시작하게 합니다.

먼저 Effect를 삭제하세요. 대신, 렌더링 중에 상태를 직접 조정하세요:

이전 렌더링의 정보 저장하기는 이해하기 어려울 수 있지만, Effect에서 동일한 상태를 업데이트하는 것보다 낫습니다. 위 예제에서setSelection은 렌더링 중에 직접 호출됩니다. React는List즉시 return문을 종료한 후 다시 렌더링합니다. React는 아직List의 자식들을 렌더링하거나 DOM을 업데이트하지 않았으므로, 이를 통해List 자식들이 오래된 selection값 렌더링을 건너뛸 수 있습니다.

렌더링 중에 컴포넌트를 업데이트하면 React는 반환된 JSX를 버리고 즉시 렌더링을 다시 시도합니다. 매우 느린 연쇄 재시도를 피하기 위해, React는 렌더링 중에동일한컴포넌트의 상태만 업데이트하도록 허용합니다. 렌더링 중에 다른 컴포넌트의 상태를 업데이트하면 오류가 발생합니다.items !== prevItems와 같은 조건은 루프를 피하기 위해 필요합니다. 이렇게 상태를 조정할 수 있지만, 다른 부수 효과들(예: DOM 변경이나 타임아웃 설정)은 이벤트 핸들러나 Effect에 남겨두어컴포넌트를 순수하게 유지해야 합니다.

이 패턴은 Effect보다 효율적이지만, 대부분의 컴포넌트는 이것도 필요하지 않습니다.어떻게 하든, props나 다른 상태를 기반으로 상태를 조정하면 데이터 흐름을 이해하고 디버그하기가 더 어려워집니다. 항상키로 모든 상태를 초기화하거나렌더링 중에 모든 것을 계산할 수 있는지 확인하세요. 예를 들어, 선택된항목을 저장(및 초기화)하는 대신 선택된항목 ID를 저장할 수 있습니다.

이제 상태를 '조정'할 필요가 전혀 없습니다. 선택된 ID를 가진 항목이 목록에 있으면 계속 선택된 상태로 유지됩니다. 목록에 없다면, 렌더링 중에 계산된selection은 일치하는 항목을 찾지 못했기 때문에null이 됩니다. 이 동작은 다르지만, items에 대한 대부분의 변경 사항이 선택 상태를 유지하므로 더 나을 수 있습니다.

이벤트 핸들러 간 로직 공유

상품 페이지에 두 개의 버튼(구매하기와 결제하기)이 있고 둘 다 해당 상품을 구매할 수 있다고 가정해 보겠습니다. 사용자가 장바구니에 상품을 담을 때마다 알림을 표시하려고 합니다. 두 버튼의 클릭 핸들러에서showNotification()을 호출하는 것은 반복적으로 느껴질 수 있으므로 이 로직을 Effect에 배치하고 싶을 수 있습니다:

이 Effect는 불필요합니다. 또한 버그를 유발할 가능성이 높습니다. 예를 들어, 앱이 페이지 새로고침 사이에 장바구니를 '기억'한다고 가정해 보겠습니다. 장바구니에 상품을 한 번 추가하고 페이지를 새로고침하면 알림이 다시 나타납니다. 해당 상품 페이지를 새로고침할 때마다 계속 나타날 것입니다. 이는 페이지 로드 시product.isInCart가 이미 true가 되어 위의 Effect가showNotification()을 호출하기 때문입니다.

어떤 코드가 Effect에 있어야 하는지 아니면 이벤트 핸들러에 있어야 하는지 확실하지 않다면, 이 코드가실행되어야 하는지 스스로에게 물어보세요. 컴포넌트가 사용자에게 표시되었기때문에실행되어야 하는 코드에만 Effect를 사용하세요. 이 예제에서 알림은 사용자가 버튼을 눌렀기 때문에나타나야 하며, 페이지가 표시되었기 때문이 아닙니다! Effect를 삭제하고 공유 로직을 두 이벤트 핸들러에서 호출되는 함수에 넣으세요:

이렇게 하면 불필요한 Effect를 제거하고 버그도 수정됩니다.

POST 요청 보내기

Form컴포넌트는 두 종류의 POST 요청을 보냅니다. 마운트될 때 분석 이벤트를 보냅니다. 폼을 작성하고 제출 버튼을 클릭하면/api/register엔드포인트로 POST 요청을 보냅니다:

앞선 예제와 동일한 기준을 적용해 봅시다.

분석 POST 요청은 Effect에 남아 있어야 합니다. 이는 분석 이벤트를 전송하는이유가 폼이 표시되었기 때문입니다. (개발 환경에서는 두 번 실행되지만, 처리 방법은여기서 확인하세요.)

그러나 /api/register POST 요청은 폼이 표시되었기 때문에 발생하는 것이 아닙니다. 특정 순간, 즉 사용자가 버튼을 누를 때만 요청을 보내고 싶을 것입니다. 이는 오직그 특정 상호작용에서만발생해야 합니다. 두 번째 Effect를 삭제하고 해당 POST 요청을 이벤트 핸들러로 옮기세요:

어떤 로직을 이벤트 핸들러에 넣을지 Effect에 넣을지 선택할 때, 답해야 할 주요 질문은 사용자 관점에서 이 로직이어떤 종류의 로직인지입니다. 이 로직이 특정 상호작용에 의해 발생한다면 이벤트 핸들러에 두세요. 사용자가 컴포넌트를화면에서 보는 것에 의해 발생한다면 Effect에 두세요.

계산 연쇄

때로는 각각이 다른 상태를 기반으로 상태 조각을 조정하는 Effect들을 연쇄적으로 사용하고 싶은 유혹을 느낄 수 있습니다:

이 코드에는 두 가지 문제가 있습니다.

첫 번째 문제는 매우 비효율적이라는 점입니다: 컴포넌트(와 그 자식들)는 체인 내의 각set호출 사이에 다시 렌더링되어야 합니다. 위의 예시에서 최악의 경우(setCard→ 렌더링 →setGoldCardCount→ 렌더링 →setRound→ 렌더링 →setIsGameOver→ 렌더링) 아래 트리의 불필요한 재렌더링이 세 번 발생합니다.

두 번째 문제는, 속도가 느리지 않다고 해도 코드가 발전함에 따라 작성한 "체인"이 새로운 요구사항에 맞지 않는 경우가 생길 수 있다는 점입니다. 게임 이동 기록을 단계별로 살펴볼 수 있는 기능을 추가한다고 상상해 보세요. 각 상태 변수를 과거의 값으로 업데이트하여 이를 수행할 것입니다. 그러나card상태를 과거의 값으로 설정하면 Effect 체인이 다시 트리거되어 표시 중인 데이터를 변경하게 됩니다. 이러한 코드는 종종 경직되고 취약합니다.

이 경우에는 렌더링 중에 계산할 수 있는 것은 계산하고, 상태는 이벤트 핸들러에서 조정하는 것이 더 좋습니다:

이 방식은 훨씬 더 효율적입니다. 또한 게임 기록을 보는 방법을 구현한다면, 이제 각 상태 변수를 과거의 이동으로 설정할 때 다른 모든 값을 조정하는 Effect 체인을 트리거하지 않을 수 있습니다. 여러 이벤트 핸들러 간에 로직을 재사용해야 한다면,함수를 추출하여 해당 핸들러에서 호출할 수 있습니다.

이벤트 핸들러 내부에서는 상태가스냅샷처럼 동작한다는 점을 기억하세요. 예를 들어,setRound(round + 1)를 호출한 후에도round변수는 사용자가 버튼을 클릭한 시점의 값을 반영합니다. 계산에 다음 값을 사용해야 한다면,const nextRound = round + 1처럼 수동으로 정의하세요.

어떤 경우에는 이벤트 핸들러에서 다음 상태를 직접 계산할 수없습니다. 예를 들어, 다음 드롭다운의 옵션이 이전 드롭다운의 선택된 값에 따라 달라지는 여러 개의 드롭다운이 있는 폼을 상상해 보세요. 그러면 네트워크와 동기화하는 것이므로 Effect 체인이 적절합니다.

애플리케이션 초기화

일부 로직은 앱이 로드될 때 한 번만 실행되어야 합니다.

최상위 컴포넌트의 Effect에 배치하고 싶은 유혹을 느낄 수 있습니다:

그러나 개발 환경에서 이 로직이두 번 실행된다는 사실을 금방 발견하게 될 것입니다. 이로 인해 문제가 발생할 수 있습니다—예를 들어, 함수가 두 번 호출되도록 설계되지 않았기 때문에 인증 토큰이 무효화될 수 있습니다. 일반적으로 컴포넌트는 다시 마운트되는 상황에 대해 탄력적이어야 합니다. 여기에는 최상위App컴포넌트도 포함됩니다.

실제 프로덕션 환경에서는 다시 마운트되지 않을 수 있지만, 모든 컴포넌트에서 동일한 제약 조건을 따르면 코드를 이동하고 재사용하기가 더 쉬워집니다. 일부 로직이컴포넌트 마운트당 한 번이 아니라앱 로드당 한 번실행되어야 한다면, 이미 실행되었는지 추적하기 위한 최상위 변수를 추가하세요:

모듈 초기화 중과 앱 렌더링 전에 실행할 수도 있습니다:

최상위 수준의 코드는 컴포넌트가 가져와질 때 한 번 실행됩니다—렌더링되지 않더라도 말이죠. 임의의 컴포넌트를 가져올 때 속도 저하나 예상치 못한 동작을 피하려면 이 패턴을 과도하게 사용하지 마세요. 앱 전체 초기화 로직은App.js와 같은 루트 컴포넌트 모듈이나 애플리케이션의 진입점에 유지하세요.

상태 변경을 부모 컴포넌트에 알리기

내부 Toggle컴포넌트를 작성 중이며, 내부isOn 상태는 true또는false일 수 있다고 가정해 보겠습니다. 이를 토글하는 방법은 몇 가지 있습니다(클릭하거나 드래그하여).Toggle내부 상태가 변경될 때마다 부모 컴포넌트에 알리고 싶으므로onChange이벤트를 노출하고 Effect에서 호출합니다:

이전과 마찬가지로, 이 방식은 이상적이지 않습니다.Toggle이 먼저 자신의 상태를 업데이트하고, React가 화면을 업데이트합니다. 그런 다음 React가 Effect를 실행하여 부모 컴포넌트에서 전달된onChange함수를 호출합니다. 이제 부모 컴포넌트는 자신의 상태를 업데이트하게 되어 또 다른 렌더링 패스를 시작하게 됩니다. 모든 작업을 단일 패스에서 처리하는 것이 더 좋습니다.

Effect를 삭제하고 대신 동일한 이벤트 핸들러 내에서컴포넌트의 상태를 업데이트하세요:

이 접근 방식에서는Toggle컴포넌트와 그 부모 컴포넌트 모두 이벤트 발생 중에 자신의 상태를 업데이트합니다. React는 서로 다른 컴포넌트의 업데이트를일괄 처리하므로 렌더링 패스는 단 한 번만 발생합니다.

상태를 완전히 제거하고, 대신 부모 컴포넌트로부터isOn을 받아올 수도 있습니다:

"상태 끌어올리기"를 통해 부모 컴포넌트가 자신의 상태를 토글함으로써Toggle을 완전히 제어할 수 있습니다. 이는 부모 컴포넌트에 더 많은 로직이 포함되어야 함을 의미하지만, 전체적으로 걱정해야 할 상태는 줄어듭니다. 두 개의 다른 상태 변수를 동기화하려고 할 때마다, 상태 끌어올리기를 시도해 보세요!

부모에게 데이터 전달하기

Child컴포넌트는 일부 데이터를 가져온 다음 Effect에서Parent컴포넌트로 전달합니다:

React에서는 데이터가 부모 컴포넌트에서 자식 컴포넌트로 흐릅니다. 화면에 문제가 보이면 컴포넌트 체인을 거슬러 올라가면서 잘못된 prop을 전달하거나 잘못된 state를 가진 컴포넌트를 찾을 때까지 정보의 출처를 추적할 수 있습니다. 자식 컴포넌트가 Effect에서 부모 컴포넌트의 state를 업데이트하면 데이터 흐름을 추적하기가 매우 어려워집니다. 자식과 부모 모두 동일한 데이터가 필요하므로, 부모 컴포넌트가 해당 데이터를 가져오게 하고 자식에게전달하도록하는 것이 좋습니다:

이 방식이 더 간단하며 데이터 흐름을 예측 가능하게 유지합니다: 데이터는 부모에서 자식으로 아래로 흐릅니다.

외부 스토어 구독하기

때로는 컴포넌트가 React state 외부의 일부 데이터를 구독해야 할 수 있습니다. 이 데이터는 서드파티 라이브러리나 내장 브라우저 API에서 올 수 있습니다. 이 데이터는 React가 알지 못하는 상태로 변경될 수 있으므로, 컴포넌트를 수동으로 구독해야 합니다. 이는 종종 Effect를 사용하여 수행되며, 예를 들면 다음과 같습니다:

여기서 컴포넌트는 외부 데이터 스토어(이 경우 브라우저navigator.onLineAPI)를 구독합니다. 이 API는 서버에 존재하지 않으므로(따라서 초기 HTML 생성에 사용할 수 없음) 초기 state는true로 설정됩니다. 브라우저에서 해당 데이터 스토어의 값이 변경될 때마다 컴포넌트는 자신의 state를 업데이트합니다.

이를 위해 Effect를 사용하는 것이 일반적이지만, React에는 외부 스토어 구독을 위한 특수 목적의 Hook이 있어 이를 대신 사용하는 것이 권장됩니다. Effect를 삭제하고useSyncExternalStore호출로 대체하세요:

이 접근 방식은 Effect를 사용하여 변경 가능한 데이터를 React state에 수동으로 동기화하는 것보다 오류 발생 가능성이 적습니다. 일반적으로 위의useOnlineStatus()와 같은 커스텀 Hook을 작성하여 개별 컴포넌트에서 이 코드를 반복하지 않도록 합니다.React 컴포넌트에서 외부 스토어 구독에 대해 자세히 알아보세요.

데이터 가져오기

많은 앱이 데이터 가져오기를 시작하기 위해 Effect를 사용합니다. 다음과 같은 데이터 가져오기 Effect를 작성하는 것은 매우 일반적입니다:

이 fetch를 이벤트 핸들러로 옮길필요는 없습니다.

이것은 앞선 예시들에서 로직을 이벤트 핸들러에 넣어야 했던 것과 모순되어 보일 수 있습니다! 그러나, fetch의 주요 이유가타이핑 이벤트가 아니라는 점을 고려해 보세요. 검색 입력은 종종 URL에서 미리 채워지며, 사용자는 입력을 건드리지 않고 뒤로 가기와 앞으로 가기를 통해 탐색할 수 있습니다.

어디서 pagequery가 왔는지는 중요하지 않습니다. 이 컴포넌트가 보이는 동안에는 현재results동기화된 상태로 유지하고 싶을 것입니다. 네트워크로부터의 데이터와 현재pagequery에 대한 데이터와 말이죠. 이것이 Effect인 이유입니다.

그러나 위의 코드에는 버그가 있습니다."hello"를 빠르게 타이핑한다고 상상해 보세요. 그러면query"h"에서"he","hel","hell", 그리고"hello"로 변경될 것입니다. 이는 별도의 fetch를 시작하지만, 응답이 어떤 순서로 도착할지 보장할 수 없습니다. 예를 들어,"hell" 응답이 이후에도착할 수 있습니다."hello"응답보다 말이죠.setResults()를 마지막으로 호출하게 되므로, 잘못된 검색 결과를 표시하게 됩니다. 이를"경쟁 조건"이라고 합니다: 두 개의 다른 요청이 서로 "경쟁"하여 예상과 다른 순서로 들어온 것입니다.

경쟁 조건을 해결하려면, 오래된 응답을 무시하기 위해클린업 함수를 추가해야 합니다:

이렇게 하면 Effect가 데이터를 가져올 때, 마지막으로 요청된 응답을 제외한 모든 응답이 무시됩니다.

경쟁 상태를 처리하는 것은 데이터 페칭 구현 시 겪는 유일한 어려움은 아닙니다. 응답 캐싱(사용자가 뒤로 가기를 클릭했을 때 이전 화면을 즉시 볼 수 있도록), 서버에서 데이터를 가져오는 방법(초기 서버 렌더링된 HTML에 스피너 대신 가져온 콘텐츠가 포함되도록), 그리고 네트워크 폭포 현상을 피하는 방법(모든 부모 컴포넌트를 기다리지 않고 자식 컴포넌트가 데이터를 가져올 수 있도록)에 대해서도 고려해야 할 수 있습니다.

이러한 문제는 React뿐만 아니라 모든 UI 라이브러리에 적용됩니다. 이를 해결하는 것은 간단하지 않기 때문에, 현대의프레임워크는 Effect에서 데이터를 가져오는 것보다 더 효율적인 내장 데이터 페칭 메커니즘을 제공합니다.

프레임워크를 사용하지 않거나(자체적으로 구축하지 않으면서) Effect에서의 데이터 페칭을 더 편리하게 만들고 싶다면, 다음 예시처럼 페칭 로직을 커스텀 훅으로 추출하는 것을 고려해 보세요:

에러 처리와 콘텐츠 로딩 상태 추적을 위한 로직도 추가하고 싶을 것입니다. 이러한 훅을 직접 구축하거나 React 생태계에서 이미 제공되는 많은 솔루션 중 하나를 사용할 수 있습니다.이것만으로는 프레임워크의 내장 데이터 페칭 메커니즘을 사용하는 것만큼 효율적이지는 않지만, 데이터 페칭 로직을 커스텀 훅으로 옮기면 나중에 효율적인 데이터 페칭 전략을 채택하기가 더 쉬워집니다.

일반적으로, Effect를 작성해야 할 때는 위의 useData컴포넌트 내에 원시적인useEffect호출이 적을수록 애플리케이션 유지 관리가 더 쉬워집니다.

요약

  • 렌더링 중에 계산할 수 있는 것은 Effect가 필요하지 않습니다.
  • 비용이 많이 드는 계산을 캐시하려면useMemouseEffect대신 추가하세요.
  • 전체 컴포넌트 트리의 상태를 초기화하려면 다른key를 전달하세요.
  • prop 변경에 대한 응답으로 특정 상태 비트를 초기화하려면 렌더링 중에 설정하세요.
  • 컴포넌트가 표시되었기 때문에실행되는 코드는 Effect에 있어야 하며, 나머지는 이벤트에 있어야 합니다.
  • 여러 컴포넌트의 상태를 업데이트해야 한다면 단일 이벤트 중에 수행하는 것이 더 좋습니다.
  • 서로 다른 컴포넌트의 상태 변수를 동기화하려고 할 때마다 상태 끌어올리기를 고려하세요.
  • Effect로 데이터를 가져올 수 있지만, 경쟁 상태를 피하기 위해 정리(cleanup)를 구현해야 합니다.

Try out some challenges

Challenge 1 of 4:Transform data without Effects #

The TodoList below displays a list of todos. When the “Show only active todos” checkbox is ticked, completed todos are not displayed in the list. Regardless of which todos are visible, the footer displays the count of todos that are not yet completed.

Simplify this component by removing all the unnecessary state and Effects.