From 7bd74746447caa82ae520c2f891035c1b605e6cd Mon Sep 17 00:00:00 2001 From: brgndy Date: Sun, 16 Jun 2024 18:05:10 +0900 Subject: [PATCH] docs: How-useSyncExternalStore()-works-internally --- ...ernalStore()-works-internally-in-React?.md | 880 ++++++++++++++++++ 1 file changed, 880 insertions(+) create mode 100644 June/article/How-useSyncExternalStore()-works-internally-in-React?.md diff --git a/June/article/How-useSyncExternalStore()-works-internally-in-React?.md b/June/article/How-useSyncExternalStore()-works-internally-in-React?.md new file mode 100644 index 0000000..d02c537 --- /dev/null +++ b/June/article/How-useSyncExternalStore()-works-internally-in-React?.md @@ -0,0 +1,880 @@ +## ๐Ÿ”— [How useSyncExternalStore() works internally in React?](https://jser.dev/2023-08-02-usesyncexternalstore/) + +### ๐Ÿ—“๏ธ ๋ฒˆ์—ญ ๋‚ ์งœ: 2024.06.10 + +### ๐Ÿงš ๋ฒˆ์—ญํ•œ ํฌ๋ฃจ: ๋ฒ„๊ฑด๋””(์ „ํƒœํ—Œ) + +--- + +# ๋ฆฌ์•กํŠธ์—์„œ useSyncExternalStore๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”๊ฐ€ ? + +`useSyncExternalStore()`๋Š” ์™ธ๋ถ€ ์ €์žฅ์†Œ์— ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” React ํ›…์ž…๋‹ˆ๋‹ค. + +์ €๋Š” ์ด ํ›…์„ ์‚ฌ์šฉํ•ด๋ณธ ์ ์ด ์—†์ง€๋งŒ, ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์™€ ๊ฐ™์€ ์ž์ฒด ์ƒํƒœ๋ฅผ ๊ฐ€์ง„ ์›น API์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ฝค ์œ ์šฉํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. + +## 1. ์™œ useSyncExternalStore๋Š” ํ•„์š”ํ•œ๊ฐ€ ? + +์•„๋ž˜๋Š” React.dev์—์„œ ๊ฐ€์ ธ์˜จ [๋ฐ๋ชจ ์ฝ”๋“œ](https://react.dev/reference/react/useSyncExternalStore)์ž…๋‹ˆ๋‹ค. + +์ด ์ฝ”๋“œ๋Š” ๋ธŒ๋ผ์šฐ์ €์˜ ํ˜„์žฌ ์˜จ๋ผ์ธ ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. + +๋„คํŠธ์›Œํฌ๋ฅผ ๋„๋ฉด ์ƒํƒœ๊ฐ€ ๊ทธ์— ๋”ฐ๋ผ ์—…๋ฐ์ดํŠธ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ผ๋ถ€ ์ฝ”๋“œ๋Š” ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + +## - useOnlineStatus.js + +```jsx +import { useEffect, useCallback, useState } from "react"; + +export function useOnlineStatus() { + const [isOnline, setIsOnline] = useState(navigator.onLine); + + const update = useCallback(() => { + setIsOnline(navigator.onLine); + }, []); + + useEffect(() => { + window.addEventListener("online", update); + window.addEventListener("offline", update); + return () => { + window.removeEventListener("online", update); + window.removeEventListener("offline", update); + }; + }, [update]); + + return isOnline; +} +``` + +## - App.js + +```jsx +import { useOnlineStatus } from "./useOnlineStatus.js"; + +function StatusBar() { + const isOnline = useOnlineStatus(); + return

{isOnline ? "โœ… Online" : "โŒ Disconnected"}

