이번 포스팅은 4-3장과 5-1장까지 내용을 정리한 포스팅입니다.
📌4-3장 내용 정리하기
예전에 Next.js
에서 Css-in-Js
관련된 라이브러리를 사용했을 때, 호환되지 않는 부분이 많았는데 Next.js
의 문서화가 정리되면서 호환성 관련해서 많이 개선이 되었다는 것을 알게 되었어요.
npm install styled-components
styled-component
를 설치하고, next.config.js
에 아래와 같이 설정만 해주면 끝나더군요...!🥹
module.exports = {
compiler: {
styledComponents: true,
},
}
이외에도 emotion
이나 styled-jsx
설정 관련해서 Next.js
공식문서에 아주 잘 정리되어있는 것을 확인할 수 있었습니다.
사실 저는 4-3장에서 왜 Next.js
에서 Css-in-Js
가 제대로 호환되지 않는 이유에 대해서 자세하게 기술되어있을 줄 알았는데, 아래 문장으로 끝나있더군요 🥲
스타일이 브라우저에 뒤늦게 추가되어 FOUC(flash of unstyled content)라는, 스타일이 입혀지지 않은 날것의 HTML을 잠시동안 사용자에게 노출하게 된다.
그래서 Next.js
에서 app라우터를 사용한다는 가정에, Css-in-Js
설정을 제대로 해주지 않으면 대체로 use client
디렉티브를 사용해야하더라구요.. Next.js
에서 framer-motion
을 사용할 때, 대체로 모두 use client
디렉티브를 사용해야했던...😅 그래서 반응이 매우 다양해야하는 페이지를 사용할거라면 Next.js
사용은 비추하는 듯 했습니다.
아무래도 SSR
같은 경우에는 처음에 로드되는 HTML
은 입혀지지 않은 상태로 보여지고 이후 JS번들이 hydrate
되면서 반응형이나 스타일이 뒤늦게 적용되기 때문인 것 같았어요.
- 결론
Next.js
에서 너무 반응이 다양해야하는 페이지는 적절하지 못하다.css-in-js
를 사용한다면, 설정을 제대로 해주지 않으면 스타일이 적용되지 않은HTML
이 잠깐 보일 수 있다.
📌5-1장 내용 정리하기
5-1장이 책 내용이 직접 상태라이브러리를 구현해보는 내용이었는데요, 책을 읽으면서 tearing
현상과 useSyncExternalStore
훅을 왜쓰는지에 대한 이해를 하고자 내용을 정리해보았습니다.
많은 블로그에서 tearing
현상이 아래와 같은 현상이라고 많이 적혀있었습니다.
많은 블로그에서 tearing
관련된 설명 이미지를 아래를 첨부하고 있었는데요, 실질적으로 저는 이 tearing
현상을 직접적으로 보고 싶었습니다.
그럼 이 tearing
현상이 왜 일어나고 어떤 현상일까요?
📌 externalStore, internalStore
리액트 관련 관계자들은 externalStore
와 internalStore
에 대해서 분류를 나눠서 설명하고 있습니다.
- externalStore: mobx, redux, recoil, jotai, xtsate, zustand, rect query와 같은 외부 상태라이브러리들
- internalStore: useState, useReducer, context, props와 같은 리액트에서 제공하는 상태관리 도구
tearing
현상은 externalStore
, 즉 외부상태라이브러리를 사용할 때, 주로 일어나는 현상이라고 합니다.
internalStore
같은 경우에는 리액트에서 만든 관리도구다보니, fiber
아키텍처가 적용된 이후로 내부적으로 상태관련해서 렌더링의 우선순위나, 스케줄링 등 관련 깊은 알고리즘이 잘 구현되어있다고해요. 이 알고리즘 관련된 코드는 사용자들이 볼 수 없으며 관리도구 API를 사용할 수 있기만 할뿐이라고 합니다.(useReducer, useState...)
반대로 externalStore
는 리액트가 만든 라이브러리가 아니다보니, 렌더링 관련해서 문제가 생길 수 있다는 점이 존재했다고 해요. 그게 바로 tearing
현상입니다.
📌 tearing 현상 이해하기
아까 위에서 업로드 된 이미지를 기반으로 먼저 설명을 해보자면, 렌더링 도중 들어오는 유저 인터렉션에 대해 기존 렌더링을 중지하고 인터렉션에 대한 UI를 먼저 렌더링 하기 때문에 아래처럼 빨간색으로 렌더링 트리를 업데이트하는 도중 파란색의 인터렉션으로 인해 트리의 일부분이 파란색으로 렌더링될 수 있습니다. 이런 현상을 tearing
이라고 하는데요,
쉽게 말하면 유저 인터렉션으로 인해서 기존 렌더링이 중지되었다가, 다시 돌아와 렌더링을 진행하는데, 이전 렌더링과 이후 렌더링이 일치하지 않는 현상이라고 이해하면 될 것 같습니다.
그래도 저는 이론적으로 설명이 조금 애매모호 하다고 느낀 것 같아요. 그래서 tearing
현상을 직접 구현한 블로그와 코드가 있어서 같이 업로드 해봅니다.
기본적으로 리액트에서 외부 시스템과 동기화할 때 useEffect
라는 훅을 사용한다고 해요. 따라서 컴포넌트의 렌더링 이후 화면이 업데이트 된 후에 발생하게 됩니다.
이런 특성을 사용한다면 리액트 외부에 있는 스토어와 동기화하는데 사용할 수 있게 됩니다.
Redux
와 비슷한 방식으로 스토어를 만들어서 useEffect
를 사용해 외부 시스템과 동기화를 구현해보겠습니다.
아래 예제에서는 Store
의 count
변수를 setInterval
를 사용하여 1초마다 1씩 증가하도록 dispatch
하고 이를 구독하는 Counter
컴포넌트를 만든 것입니다.
const store = {
state: { count: 0 },
listeners: new Set<() => void>(),
// subscribe의 callback함수는 state 내부에서 구독할 값을 필터링 거는 함수입니다.
subscribe: (callback: () => void) => {
store.listeners.add(callback);
return () => {
store.listeners.delete(callback);
};
},
};
export const dispatch = (action: { type: string }) => {
if (action.type === "increment") {
store.state = { count: store.state.count + 1 };
}
store.listeners.forEach((listener) => listener());
};
export const useStore = () => {
const [state, setState] = useState(store.state);
useEffect(() => {
const handleChange = () => setState(store.state);
const unsubscribe = store.subscribe(handleChange);
return unsubscribe;
}, []);
return state;
};
전체 코드는 아래 링크에서 확인하실 수 있습니다.
Fullcode
실제 코드를 실행시켜보면 사진처럼 버튼을 누르면 위와 같이 렌더링 중간에 스토어의 값이 바뀌었기 때문에 UI에 상태가 다르게 표시되는 Tearing
현상이 발생하는 것을 확인할 수 있습니다.
tearing
현상에 대해서 더 자세한 글은 아래에서 확인하실 수 있습니다.
리액트에서 외부 시스템과 동기화하기
📌 externalStore에서 tearing 현상이?
그래서 externalStore
을 사용하면 이런 tearing
현상이 존재했다고 합니다. 그래서 redux
의 메인테이너 개발자분께서 tearing
현상을 막기위해 리액트 팀이 만든 useMutableStore
훅을 사용 시 selector
함수를 useCallback
으로 감싸줘야 하는 필요성에 대해 이야기를 했다고 합니다.
근데 selector
함수에 매번 useCallback
을 감싸야하는 건 보일러플레이트뿐만 아니라 넘 귀찮은 일이죠🥲
이전에 부트캠프에 참여했을 때, 리덕스를 배웠었는데 그때 selector
함수를 매번 useCallback
으로 감싸야하냐는 질문을 했었는데, 그러면 좋겠죠...?
라는 말을 들었었는데... 그 이유가 tearing
현상 때문이었다는 것을 오늘에서야 이해하게 되었네요.😅
하지만 redux
는 v8이후부터는 패치가 적용되어서 useCallback
을 사용하지 않아도 된다고 어디서 본 것 같은데, 확실하지는 않지만 패치되었다고 알고 있습니다. tearing
현상을 막기 위해 패치가 적용되면서 사용한 훅이 useSyncExternalStore
라고 합니다. 실제로 외부 상태 라이브러리를 직접 구현했을 때, tearing
현상을 막기 위해 useSyncExternalStore
훅을 이용하면 보일러플레이트를 크게 줄일 수 있다고 해요.
📌 startTransition사용하면 tearing 현상을 막아주는 useSyncExternalStore
2021년도에 Daishi Kato
가 리액트컨퍼런스2021년에 설명해놓은 영상인데요, 이영상을 보면 훅의 사용법을 한번에 이해할 수 있습니다...!
React 18 for External Store Libraries
Reference Doc
useSyncExternalStore 어후 이름이 너무 길어.
React - useSyncExternalStore hook
Concurrent React
리액트에서 외부 시스템과 동기화하기
React 18 useSyncExternalStore를 이용하여 전역 상태 구현하기 ex) recoil, zustand, jotai