728x90
요즘 리액트를 사용하면서, 시니어 엔지니어들 또한 useEffect의 늪에 빠져있는 경우가 있다.
솔직히 우리는 useEffect에 너무 많은걸 집어 넣고 있고, 그걸로 인해 코드의 가독성이 떨어지고 어떤 상태를 원하는지 이해하기 어려워 사이드 이펙트를 마주하는 경우에 곤란한 상황이 많다.
useEffect(()=>{
doSomething(); // Effect
}, [whenever, these, things] // Dependency Array)
요즘엔 doSomething()과 같은 선언형 함수를 많이 사용하는데,
- 명령형으로 useEffect를 작성한다면 무슨일이 발생 할 때 이 effect 실행시킨다
- 선언형은 무슨일이 발생할 때 상태를 변경시키고, 변화된 상태에 의존하는 부분을 변경 시킨다. 특정 조건이 True 라면 effect가 실행되지만 어떤 이유로 다시 실행되곤 한다. (대신 혹시 모를 문제를 방지하기 위해 켜둔 Strict Mode에서만..)
David Khourshid는 useEffect는 명령형으로 선언하기를 선호한다고 주장한다.
그래서 useEffect가 Synchronization을 위한거라면 이제 까지 사용한 Effect들은 어디로 가야할까?
- 1. 데이터를 변화 시킬 때 useEffect -> useMemo를 사용하자.
// useEffect
function Cart() {
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
useEffect(()=>{
setTotal(
items.reduce((curruentTotal, item)=>{
return currentTotal + item.price;
}, 0)
)
}, [items])
}
const total = useMemo(() =>
items.reduce((currentTotal, item) => {
...
},0),
[items])
- 2. 부모 컴포넌트와 Communication 할 때 useEffect가 아닌 eventHandler를 사용하자.
// X
function Product() {
const [isOpen, setIsOpen] = useState(false)
useEffect(()=> {
if(isOpen) {
onOpen();
} else {
onClose();
}
}, [isOpen])
...
}
// to be
function useToggleView({onOpen, onClose}) {
const [isOpen, setIsOpen] = useState(false);
function toggler() {
const nextIsOpen = !open;
setIsOpen(nextIsOpen);
if(nextIsOpen) {
onOpen();
} else {
onClose();
}
}
return [isOpen, toggler];
}
function Product() {
const [isOpen, toggler] = useToggleView({onOpen, onClose});
return (
<button onClick={toggler}>...
)
}
- 3. 외부 저장소를 subscribing 할 때 useEffect가 아닌 useSyncExternalStore hook을 사용하자.
(하지만 SWR, react-query, use를 사용한다면 hook으로 해결 가능)
// X
useEffect(()=>{
const sub = storeApi.subscribe({ status }) => {
setIsConnectedStatus === 'connected'
})
return () => {
sub.unsubscribe
} // clear
}, [])
// to be
const isConnected = useSyncExternalStore(
// subscribe
storeApi.subscribe,
() => storeApi.getStatus() === 'connected',
true
)
- 4. 데이터를 Fetching 할 때 useEffect 보다 renderAsYourFetch를 사용하자
// X
useEffect(()=>{
let isCancelled = false;
getItems().then((data)=>{
if(isCancelled) return;
setItems(data);
})
return () => {
isCancelled = true;
}
})
// Remix
export const loader = async () => {
const items = await getItems();
return json(items)
}
export default function Store() {
const items = useLoaderData();
}
// Next
function Store({ items }) { ... }
export async function getServerSideProps() {
const items = await getItems();
return { props: { items} }
}
export default Store;
// React Query
const queryClient = useQueryClient();
return (
<div...
queryClient.prefetchQuery('items', getItems);
- 5. Global Singletons을 Initialize하기 위해서 UseEffect 안에서 사용하지 말고 그냥 호출하자.
// X
useEffect(()=>{
storeApi.authenticate(); // 두 번 호출됨
}, []);
// Just Call it
storeApi.authenticate();
function Store() {...}
// 테스트나 SSR을 위해 함수 내부로 이동시킨다면
function renderApp() {
if (typeof window !== undefiend) {
storeApi.authenticate();
}
...
}
- 6. userEvent를 handling하기 위해서 useEffect 대신 eventHandler를 사용하자.
// X
useEffect(()=>{
...
submitData(event)
.then(()=>{
...
})
.catch(err=> {
...
})
})
// X
<Form onSubmit={event => {
if(isLoading) return
// side effect !
submitData(event)
.then(()=> {...}
.catch(err => ...)
setIsLoading(true)
}}>...
// extract logic to hooks
<Form onSubmit={event => {
send({ type: 'submit', data: event });
}}>...
https://velog.io/@jay/useSyncExternalStore
https://stately.ai/blog/goodbye-useeffect-at-react-next-conference
https://www.youtube.com/watch?v=bGzanfKVFeU&ab_channel=BeJS
'software engineering > frontend' 카테고리의 다른 글
브라우저의 동작원리 (0) | 2023.01.07 |
---|---|
웹 접근성(a11y)과 storybook addon (0) | 2023.01.06 |
(React) clean code - SOLID (1) | 2022.11.29 |
SSR 그리고 사용자 경험 (0) | 2022.11.25 |
Migrate to Next.js 13 on Turborepo (0) | 2022.10.28 |