useEffect의 예시나
dependency(의존성) 배열을 다시 살펴보면서
useEffect에 대해 좀더 자세히 배워보기 이전에
명확하게 해 두고 싶은 점이 있습니다
모든 부수 효과가 useEffect의 사용을 필요로 하지는 않으며
useEffect의 과한 사용이나
불필요한 곳에서의 사용은 좋지 않다는 것입니다
이것은 실행 과정에서
앱 또는 그 외의 컴포넌트가 실행된 이후
추가적인 실행이 진행되는
방식이라는 것을 잊지 말아야 합니다
그러므로 필요한 것이 아니라면 useEffect의 사용을 자제하는 것이 좋습니다
필요하지 않은 상황의 예시를 들어보겠습니다
여기 이 handleSelectPlace에서
그저 상태가 업데이트되는 것이 아니라
이렇게 장소 목록에 장소를 추가하기 위해
클릭을 했을 때
선택한 장소와 선택한 장소들의 목록 전체가
브라우저의 저장소에
저장되기를 원한다고 해봅시다
우리가 앱을 다시 로딩했을 때
이 장소들이 없어지지 않고
그대로 있는 상태를 원하는 것입니다
그렇게 작업을 해 보겠습니다
그렇게 하기 위해서는
브라우저에 심어진 다른 함수를 사용하면 되는데
navigator 기능과 마찬가지로
브라우저가 제공하는 localStorage 라는 객체입니다
이 localStorage는 리액트에서 온 것도 아니고
제가 만든 것도 아니고
브라우저에서 온 것입니다
localStroage는 우리가 setItem 메소드를 사용해
브라우저의 저장소에 데이터를 저장할 수 있도록 해주는데
우리가 웹사이트를 떠나거나
다시 돌아오거나
웹사이트를 다시 로딩해도
해당 데이터는 그대로 유지됩니다
localStorage는 사용하기에 비교적 쉽습니다
setItem으로 selectedPlaces와 같은
식별자를 전달하고
두 번째 인수로는
저장되어야 할 값을 전달합니다
알려드려야 할 중요한 부분은
이 데이터가 꼭 문자열이어야 한다는 것입니다
그러므로 배열이나 객체를 그대로 저장할 수는 없고
내장된 JSON.stringfy 메소드 등을 이용해
해당 데이터를 문자열 형태로
먼저 변환해 주어야만 합니다
이 또한 브라우저에서 제공하는
메소드이고 객체입니다
JSON.stringfy의 경우, 주어진 배열을
저장 가능한 문자열로 변환시켜 줍니다
제가 JSON.stringfy에 전달하고자 하는 배열은
제가 선택한 장소들의 장소 ID 인데
여기 보여지는 이 ID와
앞서 저장된 ID 모두입니다
그러려면 앞서 저장된
ID가 필요한데
그것은 localStorage.getItem을 사용해 가져올 수 있습니다
그리고 물론, 저장할 때 사용한 것과
동일한 key(키)를 사용해서
localStorage로부터 데이터를 추출하고
그 데이터는 저장되었던 방식 그대로
문자열 형태로 추출되게 됩니다
이것을 다시 배열로 변환하기 위해서는
다시 JSON 객체를 사용해야 하는데
이번에는 parse 메소드라고 하는
stringfy 와는 반대되는 메소드를 사용해야 합니다
지금은 물론 저장해둔 장소가 없을 수 있기 때문에
일단은 fallback(대체) 코드를 추가해서
정의되지 않은 데이터가 산출될 경우
빈 배열이 만들어지도록 해두겠습니다
이제 storedIds가 만들어졌으니
여기에 storedIds를 이렇게 넣어서
저장하고자 하는 데이터를
이 배열에 펼쳐주고
storedIds 앞에 지금 막 선택한
새로운 장소 ID를 넣어 주겠습니다
UI 업데이트를 하기 위해서
현재 구동 중인 앱에서
상태 데이터를 업데이트한 것 외에
이 ID 리스트를 여기에 저장하는데
이 경우엔 localStorage가 되겠습니다
또한, 이미 존재하는 ID를
이 목록에 다시 저장하는 대신
if 체크를 추가해서
storedIds 에 ID가 이미 들어가있는지를 확인합니다
여기에 인수로 들어가있는 ID인데
이것은 indexOf를 사용하면 됩니다
만약 결과가 -1 으로 나온다면
해당 ID가 아직 storedIds의 일부가 아니라는 것이고
그렇다면
저장된 데이터를 업데이트하면 되지만
그것이 아니라면 아무것도 하지 않으면 됩니다
여기에 있는 모든 코드는
결국 부수 효과를 위한 또다른 예시일 뿐인데
사용자의 위치를 파악하는 것과 마찬가지로
우리가 브라우저의 저장소에
데이터를 저장하는 이 코드는
이 JSX 코드의 렌더링과 직접적인 연관이 없습니다
반면, 상태를 업데이트하는 부분은
해당되는 상태 업데이트는
새로운 JSX snapshot(스냅샷)으로 바로 이어지기 때문이지만
여기 있는 저장소는 전혀 그렇지 않기 때문입니다
물론 그럼에도 불구하고
이 앱 필요한 코드이고
그것이 있어야만 사용자가 만든
장소 목록을 유지할 수 있는 기능을 추가할 수 있습니다
그러므로 이 코드가 나쁜 것은 아닙니다
여러분이 확실히 이해하셔야 됩니다만
이것은 단지 부수 효과일 뿐입니다
여기 있는 navigator 코드와는 달리
이쪽 아래의 코드는 useEffect로 묶어줄
필요가 없습니다
게다가 우리는 함수 안에 들어와 있기 때문에
여기에서 useEffect를 사용할 수도 없습니다
그러한 사용은 훅의 규칙을 어기는 것에 속하게 되는데
훅의 규칙이란, 중첩된 함수나
If문 등에서
리액트 훅의 사용이 불가하다는 것입니다
우리가 여기에 사용한 것처럼
컴포넌트 함수의 root(루트) 층위에서
직접적으로만 사용이 가능합니다
그렇지만 여기에서도 코드 실행에는 문제가 없기에
useEffect는 필요가 없는 상황입니다
이 코드의 실행 조건은
handleSelectPlace 함수의 실행인데
이 함수가 실행되기 위해서는
사용자가 이 아이템 중 하나를 클릭해야만 합니다
그리고 이 코드는 상태를 업데이트하지 않기 때문에
무한 루프에 들어가게 되지 않습니다
그리고 설사 localStorage 데이터 저장과 관련해서
상태 업데이트를 하게 되는
상황이 된다고 하더라도
무한 루프는 일어나지 않습니다
왜냐하면 handleSelectPlace 함수에 속한 이 코드는
앱 컴포넌트 함수가 재실행되는 때가 아닌
사용자가 아이템을 클릭할 때 실행되기 때문입니다
그러므로 컴포넌트 함수 코드가 실행될 때
함께 작동해야 했던 navigator 코드와는 달리
이 localStorage 데이터 저장 코드는
사용자 상호작용이 있어야만 작동하기에
설령 상태 업데이트가 되더라도
무한 루프를 만들어내지 않습니다
이 점을 이해하는 것이 매우 중요합니다
모든 부수 효과에 useEffect가 필요한 것은 아닙니다
useEffect 훅이 필요한 경우는
무한 루프를 방지하기 위해서이거나
컴포넌트 함수가 최소 한번은 실행된 이후에
작동이 가능한 코드가 있을 때뿐입니다
조금 후에 관련 예시를 보여드리겠습니다
그러나 여기 localStorage 코드는 전혀 문제가 없습니다