function ProfilePage(){
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetchUser().then(u => setUser(u)).finally(setLoading(false));
},[])
if (loading){
return <p>Loading profile</p>
}
return (
<div>
<h1>{user.name}</h1>
<ProfileTimeline /> // post의 값을 fetching 해야함
</div>
)
}
function ProfilePage(){
const [user, setUser] = useState(null);
const [posts, setPosts] = useState(null)
useEffect(() => {
Promise.all([fetchUser(), fetchPosts()])
.then([user, posts] => {
setUser(user)
setPosts(posts)
})
},[])
if (user === null){
return <p>Loading profile</p>
}
return (
<div>
<h1>{user.name}</h1>
<ProfileTimeline posts={posts} />
</div>
)
}
function ProfilePage(){
const user = useGetUser();
return (
<div>
<h1>{user.name}</h1>
<ErrorBoundary fallback={<ErrorPage />}>
<Suspense fallback={<Skeleton />}>
<ProfileTimeline posts={posts} />
</Suspense>
</ErrorBoundary>
</div>
)
}
function Page(){
return (
<ErrorBoundary fallback={<ErrorPage />}>
<Suspense fallback={<Skeleton />}>
<ProfilePage />
</Suspense>
</ErrorBoundary>
)
}
function App(){
return (
<Suspense fallback={<fallbackUI />}>
<ConponentThatThrowPromise />
</Suspense>
)
}
function App(){
return (
<ErrorBoundary fallback={<ErrorPage />}>
<Suspense fallback={<fallbackUI />}>
<ConponentThatThrowPromiseOrError />
</Suspense>
</ErrorBoundary>
)
}
대수적 효과는 순수하지 않은 행동이 일련의 활동에서 발생된다는 전제 하에 computational effects에 대해 접근하는 방식이다. 물론 순수하지 않은 행동들을 적절하게 처리할 수 있는 handler들을 자연스럽게 주어진 상태로 말이다. - Matija Pretnar
// infrastructure.js
let cache = new Map();
let pending = new Map();
function fetchTextSync(url) {
if (cache.has(url)) {
return cache.get(url);
}
if (pending.has(url)) {
return pending.get(url);
}
let promise = fetch(url).then(response => response.text()).then(text => {
pending.delete(url);
cache.set(url, text)
})
pending.set(url, promise);
throw promise;
}
async function runPureTask(task){
for(;;){
try{
return task();
} catch (x){
if(x instanceof Promise){
await x;
} else {
throw x;
}
}
}
}
1. performConcurrentWarkOnRoot
2. renderRootConcurrent
3. workLoopConcurrent
4. performUnitOfWokr
5. beginWork
6. case: xxComponent
function renderRootConcurrent(root: FiberRoot, lanes: Lanes){
// ...
outer: do {
try {
if(__DEV__ && ReactCurrentActQueue.current !== null) {
workLoopSync();
}else {
workLoopConcurrent(); // throw Promise!
}
}catch (thrownValue) {
handleThrow(root, thrownValue); // catch!
}
}
}
function handleThrow(root: FiberRoot, thrownValue: any): void {
// ...
if(thrownValue === SuspeseException) {
thrownValue = getSuspendedThenable();
workInProgressSuspendedReason =
shouldRemainOnPreviousScreen() &&
!isincludesNonIdleWork(workInProgressRootSkippedLanes) &&
!isincludesNonIdleWork(workInProgressRootInterleavedUpdateLanes)
? SuspendedOnData: SuspendedOnImmediate;
}
}
beginWork에서 Suspense Component를 만나게된다면 updateSuspenseComponent라는 메서드가 실행됩니다.

updateSuspenseComponent의 작동 과정을 그림으로 표현하면 다음과 같습니다.
showFallback에서 맨 처음 렌더링할 때와 업데이트되었을 때 두 가지 경우가 다 있는데 다음 이미지는 맨 처음 렌더링되었을 때의 과정입니다.
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logErrorToMyService(error, errorInfo.componentStack);
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
function renderRootConcurrent(root: FiberRoot, lanes: Lanes){
// ...
outer: do {
try {
if(__DEV__ && ReactCurrentActQueue.current !== null) {
workLoopSync();
}else {
workLoopConcurrent(); // throw Promise!
}
}catch (thrownValue) {
handleThrow(root, thrownValue); // catch!
}
}
}
function handleThrow(root: FiberRoot, thrownValue: any): void {
// ...
if(thrownValue === SuspeseException) {
// ...
} else {
const isWakeable =
thrownValue !== null &&
typeof thrownValue === 'object' &&
typeof thrownValue.then === 'function';
workInProgressSuspendedReason = isWakeable
? SuspendedOnDeprecatedThrowPromise
: SuspendedOnError;
}
}
function renderRootConcurrent(root: FiberRoot, lanes: Lanes){
// ...
case SuspendedOnError: {
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(unitOfWork, thrownValue);
break;
}
}
function throwAndUnwindWorkLoop(unitOfWork: Fiber, thrownValue: mixed){
try{
throwException(
workInProgressRoot,
returnFiber,
untilOfWork,
thrownValue,
workInProgressRootRenderLanes,
)
} catch (error) {
workInProgress = returnFiber
throw error;
}
}
이번 글에서는 Suspense와 ErrorBoundary에 대해서 다뤘는데요. 기존에 알고 있던 것들이라 가벼운 마음으로 영상을 시청했었는데요.. 하하하 이렇게 깊게 들어갈줄은 몰랐습니다. 내부 동작을 코드 하나하나 따라가면서 설명해주셔서 이해는 됐으나 복잡하네요. 이 부분은 나중에 다시 한 번 볼 필요가 있을 거 같습니다. 영상 내용 너무 유익하니까 여러분도 꼭 시청하시면 좋겠습니다.