[참고: Synchronizing with Effects]
Some components need to synchronize with external systems. Effects let you run some code after rendering so that you can synchronize your component with some system outside of React.
리액트 공식문서를 살펴보다보면 흥미로운 카테고리가 존재합니다.
Escape Hatches 라는 탈출구라는 카테고리입니다.
여기에 속해있는 내용들을 살펴보면, ref / effect 입니다.
이들의 공통점이 뭘까요?
해당 글의 인용문가 이전 글의 인용문에 나와있듯이 이 두 개념은 리액트 렌더링 흐름에서의 탈출구를 제공해주는 개념들입니다.
이전 글에서는 ref를 사용하면 상태를 렌더링 흐름에 벗어나 관리 가능하다는 내용을 서술했었습니다.
이번에 다룰 effect는 인용문에서 나와있듯이 렌더링 후에 사이드 이펙트를 관리 할 수 있다는 내용입니다.
우선 이 내용을 이해하기 위해서는 리액트에서 사이드 이펙트란 무엇인지 알아야합니다
사이드 이펙트
[참고1: Side Effects: (un)intended consequences]
[참고2: Side effects must run outside of render]
Side effects should not run in render
위의 글에서 나와있는 내용들을 요약하면 사이드 이펙트란 컴포넌트를 호출하여 JSX를 반환하는 흐름에서 발생하는 것들을 제외한 나머지 부수 효과라는 뜻이라고 말합니다.
예시로, 데이터 가져오기, DOM을 직접 수정하거나, API 호출 등을 언급하고 있습니다.
이러한 리액트 렌더링 흐름과 관련없는 사이드 이펙트를 따로 관리해야 한다고 말합니다. (이유로는 리액트 컴포넌트 순수해야 하고, 이를 통해 예측 가능해야 하기 때문이라고 말합니다)
요약하면 사이드 이펙트는 리액트 렌더링 흐름과 관련 없는 것들이고, 이것들은 따로 관리되어야한다고 합니다.
그러면 이것들을 어떻게 관리할 수 있을까요?
이벤트 헨들러로 사이드 이펙트 관리
가장 먼저 권장되는 방법은 이벤트 헨들러에 사이드 이펙트 로직을 넣어서 관리하라는 방법입니다.
그리고 그 이유로는 이벤트 핸들러를 사용하면 React에 이 코드가 render 중에 실행될 필요가 없다고 명시적으로 알려 render 순수성을 유지 할 수 있기 때문이라고 언급합니다
무슨 말일까요?
function Greeting ({ name }) {
const [index, setIndex] = React.useState(0)
const greetings = ['Hello', "Hola", "Bonjour"]
const handleClick = () => {
const nextIndex = index === greetings.length - 1
? 0
: index + 1
setIndex(nextIndex)
}
localStorage.setItem("index", index)
return (
<main>
<h1>{greetings[index]}, {name}</h1>
<button onClick={handleClick}>Next Greeting</button>
</main>
)
}
export default function App () {
return <Greeting name="Tyler" />
}
위의 예제에서 외부 시스템(로컬 스토리지)와 상호작용을 하고 있고, 이 외부 시스템과의 동기화를 해야하는 코드가 있습니다.
현재 코드는 잘 동작합니다. 하지만 위에서 계속 언급한 내용에 위반됩니다.
외부 시스템이 렌더링 흐름에 관여되고 있습니다.
즉, 이것을 올바른 코드로 리팩토링 하기 위해서는 사이드 이펙트 로직을 렌더링 흐름에서 제외시켜야합니다. 이때 사용할 수 있는 첫번째 방법이 이벤트 헨들러입니다.
const handleClick = () => {
const nextIndex = index === greetings.length - 1
? 0
: index + 1
setIndex(nextIndex)
localStorage.setItem("index", nextIndex)
}
이제 사이드 이펙트가 렌더링 흐름에서 벗어나서 이벤트 헨들러 내부에서 작동하고, 이를 통해 render 중에 실행될 필요가 없다고 명시적으로 알려줍니다.
Effect
모든 사이드 이펙트 로직을 이벤트 헨들러에 넣어서 처리 가능하면 문제가 없지만, 대부분의 앱은 불가능 할 것입니다.
예를들어,
function Greeting ({ name }) {
const [index, setIndex] = React.useState(() => {
return Number(localStorage.getItem("index"))
})
const greetings = ['Hello', "Hola", "Bonjour"]
const handleClick = () => {
const nextIndex = index === greetings.length - 1
? 0
: index + 1
setIndex(nextIndex)
localStorage.setItem("index", nextIndex)
}
return (
<main>
<h1>{greetings[index]}, {name}</h1>
<button onClick={handleClick}>Next Greeting</button>
</main>
)
}
export default function App () {
return <Greeting name="Tyler" />
}
위의 코드에서 사이드 이펙트 로직을 딱 한번만 가져오고 있습니다. (참고: Lazy initialization)
하지만 딱 한번이라고 하지만 앞에서 계속 언급한 문제에 걸립니다.
사이드 이펙트가 렌더링 흐름에 관여되어있습니다. 이는 피해야하는 패턴입니다.
하지만 위의 로직에서 저 사이드 이펙트 로직을 이벤트 헨들러로 빼는 것은 불가능해보입니다.
이럴때 다음으로 고려할 수 있는 사이드 이펙트 관리법은 `useEffect`를 활용하는 것입니다.
맨 처음에 가져온 참고: Synchronizing with Effect 글에서 언급하듯이 외부 시스템과 동기화하기 위해서는 `effect`를 활용하라 합니다.
useEffect 내부에 들어있는 로직의 첫 실행 시점은 렌더링 직후입니다.
즉, 렌더링이 완료된 후 외부 시스템과 동기화하기에는 최적의 시점입니다.
useEffect(() => {
const storedIndex = Number(localStorage.getItem("index"));
if (!isNaN(storedIndex)) {
setIndex(storedIndex); //
}
}, []);
'React' 카테고리의 다른 글
[React] 공통 컴포넌트를 설계할 때 고려할 점 (2) | 2024.09.21 |
---|---|
[React] React Query 알아보기 01 - 왜 React Query를 사용해야하는지 (0) | 2024.09.18 |
[React] Suspense 이해하고 활용하기 01 - Suspense를 활용하면 스피너 지옥에서 탈출할 수 있습니다 (0) | 2024.09.18 |
[React] ref는 단순히 DOM 조작 용도가 아닙니다 (2) | 2024.09.15 |
[React] React에서 불변성은 왜 중요할까? (2) | 2024.09.15 |