; +} + +function SaveButton() { + const isOnline = useOnlineStatus(); + + function handleSaveClick() { + console.log("โœ… Progress saved"); + } + + return ( + + ); +} + +export default function App() { + return ( + <> +

Turn on & off your network to see the status changing

+ + + ); +} +``` + +์ €๋งŒ์˜ `useOnlineStatus`๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. + +```jsx +import { useEffect, useCallback, useState } from "react"; +export function useOnlineStatus() { + const [isOnline, setIsOnline] = useState(navigator.onLine); + const update = useCallback(() => { + setIsOnline(navigator.onLine); + }, []); + useEffect(() => { + window.addEventListener("online", update); + window.addEventListener("offline", update); + return () => { + window.removeEventListener("online", update); + window.removeEventListener("offline", update); + }; + }, [update]); + return isOnline; +} +``` + +์ฝ”๋“œ๋Š” ๊ดœ์ฐฎ์•„ ๋ณด์ด์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ๊ฒฌ๊ณ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +ํ•ต์‹ฌ ๋ฌธ์ œ๋Š” ์™ธ๋ถ€ ์ €์žฅ์†Œ๊ฐ€ ์–ธ์ œ๋“ ์ง€ ์—…๋ฐ์ดํŠธ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ `useState()`์™€ `useEffect()` ์‚ฌ์ด์— ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด ๊ฒฝ์šฐ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋‚˜์ค‘์— ๋“ฑ๋ก๋˜๋ฏ€๋กœ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๊ฐ์ง€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. + +์ข€ ๋” ์ผ์ฐ ์‹คํ–‰๋˜๋Š” `useLayoutEffect()`๋‚˜ `useInsertionEffect()`๋กœ ์ „ํ™˜ํ•ด๋„ ๋ฌธ์ œ๋Š” ํ•ด๊ฒฐ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +์™ธ๋ถ€ ์ €์žฅ์†Œ์— ๋Œ€ํ•ด ์•„๋ฌด๊ฒƒ๋„ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. + +์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด **์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์™ธ๋ถ€ ์ €์žฅ์†Œ๋ฅผ ์ฒ˜์Œ ๊ฐ€์ ธ์˜ฌ ๋•Œ๋ณด๋‹ค ๋Šฆ์ง€ ์•Š๊ฒŒ ๋“ฑ๋ก๋˜๋„๋ก ํ•˜๊ฑฐ๋‚˜**, ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋“ฑ๋ก๋œ ํ›„ ํ•œ ๋ฒˆ ๋” ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +์•„๋ž˜์™€ ๊ฐ™์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +```ts +import { useEffect, useCallback, useState } from "react"; +export function useOnlineStatus() { + const [isOnline, setIsOnline] = useState(navigator.onLine); + const update = useCallback(() => { + setIsOnline(navigator.onLine); + }, []); + useEffect(() => { + window.addEventListener("online", update); + window.addEventListener("offline", update); + update(); + // ์™ธ๋ถ€ ์ €์žฅ์†Œ๊ฐ€ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ•œ ๋ฒˆ ๋” ํ™•์ธํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. + + return () => { + window.removeEventListener("online", update); + window.removeEventListener("offline", update); + }; + }, [update]); + return isOnline; +} +``` + +์ค‘๋ณต๋œ ์ฝ”๋“œ๋ฅผ ์ง€์›€์œผ๋กœ์จ ์กฐ๊ธˆ ๋” ๋‚ซ๋„๋ก ์ˆ˜์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +```ts +import { useEffect, useCallback, useState } from "react"; +function getIsOnLine() { + return navigator.onLine; +} +function subscribe(callback) { + window.addEventListener("online", callback); + window.addEventListener("offline", callback); + callback(); + // ์ด๋Š” ์ตœ์‹  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. + + return () => { + window.removeEventListener("online", callback); + window.removeEventListener("offline", callback); + }; +} +export function useOnlineStatus() { + const [isOnline, setIsOnline] = useState(getIsOnLine()); + const update = useCallback(() => { + setIsOnline(getIsOnLine()); + }, []); + useEffect(() => { + return subscribe(update); + }, [update]); + return isOnline; +} +``` + +์šฐ๋ฆฌ๋Š” ์ด๊ฒƒ์„ `useSyncExternalStore()`์ฒ˜๋Ÿผ ๋” ๋น„์Šทํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +```tsx +import { useEffect, useCallback, useState } from "react"; + +function getIsOnLine() { + return navigator.onLine; +} + +function subscribe(callback) { + window.addEventListener("online", callback); + window.addEventListener("offline", callback); + return () => { + window.removeEventListener("online", callback); + window.removeEventListener("offline", callback); + }; +} +function useSyncExternalStore(subscribe, getSnapshot) { + const [data, setData] = useState(getSnapshot()); + const update = useCallback(() => { + setData(getSnapshot()); + }, []); + useEffect(() => { + update(); + return subscribe(update); + }, [update]); + return data; +} + +export function useOnlineStatus() { + const isOnline = useSyncExternalStore(subscribe, getIsOnLine); + return isOnline; +} +``` + +## - App.js + +```tsx +import { useOnlineStatus } from "./useOnlineStatus.js"; + +function StatusBar() { + const isOnline = useOnlineStatus(); + return

{isOnline ? "โœ… Online" : "โŒ Disconnected"}

; +} + +export default function App() { + return ( + <> +

Turn on & off your network to see the status changing

+ + + ); +} +``` + +์šฐ๋ฆฌ๊ฐ€ ์™ธ๋ถ€ ์ €์žฅ์†Œ๋ฅผ ๋™๊ธฐํ™”ํ•  ๋•Œ ์–ด๋ ค์šด ๋ถ€๋ถ„์€ ์™ธ๋ถ€์˜ ๋ชจ๋“  ์—…๋ฐ์ดํŠธ๊ฐ€ ๊ฐ์ง€๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + +์šฐ๋ฆฌ์˜ ํ•ด๊ฒฐ์ฑ…์€ **์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ๋“ฑ๋ก๋œ ํ›„ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋„๋ก ๋ณด์žฅํ•˜๋Š” ๊ฒƒ**์ž…๋‹ˆ๋‹ค. + +ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ์šฐ๋ฆฌ์˜ ๊ตฌํ˜„์—๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ '์ฐข๊น€(tear)' ํ˜„์ƒ์ž…๋‹ˆ๋‹ค. + +## - ์ฐข๊น€(Tearing) ๋ฌธ์ œ + +๋ฆฌ์•กํŠธ ํŒ€์ด [์ฐข๊น€(tearing)](https://github.com/reactwg/react-18/discussions/69) ํ˜„์ƒ์— ๋Œ€ํ•ด ํ›Œ๋ฅญํ•˜๊ฒŒ ์„ค๋ช…ํ•ด์ฃผ์—ˆ๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํžˆ ์š”์•ฝํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +๋ฆฌ์•กํŠธ ํŒ€์—์„œ [useTransition()์ด ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์„ค๋ช…](https://jser.dev/2023-05-19-how-does-usetransition-work/)ํ•  ๋•Œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, ๋™์‹œ ๋ชจ๋“œ(concurrent mode)์—์„œ๋Š” ๋ Œ๋”๋ง ๋‹จ๊ณ„๊ฐ€ ์ž‘์—…์˜ ์šฐ์„ ์ˆœ์œ„์— ๋”ฐ๋ผ ์ค‘๋‹จ๋˜๊ณ  ์žฌ๊ฐœ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ž˜์„œ ๋ฆฌ์•กํŠธ๊ฐ€ React ํŠธ๋ฆฌ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๋™์•ˆ, ์ด ๊ณผ์ •์ด ์ค‘๋‹จ๋˜๊ณ  ๋น„๋™๊ธฐ ์Šค์ผ€์ค„๋ง์œผ๋กœ ์—ฌ๋Ÿฌ ๋ฒˆ ์‹œ๋„๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด ์Šค์ผ€์ค„๋ง์ด ๋™๊ธฐ์ ์ด์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, [React ์Šค์ผ€์ค„๋Ÿฌ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™](https://jser.dev/react/2022/03/16/how-react-scheduler-works)ํ•˜๋Š”์ง€ ์„ค๋ช…ํ•  ๋•Œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, ๊ธฐ๋ณธ์ ์œผ๋กœ ์ตœ์†Œ ์ง€์—ฐ์ด ์—†๋Š” ๋” ๋‚˜์€ `setTimeout()`์ด๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +๋”ฐ๋ผ์„œ ๋ Œ๋”๋ง ๋„์ค‘ ์™ธ๋ถ€ ์ €์žฅ์†Œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด UI์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๊ฐ€ ์ปค๋ฐ‹๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๊ฒƒ์ด ์ฐข๊น€(tear) ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜๋Š” ์ด์œ ์ด๋ฉฐ, ์•„๋ž˜๋Š” ๊ทธ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. + +## - App.js + +```tsx +import { useEffect, startTransition, useCallback, useState } from "react"; + +let data = 1; +function getData() { + return data; +} + +setTimeout(() => (data = 2), 100); + +function Cell() { + let start = Date.now(); + while (Date.now() - start < 50) { + // ๋™์‹œ์„ฑ ๋ชจ๋“œ์—์„œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์— ์ž‘์—…์„ ์–‘๋ณดํ•ฉ๋‹ˆ๋‹ค. + // 50๋ฐ€๋ฆฌ์ดˆ ๋™์•ˆ ์‹คํ–‰ ๋˜์–ด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ์ฐจ๋‹จํ•˜๊ณ , ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ + // ์ด ์‹œ๊ธด ๋™์•ˆ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. + } + const data = getData(); + return
{data}
; +} + +export default function App() { + const [showCells, setShowCells] = useState(false); + + useEffect(() => { + startTransition(() => setShowCells(true)); + }, []); + return ( + <> +

+ Example of tearing.
+ below are multiple cells rendering same external data which changes during + rendering. +

+ {showCells ? ( +
+ + + + +
+ ) : ( +

preparing..

+ )} + + ); +} +``` + +์ฐข๊น€(tear) ํ˜„์ƒ์€ ๋™์‹œ ๋ชจ๋“œ(concurrent mode)์—์„œ ๋ Œ๋”๋ง์ด ์ค‘๋‹จ๋  ๊ฐ€๋Šฅ์„ฑ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. + +(์œ„ ์ฝ”๋“œ์—์„œ `startTransition()`์„ ์ œ๊ฑฐํ•˜๋ฉด ์ฐข๊น€ ํ˜„์ƒ์ด ์žฌํ˜„๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.) + +React๋Š” ๋‚ด๋ถ€ Fiber ์•„ํ‚คํ…์ฒ˜์— ์šฐ๋ฆฌ๊ฐ€ ๊ฐœ์ž…ํ•˜๋Š” ๊ฒƒ์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ๋ฐฉ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค. + +๊ทธ๋ž˜์„œ React๋Š” ์šฐ๋ฆฌ์—๊ฒŒ `useSyncExternalStore()`๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +์•„๋ž˜๋Š” ์œ„ ์˜ˆ์ œ์—์„œ ์•ฝ๊ฐ„ ์ˆ˜์ •ํ•œ ํ›„ `useSyncExternalStore()`๋ฅผ ์‚ฌ์šฉํ•œ ๋ฐ๋ชจ์ž…๋‹ˆ๋‹ค. + +## - App.js + +```ts +import { useEffect, useSyncExternalStore, startTransition, useCallback, useState } from 'react'; + +let data = 1 +function getData() { + return data +} + +setTimeout(() => data = 2, 100) + +function Cell() { + let start = Date.now() + while (Date.now() - start < 50) { + // ๋™์‹œ์„ฑ ๋ชจ๋“œ์—์„œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์— ์ž‘์—…์„ ์–‘๋ณดํ•ฉ๋‹ˆ๋‹ค. + // 50๋ฐ€๋ฆฌ์ดˆ ๋™์•ˆ ์‹คํ–‰ ๋˜์–ด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ์ฐจ๋‹จํ•˜๊ณ , ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ + // ์ด ์‹œ๊ธด ๋™์•ˆ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. + } + const data = useSyncExternalStore(() => {return () => {}},getData); + return
{data}
+} + +export default function App() { + const [showCells, setShowCells] = useState(false) + + useEffect(() => { + startTransition(() => setShowCells(true)) + }, []) + return ( + <> +

Example of tearing.
below are multiple cells rendering same external data which changes during rendering.

+ {showCells ?
+ +
:

preparing..

} + + ); +} +``` + +์ฐข๊น€(tearing) ํ˜„์ƒ์ด ๊ณ ์ณ์ง„๊ฑธ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +## 2. ์–ด๋–ป๊ฒŒ useSyncExternalStore()๊ฐ€ ๋ฆฌ์•กํŠธ ๋‚ด๋ถ€์—์„œ ๋™์ž‘ํ•˜๋‚˜์š” ? + +์š”์•ฝํ•˜์ž๋ฉด, `useSyncExternalStore()`๋Š” ์šฐ๋ฆฌ์—๊ฒŒ ๋‘ ๊ฐ€์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. + +1. ์™ธ๋ถ€ ์ €์žฅ์†Œ์˜ ๋ชจ๋“  ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ๊ฐ์ง€๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. + +2. ๋™์‹œ ๋ชจ๋“œ(concurrent mode)์—์„œ๋„ ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ UI์—์„œ ๋™์ผํ•œ ์ €์žฅ์†Œ๋กœ ๋ Œ๋”๋ง๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. + +์ด์ œ React ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณด๋ฉด์„œ ์ด๊ฒƒ์ด ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„๋˜๋Š”์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +### 2.1 ์ดˆ๊ธฐ ๋งˆ์šดํŠธ ์‹œ์— mountSyncExternalStore() + +```ts +function mountSyncExternalStore( + subscribe: (() => void) => () => void, + getSnapshot: () => T, + getServerSnapshot?: () => T, +): T { + const fiber = currentlyRenderingFiber; + const hook = mountWorkInProgressHook(); + // ์ƒˆ๋กœ์šด ํ›…์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. + + let nextSnapshot; + const isHydrating = getIsHydrating(); + if (isHydrating) { + ... + } else { + nextSnapshot = getSnapshot(); +retrieve the data once, just like we did in initializer of useState() + +// ๋ธ”๋กœํ‚น ๋ ˆ์ธ์„ ๋ Œ๋”๋งํ•˜์ง€ ์•Š๋Š” ํ•œ, ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์Šค์ผ€์ค„๋งํ•ฉ๋‹ˆ๋‹ค. +// ์ปค๋ฐ‹ํ•˜๊ธฐ ์ง์ „์— ํŠธ๋ฆฌ๋ฅผ ํƒ์ƒ‰ํ•˜์—ฌ +// ์–ด๋–ค ์ €์žฅ์†Œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +// +// ์„œ๋ฒ„ ๋ Œ๋”๋ง๋œ ์ฝ˜ํ…์ธ ๋ฅผ ์ˆ˜๋ถ„ ๊ณต๊ธ‰(hydrating)ํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š” ์ด๊ฒƒ์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +// ์ฝ˜ํ…์ธ ๊ฐ€ ์˜ค๋ž˜๋˜์—ˆ์„ ๊ฒฝ์šฐ, ์–ด์ฐจํ”ผ ์ด๋ฏธ ๋ณด์ด๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. +// ๋Œ€์‹ , ์šฐ๋ฆฌ๋Š” ์ด๋ฅผ ์ˆ˜๋™ ํšจ๊ณผ(passive effect)์—์„œ ์ˆ˜์ •ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + + const root: FiberRoot | null = getWorkInProgressRoot(); + if (!includesBlockingLane(root, renderLanes)) { + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); + } + +// ์ด ๋ถ€๋ถ„์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. +// ์ปค๋ฐ‹ํ•˜๊ธฐ ์ง์ „์— ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒ€์‚ฌ๋ฅผ ์Šค์ผ€์ค„๋งํ•ฉ๋‹ˆ๋‹ค. +// ์ด๋Š” ๋น„์ฐจ๋‹จ ๋ ˆ์ธ(non-blocking lane)์—๋งŒ ์ ์šฉ๋˜๋ฉฐ, ๊ธฐ๋ณธ์ ์œผ๋กœ ์ด๋Š” ๋™์‹œ ๋ชจ๋“œ(concurrent mode)์—์„œ ์ž‘๋™ํ•จ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. +// ์ด๋Š” ์•ž์„œ ์–ธ๊ธ‰ํ•œ ์ฐข๊น€(tear) ํ˜„์ƒ์„ ํ•ด๊ฒฐํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + + } + +// ๋งค ๋ Œ๋”๋ง ์‹œ๋งˆ๋‹ค ์ €์žฅ์†Œ์—์„œ ํ˜„์žฌ ์Šค๋ƒ…์ƒท์„ ์ฝ์–ด์˜ต๋‹ˆ๋‹ค. +// ์ด๊ฒƒ์€ React์˜ ์ผ๋ฐ˜์ ์ธ ๊ทœ์น™์„ ๊นจ์ง€๋งŒ, ์ €์žฅ์†Œ ์—…๋ฐ์ดํŠธ๋Š” ํ•ญ์ƒ ๋™๊ธฐ์ ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. + hook.memoizedState = nextSnapshot; +// ์ด hoo.memoizedState๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. +// ์šฐ๋ฆฌ์˜ ๊ตฌํ˜„์—์„œ useState()์™€ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ํ•ฉ๋‹ˆ๋‹ค. + + const inst: StoreInstance = { + value: nextSnapshot, + getSnapshot, + }; + hook.queue = inst; + +//์ด๊ฒƒ์€ ์šฐ๋ฆฌ๊ฐ€ "React์—์„œ useState()๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”๊ฐ€"์—์„œ ์–ธ๊ธ‰ํ•œ ์—…๋ฐ์ดํŠธ ํ์™€ ์œ ์‚ฌํ•ด ๋ณด์ด์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” ํ•„๋“œ ์ด๋ฆ„๋งŒ ๋นŒ๋ ค์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. + + //์ €์žฅ์†Œ์— ๊ตฌ๋…ํ•˜๊ธฐ ์œ„ํ•œ ํšจ๊ณผ๋ฅผ ์Šค์ผ€์ค„๋งํ•ฉ๋‹ˆ๋‹ค. + mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]); +Great, mountEffect() is the internal of useEffect(), so + +it is similar to what we do, scheduling a (passive) effect to init subscription + +// ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ ์ธ์Šคํ„ด์Šค ํ•„๋“œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์œ„ํ•œ ํšจ๊ณผ๋ฅผ ์Šค์ผ€์ค„๋งํ•ฉ๋‹ˆ๋‹ค. +// subscribe, getSnapshot ๋˜๋Š” ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ด๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. +// ์ •๋ฆฌ(clean-up) ํ•จ์ˆ˜๊ฐ€ ์—†๊ณ , ์ข…์†์„ฑ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ถ”์ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— +// ์ถ”๊ฐ€ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š๊ณ ๋„ ์ง์ ‘ pushEffect๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +// ๊ฐ™์€ ์ด์œ ๋กœ, ์ •์  ํ”Œ๋ž˜๊ทธ๋ฅผ ์„ค์ •ํ•  ํ•„์š”๋„ ์—†์Šต๋‹ˆ๋‹ค. +// TODO: ์ปค๋ฐ‹ ์ „ ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์ด๋ฅผ ์ˆ˜๋™ ํšจ๊ณผ(passive phase)๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +// ๋‹ค์Œ ์ฃผ์„์„ ์ฐธ์กฐํ•˜์„ธ์š”. + fiber.flags |= PassiveEffect; + + // pushEffect : React์—์„œ useEffect()๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์„ค๋ช…ํ•œ ๊ฒƒ๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. + // ์ด๊ฒƒ์€ ํšจ๊ณผ๋ฅผ Fiber์— ํ‘ธ์‹œํ•ฉ๋‹ˆ๋‹ค. + pushEffect( + + + HookHasEffect | HookPassive, +// HookPassive : ์ˆ˜๋™ ํšจ๊ณผ(passive effect)๋Š” useEffect()์—์„œ ํ•œ ๊ฒƒ๊ณผ ์œ ์‚ฌํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. + + updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), +// updateStoreInstance : +// ์ดˆ๊ธฐ ๋งˆ์šดํŠธ ์‹œ, nextSnapshot์ด ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฒ€์‚ฌ์˜ ์ด์ ์„ ์ทจํ•ฉ๋‹ˆ๋‹ค. ์ด๋ก ์ ์œผ๋กœ 314๋ฒˆ ์ค„์˜ getSnapshot()๊ณผ 359๋ฒˆ ์ค„์˜ mountEffect() ์‚ฌ์ด์— ์ž‘์€ ์ฐฝ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ฐข๊น€(tear) ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ์™€๋Š” ๋‹ฌ๋ฆฌ, ์ตœ์‹  ๋ฐ์ดํ„ฐ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์‹œ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์šฐ๋ฆฌ์˜ ๊ตฌํ˜„์—์„œ update()๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœํ•˜๋Š” ๋ชฉ์ ๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. + + undefined, + null, + ); + return nextSnapshot; +} +function subscribeToStore(fiber, inst, subscribe) { + const handleStoreChange = () => { +// ์ €์žฅ์†Œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +// ๋งˆ์ง€๋ง‰์œผ๋กœ ์ €์žฅ์†Œ์—์„œ ์ฝ์€ ์ดํ›„ ์Šค๋ƒ…์ƒท์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + if (checkIfSnapshotChanged(inst)) { + // ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค. + forceStoreRerender(fiber); + } + }; +// ์ €์žฅ์†Œ์— ๊ตฌ๋…ํ•˜๊ณ  ์ •๋ฆฌ(clean-up) ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + return subscribe(handleStoreChange); +} +function updateStoreInstance( + fiber: Fiber, + inst: StoreInstance, + nextSnapshot: T, + getSnapshot: () => T, +) { +// ์ด๊ฒƒ๋“ค์€ ์ˆ˜๋™ ๋‹จ๊ณ„์—์„œ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค. + inst.value = nextSnapshot; + inst.getSnapshot = getSnapshot; + +// ๋ Œ๋”์™€ ์ปค๋ฐ‹ ์‚ฌ์ด์— ๋ฌด์–ธ๊ฐ€๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +// ์ด๊ฒƒ์€ ์ˆ˜๋™ ํšจ๊ณผ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ์—์„œ ๋ฐœ์ƒํ–ˆ์„ ์ˆ˜๋„ ์žˆ๊ณ , +// ๋ ˆ์ด์•„์›ƒ ํšจ๊ณผ์—์„œ ๋ฐœ์ƒํ–ˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. +// ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” ์ด์ „ ์Šค๋ƒ…์ƒท๊ณผ getSnapshot ๊ฐ’์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น ์ ธ๋‚˜๊ฐ”์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. +// ํ•œ ๋ฒˆ ๋” ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + + if (checkIfSnapshotChanged(inst)) { + // ๋ฆฌ๋ Œ๋”๋ง์„ ๋‹ค์‹œ ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค. + forceStoreRerender(fiber); + } +} +function checkIfSnapshotChanged(inst) { + const latestGetSnapshot = inst.getSnapshot; + const prevValue = inst.value; + try { + const nextValue = latestGetSnapshot(); + return !is(prevValue, nextValue); + // subscribeToStore()์™€ updateStoreInstance() ๋‘˜ ๋‹ค์—์„œ, +// ์ด ๊ฒ€์‚ฌ๋Š” ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๊ฒฝ์šฐ ๋ฆฌ๋ Œ๋”๋ง์ด ์Šค์ผ€์ค„๋ง๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + + } catch (error) { + return true; + } +} +function forceStoreRerender(fiber) { + const root = enqueueConcurrentRenderForLane(fiber, SyncLane); + if (root !== null) { + scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); + +// SyncLane : ์—ฌ๊ธฐ์„œ ๋ฆฌ๋ Œ๋”๋ง์„ ์œ„ํ•ด SyncLane์„ ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค. React๊ฐ€ ์ดˆ๊ธฐ ๋งˆ์šดํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ์ˆ˜ํ–‰ํ•˜๋Š”์ง€ ์„ค๋ช…ํ–ˆ๋“ฏ์ด, SyncLane์€ ๋ธ”๋กœํ‚น ๋ ˆ์ธ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ์ƒˆ๋กœ์šด ๋ Œ๋”๋ง์€ ๋™์‹œ ๋ชจ๋“œ์—์„œ ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฐข๊น€(tear) ํ˜„์ƒ์€ ๋™์‹œ ๋ชจ๋“œ์—์„œ๋งŒ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋Š” ๋‹ค์Œ ๋ Œ๋”๋ง์—์„œ ์ฐข๊น€ ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. + + } +} +``` + +์ฝ”๋“œ๋Š” ๋‹ค์†Œ ๋ณต์žกํ•˜์ง€๋งŒ, ์•„์ด๋””์–ด๋Š” ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. + +์ค‘์š”ํ•˜๊ฒŒ ๋ณด์•„์•ผํ•  ์ ์€ ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€์ž…๋‹ˆ๋‹ค. + +๊ณ„์†ํ•ด์„œ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. + +### 2.2 ๋ฆฌ๋ Œ๋”๋ง ์•ˆ์—์„œ updateSyncExternalStore()๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”๊ฐ€ + +```ts + +function updateSyncExternalStore( + subscribe: (() => void) => () => void, + getSnapshot: () => T, + getServerSnapshot?: () => T, +): T { + const fiber = currentlyRenderingFiber; + const hook = updateWorkInProgressHook(); +// ๋งค ๋ Œ๋”๋ง๋งˆ๋‹ค ์ €์žฅ์†Œ์—์„œ ํ˜„์žฌ ์Šค๋ƒ…์ƒท์„ ์ฝ์–ด์˜ต๋‹ˆ๋‹ค. +// ์ด๋Š” React์˜ ์ผ๋ฐ˜ ๊ทœ์น™์„ ๊นจ๋œจ๋ฆฌ์ง€๋งŒ, ์ €์žฅ์†Œ ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•ญ์ƒ ๋™๊ธฐ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. + const nextSnapshot = getSnapshot(); + + const prevSnapshot = hook.memoizedState; + const snapshotChanged = !is(prevSnapshot, nextSnapshot); + if (snapshotChanged) { + hook.memoizedState = nextSnapshot; + // ๋”ฐ๋ผ์„œ ์ƒˆ๋กœ์šด ์ƒํƒœ๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋ฉด, ๋ฐ์ดํ„ฐ๋ฅผ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. + + markWorkInProgressReceivedUpdate(); + } + const inst = hook.queue; + updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [ + subscribe, + ]); +// ์ด๋Š” ์ข…์†์„ฑ(deps)์— ๋”ฐ๋ผ ์—…๋ฐ์ดํŠธ์™€ ์ •๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด useEffect()๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. + +// getSnapshot ๋˜๋Š” subscribe๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค, +// ์ปค๋ฐ‹ ๋‹จ๊ณ„์—์„œ ์ค‘์ฒฉ๋œ ๋ณ€๊ฒฝ์ด ์žˆ์—ˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +// ๋™์‹œ ๋ชจ๋“œ์—์„œ๋Š” ์ด๋Ÿฌํ•œ ์ผ์ด ์ž์ฃผ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, +// ์‹ฌ์ง€์–ด ๋™๊ธฐ ๋ชจ๋“œ์—์„œ๋„ ์ด์ „ ํšจ๊ณผ๊ฐ€ ์ €์žฅ์†Œ๋ฅผ ๋ณ€๊ฒฝํ–ˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + if ( + inst.getSnapshot !== getSnapshot || + snapshotChanged || +// subscribe ํ•จ์ˆ˜๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. +// ์œ„์—์„œ ๊ตฌ๋… ํšจ๊ณผ๋ฅผ ์Šค์ผ€์ค„๋งํ–ˆ๋Š”์ง€ ํ™•์ธํ•จ์œผ๋กœ์จ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + (workInProgressHook !== null && + workInProgressHook.memoizedState.tag & HookHasEffect) + ) { + fiber.flags |= PassiveEffect; + pushEffect( + HookHasEffect | HookPassive, + updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), + undefined, + null, + ); +// getSnapshot()์ด ๋ณ€๊ฒฝ๋˜๋ฉด, ์˜์กด์„ฑ ๋ฐฐ์—ด์ด ๋ณ€๊ฒฝ๋  ๋•Œ useEffect()๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ํšจ๊ณผ๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +// ๋ธ”๋กœํ‚น ๋ ˆ์ธ์„ ๋ Œ๋”๋งํ•˜์ง€ ์•Š๋Š” ํ•œ, ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์Šค์ผ€์ค„๋งํ•ฉ๋‹ˆ๋‹ค. +// ์ปค๋ฐ‹ ์ง์ „์— ํŠธ๋ฆฌ๋ฅผ ํƒ์ƒ‰ํ•˜์—ฌ ์ €์žฅ์†Œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. + const root: FiberRoot | null = getWorkInProgressRoot(); + if (root === null) { + throw new Error( + 'Expected a work-in-progress root. This is a bug in React. Please file an issue.', + ); + } + if (!includesBlockingLane(root, renderLanes)) { + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); + } +// ์—ฌ๊ธฐ์„œ ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ๋‹ค์‹œ ์Šค์ผ€์ค„๋ง๋ฉ๋‹ˆ๋‹ค. +// ์†”์งํžˆ ๋งํ•ด, ์™œ ์—ฌ๊ธฐ์— ๋ฐฐ์น˜๋˜์—ˆ๋Š”์ง€ ์ž˜ ์ดํ•ด๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค... + +// ์Šค๋ƒ…์ƒท ๋ณ€ํ™”์™€ getSnapshot() ๋ณ€ํ™”๊ฐ€ ์—†๋”๋ผ๋„ ์™ธ๋ถ€ ์ €์žฅ์†Œ๊ฐ€ ์šฐ๋ฆฌ์—๊ฒŒ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ๋ณ€๊ฒฝ๋  ๊ฐ€๋Šฅ์„ฑ์€ ์—ฌ์ „ํžˆ ์žˆ์Šต๋‹ˆ๋‹ค(์˜ˆ๋ฅผ ๋“ค์–ด, ์“ฐ๋กœํ‹€๋œ ์ด๋ฒคํŠธ๋ฅผ ์ƒ๊ฐํ•ด๋ณด์„ธ์š”). ๋”ฐ๋ผ์„œ ์ด ์กฐ๊ฑด์—์„œ ๊ฒ€์‚ฌ๋ฅผ ์—ฌ๊ธฐ์— ๋‘๋Š” ๊ฒƒ์€ ๋ฒ„๊ทธ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค. + +// 2023-08-11 ์—…๋ฐ์ดํŠธ: + +// ์ด๊ฒƒ์€ ๋ฒ„๊ทธ๊ฐ€ ์•„๋‹ˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ์ฃผ๋กœ subscribe()์™€ getSnapshot()์˜ ๋ณ€๊ฒฝ์— ๋Œ€ํ•ด ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์Šค์ผ€์ค„๋งํ•ฉ๋‹ˆ๋‹ค. PR์„ ํ™•์ธํ•ด๋ณด์„ธ์š”. (https://github.com/facebook/react/pull/27180) + + } + return nextSnapshot; +} +``` + +์ฝ”๋“œ๋Š” ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์Šค์ผ€์ค„๋งํ•˜๋Š” ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์„ ์ œ์™ธํ•˜๊ณ ๋Š” ํ•ฉ๋ฆฌ์ ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค. + +ํŠน์ • ์กฐ๊ฑด ํ•˜์—์„œ ์˜ˆ์ƒ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐ๋ชจ๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. + +## - App.js + +```ts + +import { + useEffect, + useSyncExternalStore, + startTransition, + useState, + useLayoutEffect, + useMemo, +} from 'react'; + +let data = 1; +let callbacks = []; + +function getSnapShot() { + return data; +} + +setTimeout(() => (data = 2), 800); + +function subscribe(callback) { + callbacks.push(callback); + return () => { + callbacks = callbacks.filter((item) => item != callback); + }; +} + +function Cell() { + let start = Date.now(); + while (Date.now() - start < 50) { + // force yielding to main thread in concurrent mode + } + const data = useSyncExternalStore(subscribe, getSnapShot); + + return
{data}
; +} + +export default function App() { + const [count, setCount] = useState(0); + + useEffect(() => { + startTransition(() => { + setCount((count) => count + 1); + }); + }, []); + + return ( +
+

+ Example of tearing.
+ below are multiple cells rendering same external data which changes + during rendering. +

+
+ + + + + + +
+
+ ); +} +``` + +์—ฌ๊ธฐ์„œ useSyncExternalStore()๋ฅผ ์‚ฌ์šฉํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์ฐข๊น€(tear) ํ˜„์ƒ์ด ๋‹ค์‹œ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ด๋Š” ๋ฒ„๊ทธ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•ด [ํ’€ ๋ฆฌํ€˜์ŠคํŠธ](https://github.com/facebook/react/pull/27180)๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. + +> ๋งŒ์•ฝ ์ฐข๊น€(tear) ํ˜„์ƒ์ด ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์ฝ”๋“œ์—์„œ ํƒ€์ž„์•„์›ƒ์„ ๋” ์ž‘์€ ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝํ•ด๋ณด์„ธ์š”. + +> 2023-08-11 ์—…๋ฐ์ดํŠธ: ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ํ•˜๋Š” ์—ญํ• ์„ ์˜คํ•ดํ•œ ๊ฒƒ์œผ๋กœ ๋“œ๋Ÿฌ๋‚ฌ์Šต๋‹ˆ๋‹ค. useSyncExternalStore()๋Š” ๋ชจ๋“  ์™ธ๋ถ€ ์ €์žฅ์†Œ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์ด ๋ฐ˜๋“œ์‹œ ๋ฐœ์ƒํ•˜๋„๋ก ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ subscribe() ๋‚ด๋ถ€์˜ forceStoreRerender()๊ฐ€ ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•œ ์ฐข๊น€ ํ˜„์ƒ์„ ์‹ค์ œ๋กœ ๋ฐฉ์ง€ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๋Š” subscribe() ๋˜๋Š” getSnapshot()์˜ ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•œ ์ฐข๊น€์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. + +### 2.3 ๋™์‹œ์„ฑ ๋ชจ๋“œ์—์„œ ์ผ๊ด€์„ฑ ํ™•์ธ + +์–ด๋–ป๊ฒŒ ์ผ๊ด€์„ฑ ์ฒดํฌ๊ฐ€ ์ด๋ฃจ์–ด์ง€๋Š”์ง€ ํ•œ๋ฒˆ ์ฐพ์•„๋ด…์‹œ๋‹ค. + +```ts +function pushStoreConsistencyCheck( + fiber: Fiber, + getSnapshot: () => T, + renderedSnapshot: T, +) { + fiber.flags |= StoreConsistency; + const check: StoreConsistencyCheck = { + getSnapshot, + value: renderedSnapshot, + }; + let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any); + if (componentUpdateQueue === null) { + componentUpdateQueue = createFunctionComponentUpdateQueue(); + currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any); + +// currentlyRenderingFiber.updateQueue : useEffect๋ฅผ ์ƒ๊ธฐํ•ด๋ณด๋ฉด, updateQueue๋Š” ํšจ๊ณผ๋“ค์„ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด lastEffect๋„ ๋ณด์œ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  renderWithHooks()์—์„œ ์ด๊ฒƒ์ด ์ง€์›Œ์ง€๋ฏ€๋กœ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง๋  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กญ๊ฒŒ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. + + componentUpdateQueue.stores = [check]; +// componentUpdateQueue.stores : ์™ธ๋ถ€ ์ €์žฅ์†Œ ๊ฒ€์‚ฌ๋ฅผ ์œ„ํ•œ ๋ณ„๋„์˜ ํ•„๋“œ์ž…๋‹ˆ๋‹ค. + + } else { + const stores = componentUpdateQueue.stores; + if (stores === null) { + componentUpdateQueue.stores = [check]; + } else { + stores.push(check); + } + } +} +``` + +์ด์ œ ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์–ธ์ œ ๊ทธ๋ฆฌ๊ณ  ์–ด๋–ป๊ฒŒ ํŠธ๋ฆฌ๊ฑฐ๋˜๋Š”์ง€ ๋ณผ ์ฐจ๋ก€์ž…๋‹ˆ๋‹ค. + +์ด๋Š” ์‹ค์ œ๋กœ ์ปค๋ฐ‹ ์ง์ „์—, ๋ Œ๋” ๋‹จ๊ณ„์˜ ๋์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. + +```ts +// ์ด ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ๋™์‹œ ์ž‘์—…์˜ ์ง„์ž…์ ์ž…๋‹ˆ๋‹ค. ์ฆ‰, Scheduler๋ฅผ ํ†ตํ•ด +// ์ง„ํ–‰๋˜๋Š” ๋ชจ๋“  ์ž‘์—…์€ ์—ฌ๊ธฐ๋กœ ๋“ค์–ด์˜ต๋‹ˆ๋‹ค. +export function performConcurrentWorkOnRoot( + root: FiberRoot, + didTimeout: boolean, +): RenderTaskFn | null { + ... + // ๋ฃจํŠธ์— ์ €์žฅ๋œ ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ ์ž‘์—…ํ•  ๋ ˆ์ธ์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. + // TODO: ํ˜ธ์ถœ์ž์—์„œ ์ด๋ฏธ ๊ณ„์‚ฐํ•œ ๊ฐ’์ž…๋‹ˆ๋‹ค. ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•˜๋„๋ก ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + let lanes = getNextLanes( + root, + root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes, + ); + if (lanes === NoLanes) { + // ๋ฐฉ์–ด์  ์ฝ”๋”ฉ. ์ด ๊ฒฝ์šฐ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. + return null; + } + // ํŠน์ • ๊ฒฝ์šฐ์—๋Š” ์‹œ๊ฐ„ ๋ถ„ํ• ์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค: ์ž‘์—…์ด ๋„ˆ๋ฌด ์˜ค๋ž˜ CPU์— ๋ฌถ์—ฌ ์žˆ์—ˆ์„ ๊ฒฝ์šฐ("๋งŒ๋ฃŒ๋œ" ์ž‘์—…์œผ๋กœ, ๊ธฐ์•„๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•จ) ๋˜๋Š” ๋™๊ธฐ ์—…๋ฐ์ดํŠธ ๊ธฐ๋ณธ ๋ชจ๋“œ์ผ ๊ฒฝ์šฐ. + // TODO: Scheduler ๋ฒ„๊ทธ๋ฅผ ์กฐ์‚ฌ ์ค‘์ด๋ผ, ๋ฐฉ์–ด์ ์œผ๋กœ `didTimeout`์„ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค. + // Scheduler์˜ ๋ฒ„๊ทธ๊ฐ€ ์ˆ˜์ •๋˜๋ฉด ์ด๋ฅผ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋งŒ๋ฃŒ๋ฅผ ์ž์ฒด์ ์œผ๋กœ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค. + const shouldTimeSlice = + !includesBlockingLane(root, lanes) && + !includesExpiredLane(root, lanes) && + (disableSchedulerTimeoutInWorkLoop || !didTimeout); + let exitStatus = shouldTimeSlice + ? renderRootConcurrent(root, lanes) + : renderRootSync(root, lanes); + + if (exitStatus !== RootInProgress) { + if (exitStatus === RootErrored) { + // ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๊ฒฝ์šฐ, ํ•œ ๋ฒˆ ๋” ๋ Œ๋”๋ง์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. + // ๋™์‹œ ๋ฐ์ดํ„ฐ ๋ณ€์กฐ๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด ๋™๊ธฐ์ ์œผ๋กœ ๋ Œ๋”๋งํ•˜๊ณ , ๋ชจ๋“  ๋ณด๋ฅ˜ ์ค‘์ธ ์—…๋ฐ์ดํŠธ๊ฐ€ ํฌํ•จ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. + // ๋‘ ๋ฒˆ์งธ ์‹œ๋„ ํ›„์—๋„ ์‹คํŒจํ•˜๋ฉด ๊ฒฐ๊ณผ ํŠธ๋ฆฌ๋ฅผ ์ปค๋ฐ‹ํ•ฉ๋‹ˆ๋‹ค. + const originallyAttemptedLanes = lanes; + const errorRetryLanes = getLanesToRetrySynchronouslyOnError( + root, + originallyAttemptedLanes, + ); + if (errorRetryLanes !== NoLanes) { + lanes = errorRetryLanes; + exitStatus = recoverFromConcurrentError( + root, + originallyAttemptedLanes, + errorRetryLanes, + ); + } + } + if (exitStatus === RootFatalErrored) { + const fatalError = workInProgressRootFatalError; + prepareFreshStack(root, NoLanes); + markRootSuspended(root, lanes); + ensureRootIsScheduled(root); + throw fatalError; + } + if (exitStatus === RootDidNotComplete) { + // ํŠธ๋ฆฌ๋ฅผ ์™„์„ฑํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋ Œ๋”๋ง์ด ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ผ๊ด€๋œ ํŠธ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์ปค๋ฐ‹ํ•˜์ง€ ์•Š๊ณ  + // ํ˜„์žฌ ๋ Œ๋”๋ง์„ ์ข…๋ฃŒํ•ด์•ผ ํ•˜๋Š” ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ์— ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. + markRootSuspended(root, lanes); + } else { + // ๋ Œ๋”๋ง์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. + // ์ด ๋ Œ๋”๋ง์ด ๋™์‹œ ์ด๋ฒคํŠธ์— ์–‘๋ณดํ–ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ + // ์ƒˆ๋กœ ๋ Œ๋”๋ง๋œ ์Šคํ† ์–ด๊ฐ€ ์ผ๊ด€๋œ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + // TODO: ๋ Œ๋”๋ง์ด ์ถฉ๋ถ„ํžˆ ๋น ๋ฅด๊ฑฐ๋‚˜ ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ ์ฃผ ์Šค๋ ˆ๋“œ์— ์–‘๋ณดํ•˜์ง€ ์•Š์•˜์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + const renderWasConcurrent = !includesBlockingLane(root, lanes); + const finishedWork: Fiber = (root.current.alternate: any); + if ( + renderWasConcurrent && + !isRenderConsistentWithExternalStores(finishedWork) + // ์—ฌ๊ธฐ์„œ ์ผ๊ด€์„ฑ ์ฒดํฌ๊ฐ€ ์ผ์–ด๋‚ฉ๋‹ˆ๋‹ค. + ) { + // ์Šคํ† ์–ด๊ฐ€ ๊ต์ฐจ ์ด๋ฒคํŠธ์—์„œ ๋ณ€์กฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋™์‹œ ๋ณ€์กฐ๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด ๋™๊ธฐ์ ์œผ๋กœ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. + exitStatus = renderRootSync(root, lanes); + // exitStatus = renderRootSync(root, lanes) : ๋™๊ธฐ ๋ชจ๋“œ์—์„œ ๋‹ค์‹œ ๋ Œ๋”๋ง์ด ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. + // ์ปค๋ฐ‹์ด ๋ฐœ์ƒํ•˜๊ธฐ ์ „์—, ์‚ฌ์šฉ์ž๋“ค์€ UI์—์„œ ์ฐข์–ด์ง(ํ™”๋ฉด ๊นœ๋นก์ž„ ๋“ฑ)์„ ๋ณด์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. + + // ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ๋‹ค์‹œ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + if (exitStatus === RootErrored) { + const originallyAttemptedLanes = lanes; + const errorRetryLanes = getLanesToRetrySynchronouslyOnError( + root, + originallyAttemptedLanes, + ); + if (errorRetryLanes !== NoLanes) { + lanes = errorRetryLanes; + exitStatus = recoverFromConcurrentError( + root, + originallyAttemptedLanes, + errorRetryLanes, + ); + // ๋™์‹œ ์ด๋ฒคํŠธ์— ์–‘๋ณดํ•˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ ์ด์ œ ํŠธ๋ฆฌ๊ฐ€ ์ผ๊ด€๋˜๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. + } + } + if (exitStatus === RootFatalErrored) { + const fatalError = workInProgressRootFatalError; + prepareFreshStack(root, NoLanes); + markRootSuspended(root, lanes); + ensureRootIsScheduled(root); + throw fatalError; + } + // FIXME: RootDidNotComplete๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ๊ตฌ์กฐ๊ฐ€ ์ด์ƒ์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + } + // ์ด์ œ ํŠธ๋ฆฌ๊ฐ€ ์ผ๊ด€๋œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ ๋‹จ๊ณ„๋Š” ์ด๋ฅผ ์ปค๋ฐ‹ํ•˜๊ฑฐ๋‚˜, + // ๋ฌด์–ธ๊ฐ€๊ฐ€ ๋Œ€๊ธฐ ์ค‘์ด๋ผ๋ฉด ํƒ€์ž„์•„์›ƒ ํ›„ ์ปค๋ฐ‹์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. + root.finishedWork = finishedWork; + root.finishedLanes = lanes; + finishConcurrentRender(root, exitStatus, + finishedWork, lanes); + // commitRoot()๋Š” ์—ฌ๊ธฐ์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. + } + } + ensureRootIsScheduled(root); + return getContinuationForRoot(root, originalCallbackNode); +} +``` + +```ts +function isRenderConsistentWithExternalStores(finishedWork: Fiber): boolean { + // ๋ Œ๋”๋œ ํŠธ๋ฆฌ์—์„œ ์™ธ๋ถ€ ์Šคํ† ์–ด ์ฝ๊ธฐ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๊ณ , ๋™์‹œ ์ด๋ฒคํŠธ์—์„œ ์Šคํ† ์–ด๊ฐ€ ๋ณ€์กฐ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. + // ์žฌ๊ท€ ๋Œ€์‹  ๋ฐ˜๋ณต ๋ฃจํ”„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ผ์ฐ ์ข…๋ฃŒํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. + let node: Fiber = finishedWork; + while (true) { + if (node.flags & StoreConsistency) { + const updateQueue: FunctionComponentUpdateQueue | null = + (node.updateQueue: any); + if (updateQueue !== null) { + const checks = updateQueue.stores; + if (checks !== null) { + for (let i = 0; i < checks.length; i++) { + const check = checks[i]; + const getSnapshot = check.getSnapshot; + const renderedValue = check.value; + try { + if (!is(getSnapshot(), renderedValue)) { + // ๋ถˆ์ผ์น˜ํ•˜๋Š” ์Šคํ† ์–ด๋ฅผ ๋ฐœ๊ฒฌํ–ˆ์Šต๋‹ˆ๋‹ค. + return false; + } + } catch (error) { + // `getSnapshot`์ด ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ฉด `false`๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + // ์ด๋กœ ์ธํ•ด ๋‹ค์‹œ ๋ Œ๋”๋ง์ด ์˜ˆ์•ฝ๋˜๊ณ , ๋ Œ๋”๋ง ์ค‘์— ์˜ค๋ฅ˜๊ฐ€ ๋‹ค์‹œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. + return false; + } + } + } + } + } + const child = node.child; + if (node.subtreeFlags & StoreConsistency && child !== null) { + child.return = node; + node = child; + continue; + } + if (node === finishedWork) { + return true; + } + while (node.sibling === null) { + if (node.return === null || node.return === finishedWork) { + return true; + } + node = node.return; + } + node.sibling.return = node.return; + node = node.sibling; + } + // Flow๋Š” ์ด๊ฒƒ์ด ๋„๋‹ฌํ•  ์ˆ˜ ์—†๋Š” ์ฝ”๋“œ๋ผ๋Š” ๊ฒƒ์„ ์•Œ์ง€ ๋ชปํ•˜์ง€๋งŒ, eslint๋Š” ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + // eslint-disable-next-line no-unreachable + return true; +} + +``` + +### 3. ์š”์•ฝ + +์ด์ œ `useSyncExternalStore()`๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์ดํ•ดํ–ˆ์œผ๋ฏ€๋กœ, ์ฃผ๋กœ ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +1. **๋™์‹œ ๋ชจ๋“œ์—์„œ์˜ ์ฐข์–ด์ง(Tearing):** `useSyncExternalStore()`๋Š” ๋ Œ๋”๋ง์ด ์™„๋ฃŒ๋œ ํ›„ ์ปค๋ฐ‹์ด ์‹œ์ž‘๋˜๊ธฐ ์ „์— ์ผ๊ด€์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์˜ˆ์•ฝํ•˜์—ฌ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ๋™๊ธฐ ๋ชจ๋“œ์—์„œ ๋‹ค์‹œ ๋ Œ๋”๋ง์ด ๊ฐ•์ œ๋˜์–ด ๋ถˆ์ผ์น˜ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ UI์— ํ‘œ์‹œ๋˜์ง€ ์•Š๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. + +2. **๊ฐ์ง€๋˜์ง€ ์•Š์€ ์™ธ๋ถ€ ์Šคํ† ์–ด ๋ณ€๊ฒฝ:** `useSyncExternalStore()`๋Š” ์ˆ˜๋™ ํšจ๊ณผ๋ฅผ ํ†ตํ•ด ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์žˆ์œผ๋ฉด ๋™๊ธฐ ๋ชจ๋“œ์—์„œ ๋‹ค์‹œ ๋ Œ๋”๋ง์„ ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ปค๋ฐ‹ ํ›„์— ๋ฐœ์ƒํ•˜๋ฏ€๋กœ ์‚ฌ์šฉ์ž๋Š” UI ๊นœ๋นก์ž„์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.