Skip to content

Repository files navigation

my-react

Let’s build a React from scratch

Part 1 — VirtualDOM and Renderer

  • 개정: typescript 가 아닌 tsc 사용 해야함
-npx typescript --init+npx tsc --init
  • 하나의 스크립트로 묶기
"dev": "tsc -w & npx serve ."

Our First JSX

  • React 를 만들어줌
  • React.createElement 를 추가 해줌
  • HTML tag 를 작성하면 가장 하위부터 상위로 태그 개수만큼 createElement 를 호출함
constApp=(<divdraggable><h2>Hello React!</h2><p>I am a pargraph</p><inputtype="text"/></div>);
['h2',null,'Hello React!']['p',null,'I am a pargraph']['input',{}]['div',{},undefined,undefined,undefined]

Introducing Virtual DOM

  • tree 의 복제본을 저장하기 위해 tag, props, children 을 분리해서 받음
  • tag 가 fuction 인 경우 직접 실행해서 el 반환

Let’s Render our VirtualDOM (renderer)

  • <div id="myapp"></div> 을 virtual DOM 의 root node 로 잡고 실제 DOM 을 렌더링한다
  1. 해당 type의 실제 DOM node 생성
  2. props 복제
  3. children 존재하는 경우 1~2 를 반복하여 현재 DOM 하위로 append
  4. container.appendChild(domEl) 으로 브라우저 container 에 추가
  5. Text node 의 경우 별도 처리 필요

Lessons Learned

  • 리액트 처음 배울때는 초고수들만 직접 만들기 하는줄 알아서 겁먹었는데 생각 보다 간단하다.
  • 사실 JSX 가 너무 많은걸 해주는 것 같다. JSX 직접 만들기도 도전?
  • virtual DOM 의 한계에 대한 글들이 요즘 많이 보이는데, signal 기반도 직접 만들기 해보면 좋을 것 같다

Part 2 — State Management and React Hooks

🤹 Introduction to React Hooks (useState)🎪

  • 초기값을 받아 state 와 setter 를 return
constuseState=(initialState)=>{console.log("useState is initialized with value:",initialState);letstate=initialState;constsetState=(newState)=>{console.log("setState is called with newState value:",newState);state=newState;};return[state,setState];};

🌗 Introducing Re-render for our App 🌝

  • 모든 것을 다시 다 reRender 하기
    • onchange 마다 reRender 가 호출되고, useState 가 호출되면서 state 가 initialValue 로 reset 된다
    • redering 중에 새 값을 잃는다
    • 현재 render 함수는 append 만 수행

reRender 시에 rootNode.innerHTML 초기화

// ---- Library --- //constreRender=()=>{console.log("reRender-ing :)");constrootNode=document.getElementById("myapp");// reset/clean whatever is rendered alreadyrootNode.innerHTML="";// then render Freshrender(<App/>,rootNode);};

🍔 State-fulness and Global State Management 🍟

  • state 를 useState 밖에 두고 변경되었는지 확인하자
// ---- Library --- //letmyAppState;constuseState=(initialState)=>{// Check before setting AppState to initialState (reRender)myAppState=myAppState||initialState;console.log("useState is initialized with value:",myAppState);constsetState=(newState)=>{console.log("setState is called with newState value:",newState);myAppState=newState;// Render the UI fresh given state has changed.reRender();};return[myAppState,setState];};

👨🏻‍🎤 Managing Multiple States with useState() 😵‍💫

  • library 의 저자로서 얼마나 많은 state 가 어디에 필요할 지 모른다

  • 서로 다른 state 간에 overwrite 되어서는 안된다

  • cursor 로 관리하는 globalArray 생성

// ---- Library --- //
+const myAppState = [];+let myAppStateCursor = 0;
const useState = (initialState) => {
// get the cursor for this useState
+ const stateCursor = myAppStateCursor;
// Check before setting AppState to initialState (reRender)
+ myAppState[stateCursor] = myAppState[stateCursor] || initialState;
console.log(
+ `useState is initialized at cursor ${stateCursor} with value:`,
myAppState,
);
const setState = (newState) => {
console.log(
+ `setState is called at cursor ${stateCursor} with newState value:`,
newState,
);
+ myAppState[stateCursor] = newState;
// Render the UI fresh given state has changed.
reRender();
};
+ // prepare the cursor for the next state.+ myAppStateCursor++;+ console.log(`stateDump`, myAppState);+ return [myAppState[stateCursor], setState];
};
  • reRender 시에 myAppStateCursor 를 초기화
// ---- Library --- //
const reRender = () => {
// ..
rootNode.innerHTML = '';
+ // Reset the global state cursor+ myAppStateCursor = 0;
// then render Fresh
render(<App />, rootNode);
};

✌🏽 Why the rules of React 🤞🏽

Only Call Hooks at the Top Level

  • state 관리를 위한 global array 존재
  • 조건절이나 반복문 안에서 조건적으로 useState 와 같은 hook 이 호출된다면 cursor 추적하기가 어렵다

Lessons Learned

  • state 구현이 생각보다 쉬워서 놀랐다.
  • diffing 처리는 별도로 안하는건지 여기서만 단순화 시킨 건지 궁금하다.
  • multi state 처리할 때 꼭 cursor 를 별도로 둬야하나? map에대한 key 로 처리할 수는 없는지? 궁금하다

Question

  • 왜 input 의 onchange 를 정의 했는데도 우리가 흔히 하는 onChange 처럼 동작하지 않고 input 밖으로 포커스를 옮겨야만 반영되는것일까?

Part 3 — React Suspense and Concurrent Mode

  • React Suspense and Concurrent Mode

🦁 React Rendering Techniques 🐒

Approach 1: Fetch-on-Render (not using Suspense)

  • traditional way: fetching after the initial render
  • state 와 같은 방식으로 채움
functionProfilePage(){const[user,setUser]=useState(null);useEffect(()=>{fetchUser().then((u)=>setUser(u));},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimeline/></>);}
  • waterfall 단점: 의존된 데이터가 fetch 될 때마다 re-render 된다

Approach 2: Fetch-Then-Render (not using Suspense)

  • 컴포넌트에 대한 정보를 전용 function call 로 분리한다
  • render 를 trigger 하기 위해서 setState 는 여전히 사용
// Wrapping all data fetchingfunctionfetchProfileData(){returnPromise.all([fetchUser(),fetchPosts()]).then(([user,posts])=>{return{ user, posts };});}// Using it in our ComponentfunctionProfilePage(){const[user,setUser]=useState(null);const[posts,setPosts]=useState(null);useEffect(()=>{promise.then((data)=>{setUser(data.user);setPosts(data.posts);});},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimelineposts={posts}/></>);}
  1. Start fetching
  2. Finish fetching
  3. Start rendering
  • fetchProfileData 를 사용을 안하는데?

Approach 3: Render-as-You-Fetch (using Suspense)

  1. Start fetching
  2. Start rendering
  3. Finish fetching
  • fetching 최적화 고려할 필요가 없다
    • fetching 완료되면 한번만 렌더링 하면 된다
  • image, 다른페이지, 문서 등을 non-blocking 으로 가져올 수 있다

🦖 How does React Suspense Work? 🦇

  • : React render cycle 에서 async call 을 처리하기 위한 메커니즘
  • React 의 rendering 은 원래는 synchrounous
  • renderer 는 VirtualDOM 에만 적용
  • DOM 의 어떤 부분에 signal 을 주고 기다려야하는지 구분 필요
  • 모든 promise 들을 추적해서 작업이 끝나면 자동으로 rendering 수행

🐙 What is Concurrent Mode 🦑

  • 항상 부모-자식 구모가 아닌 경우를 처리하기 위해 try-catch block 의 컨셉을 차용하여 아직 로딩중인 VirtualDOM tree 정보를 전송

Concurrent React 는 중단 가능한 rendering 이다

🦈 Our own little remote API 🐋

  • simulate slow image fetching
  • 현재는 promise 처리를 못하기때문에 아래 코드가 에러 나는게 맞다
// ---- Remote API ---- //constphotoURL='https://picsum.photos/200';constgetMyAwesomePic=()=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>resolve(photoURL),1500);});};//..constApp=()=>{//..constphoto=getMyAwesomePic();return(<h2>Our Photo Album</h2><imgsrc={photo}alt="Photo"/>// ..

🌳 Suspense and Caching mechanisms with 🌴 createResource

  • createResource() 가 async call 을 추적
// ---- Library ---- //constresourceCache={};constcreateResource=(asyncTask,key)=>{// First check if the key is present in the cache.// if so simply return the cached value.if(resourceCache[key])returnresourceCache[key];// If not then we need handle the promise here// ....};
  • resourceCache 에 async task 보관
  • key 에 해당하는 resource 가 존재하면 해당 task 가 끝났다는 의미
  • key 없으면? 아직 resolved 되지 않았으므로 렌더링 하면 안됨

💫 Branching our VirtualDOM creation 🐲

// ---- Library ---- //
const resourceCache = {};
const createResource = (asyncTask, key) => {
// First check if the key is present in the cache.
// if so simply return the cached value.
if (resourceCache[key]) return resourceCache[key];
// If not
+ throw { promise: asyncTask(), key };
};
  • key 없으면 바로 throw 해버린다 -> virtual DOM tree 생성 중단
// ---- Application --- //constApp=()=>{//..constphoto=createResource(getMyAwesomePic,'photo');return(//..
  • 현재는 Uncaught error 나는게 정상
// ---- Library --- //constReact={createElement: (tag,props, ...children)=>{if(typeoftag==="function"){try{returntag(props, ...children);}catch({ promise, key }){console.log(promise);console.log(key);}}//..},};
  • catch 는 되었지만 여전히 Promise 어떻게 처리할 지 모름
  • h2 로 간단한 fallback UI 를 만들어보자
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// We branch off the VirtualDOM here// now this will be immediately be rendered.return{tag: 'h2',props: null,children: ['loading your image']};

☃️ Suspense in Action 💨

  • promise 가 throw 된 경우 resourceCache 에 담아 추적
  • 다음 번 loop 에서 resolved 되었다면 rerender 시에 반영됨
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// Handle when this promise is resolved/rejected.promise.then((value)=>{resourceCache[key]=value;reRender();});//..

🥝 Conclusion 🥥

  • 과제: 현재 구현으로는 resource 가 2 개 이상일 때 모든 resource 가 resolved 되어야 rendering 이 될 텐데 어떻게 병렬 처리를 할 것인가?

Lessons Learned

  • Concurrent React 는 중단 가능한 rendering 이다
    • try/catch 를 이런식으로 활용 할 줄은 몰랐는데.. 이건 업무로직에서도 활용 해볼 수 있을 것 같다
  • Suspense 는 async call 을 처리하기 위한 메커니즘이다
    • 그냥 성능을 위한 부가 기능으로만 생각했던 Suspense 의 동작 방식을 살펴볼 수 있어 좋았다
  • 단순한 구조의 resourceCache 만으로 Promise 를 추적할 수 있는게 흥미로웠지만,
    • 실제로는 더 복잡한 구조가 필요할 것 같다. eg) resource 가 실패했을 때 재시도 하는 로직

Question

  • fetchProfileData 예제는 오타인거 겠지?
  • Cache 관련 고민해보아야 할 것들
    • Cache invalidation은 어떻게 처리하나요?
    • Memory leak 방지를 위한 cleanup 전략은?
  • Concurrent Mode가 Fiber 아키텍처와 어떤 관련이 있나요?
  • SWR 은 데이터의 관점에서, Suspense 는 렌더링 관점에서 비동기 처리를 한거거라고 보면 될까?

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - study-react-from-scratch-hoco/henry · GitHub
Skip to content

Repository files navigation

my-react

Let’s build a React from scratch

Part 1 — VirtualDOM and Renderer

  • 개정: typescript 가 아닌 tsc 사용 해야함
-npx typescript --init+npx tsc --init
  • 하나의 스크립트로 묶기
"dev": "tsc -w & npx serve ."

Our First JSX

  • React 를 만들어줌
  • React.createElement 를 추가 해줌
  • HTML tag 를 작성하면 가장 하위부터 상위로 태그 개수만큼 createElement 를 호출함
constApp=(<divdraggable><h2>Hello React!</h2><p>I am a pargraph</p><inputtype="text"/></div>);
['h2',null,'Hello React!']['p',null,'I am a pargraph']['input',{}]['div',{},undefined,undefined,undefined]

Introducing Virtual DOM

  • tree 의 복제본을 저장하기 위해 tag, props, children 을 분리해서 받음
  • tag 가 fuction 인 경우 직접 실행해서 el 반환

Let’s Render our VirtualDOM (renderer)

  • <div id="myapp"></div> 을 virtual DOM 의 root node 로 잡고 실제 DOM 을 렌더링한다
  1. 해당 type의 실제 DOM node 생성
  2. props 복제
  3. children 존재하는 경우 1~2 를 반복하여 현재 DOM 하위로 append
  4. container.appendChild(domEl) 으로 브라우저 container 에 추가
  5. Text node 의 경우 별도 처리 필요

Lessons Learned

  • 리액트 처음 배울때는 초고수들만 직접 만들기 하는줄 알아서 겁먹었는데 생각 보다 간단하다.
  • 사실 JSX 가 너무 많은걸 해주는 것 같다. JSX 직접 만들기도 도전?
  • virtual DOM 의 한계에 대한 글들이 요즘 많이 보이는데, signal 기반도 직접 만들기 해보면 좋을 것 같다

Part 2 — State Management and React Hooks

🤹 Introduction to React Hooks (useState)🎪

  • 초기값을 받아 state 와 setter 를 return
constuseState=(initialState)=>{console.log("useState is initialized with value:",initialState);letstate=initialState;constsetState=(newState)=>{console.log("setState is called with newState value:",newState);state=newState;};return[state,setState];};

🌗 Introducing Re-render for our App 🌝

  • 모든 것을 다시 다 reRender 하기
    • onchange 마다 reRender 가 호출되고, useState 가 호출되면서 state 가 initialValue 로 reset 된다
    • redering 중에 새 값을 잃는다
    • 현재 render 함수는 append 만 수행

reRender 시에 rootNode.innerHTML 초기화

// ---- Library --- //constreRender=()=>{console.log("reRender-ing :)");constrootNode=document.getElementById("myapp");// reset/clean whatever is rendered alreadyrootNode.innerHTML="";// then render Freshrender(<App/>,rootNode);};

🍔 State-fulness and Global State Management 🍟

  • state 를 useState 밖에 두고 변경되었는지 확인하자
// ---- Library --- //letmyAppState;constuseState=(initialState)=>{// Check before setting AppState to initialState (reRender)myAppState=myAppState||initialState;console.log("useState is initialized with value:",myAppState);constsetState=(newState)=>{console.log("setState is called with newState value:",newState);myAppState=newState;// Render the UI fresh given state has changed.reRender();};return[myAppState,setState];};

👨🏻‍🎤 Managing Multiple States with useState() 😵‍💫

  • library 의 저자로서 얼마나 많은 state 가 어디에 필요할 지 모른다

  • 서로 다른 state 간에 overwrite 되어서는 안된다

  • cursor 로 관리하는 globalArray 생성

// ---- Library --- //
+const myAppState = [];+let myAppStateCursor = 0;
const useState = (initialState) => {
// get the cursor for this useState
+ const stateCursor = myAppStateCursor;
// Check before setting AppState to initialState (reRender)
+ myAppState[stateCursor] = myAppState[stateCursor] || initialState;
console.log(
+ `useState is initialized at cursor ${stateCursor} with value:`,
myAppState,
);
const setState = (newState) => {
console.log(
+ `setState is called at cursor ${stateCursor} with newState value:`,
newState,
);
+ myAppState[stateCursor] = newState;
// Render the UI fresh given state has changed.
reRender();
};
+ // prepare the cursor for the next state.+ myAppStateCursor++;+ console.log(`stateDump`, myAppState);+ return [myAppState[stateCursor], setState];
};
  • reRender 시에 myAppStateCursor 를 초기화
// ---- Library --- //
const reRender = () => {
// ..
rootNode.innerHTML = '';
+ // Reset the global state cursor+ myAppStateCursor = 0;
// then render Fresh
render(<App />, rootNode);
};

✌🏽 Why the rules of React 🤞🏽

Only Call Hooks at the Top Level

  • state 관리를 위한 global array 존재
  • 조건절이나 반복문 안에서 조건적으로 useState 와 같은 hook 이 호출된다면 cursor 추적하기가 어렵다

Lessons Learned

  • state 구현이 생각보다 쉬워서 놀랐다.
  • diffing 처리는 별도로 안하는건지 여기서만 단순화 시킨 건지 궁금하다.
  • multi state 처리할 때 꼭 cursor 를 별도로 둬야하나? map에대한 key 로 처리할 수는 없는지? 궁금하다

Question

  • 왜 input 의 onchange 를 정의 했는데도 우리가 흔히 하는 onChange 처럼 동작하지 않고 input 밖으로 포커스를 옮겨야만 반영되는것일까?

Part 3 — React Suspense and Concurrent Mode

  • React Suspense and Concurrent Mode

🦁 React Rendering Techniques 🐒

Approach 1: Fetch-on-Render (not using Suspense)

  • traditional way: fetching after the initial render
  • state 와 같은 방식으로 채움
functionProfilePage(){const[user,setUser]=useState(null);useEffect(()=>{fetchUser().then((u)=>setUser(u));},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimeline/></>);}
  • waterfall 단점: 의존된 데이터가 fetch 될 때마다 re-render 된다

Approach 2: Fetch-Then-Render (not using Suspense)

  • 컴포넌트에 대한 정보를 전용 function call 로 분리한다
  • render 를 trigger 하기 위해서 setState 는 여전히 사용
// Wrapping all data fetchingfunctionfetchProfileData(){returnPromise.all([fetchUser(),fetchPosts()]).then(([user,posts])=>{return{ user, posts };});}// Using it in our ComponentfunctionProfilePage(){const[user,setUser]=useState(null);const[posts,setPosts]=useState(null);useEffect(()=>{promise.then((data)=>{setUser(data.user);setPosts(data.posts);});},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimelineposts={posts}/></>);}
  1. Start fetching
  2. Finish fetching
  3. Start rendering
  • fetchProfileData 를 사용을 안하는데?

Approach 3: Render-as-You-Fetch (using Suspense)

  1. Start fetching
  2. Start rendering
  3. Finish fetching
  • fetching 최적화 고려할 필요가 없다
    • fetching 완료되면 한번만 렌더링 하면 된다
  • image, 다른페이지, 문서 등을 non-blocking 으로 가져올 수 있다

🦖 How does React Suspense Work? 🦇

  • : React render cycle 에서 async call 을 처리하기 위한 메커니즘
  • React 의 rendering 은 원래는 synchrounous
  • renderer 는 VirtualDOM 에만 적용
  • DOM 의 어떤 부분에 signal 을 주고 기다려야하는지 구분 필요
  • 모든 promise 들을 추적해서 작업이 끝나면 자동으로 rendering 수행

🐙 What is Concurrent Mode 🦑

  • 항상 부모-자식 구모가 아닌 경우를 처리하기 위해 try-catch block 의 컨셉을 차용하여 아직 로딩중인 VirtualDOM tree 정보를 전송

Concurrent React 는 중단 가능한 rendering 이다

🦈 Our own little remote API 🐋

  • simulate slow image fetching
  • 현재는 promise 처리를 못하기때문에 아래 코드가 에러 나는게 맞다
// ---- Remote API ---- //constphotoURL='https://picsum.photos/200';constgetMyAwesomePic=()=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>resolve(photoURL),1500);});};//..constApp=()=>{//..constphoto=getMyAwesomePic();return(<h2>Our Photo Album</h2><imgsrc={photo}alt="Photo"/>// ..

🌳 Suspense and Caching mechanisms with 🌴 createResource

  • createResource() 가 async call 을 추적
// ---- Library ---- //constresourceCache={};constcreateResource=(asyncTask,key)=>{// First check if the key is present in the cache.// if so simply return the cached value.if(resourceCache[key])returnresourceCache[key];// If not then we need handle the promise here// ....};
  • resourceCache 에 async task 보관
  • key 에 해당하는 resource 가 존재하면 해당 task 가 끝났다는 의미
  • key 없으면? 아직 resolved 되지 않았으므로 렌더링 하면 안됨

💫 Branching our VirtualDOM creation 🐲

// ---- Library ---- //
const resourceCache = {};
const createResource = (asyncTask, key) => {
// First check if the key is present in the cache.
// if so simply return the cached value.
if (resourceCache[key]) return resourceCache[key];
// If not
+ throw { promise: asyncTask(), key };
};
  • key 없으면 바로 throw 해버린다 -> virtual DOM tree 생성 중단
// ---- Application --- //constApp=()=>{//..constphoto=createResource(getMyAwesomePic,'photo');return(//..
  • 현재는 Uncaught error 나는게 정상
// ---- Library --- //constReact={createElement: (tag,props, ...children)=>{if(typeoftag==="function"){try{returntag(props, ...children);}catch({ promise, key }){console.log(promise);console.log(key);}}//..},};
  • catch 는 되었지만 여전히 Promise 어떻게 처리할 지 모름
  • h2 로 간단한 fallback UI 를 만들어보자
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// We branch off the VirtualDOM here// now this will be immediately be rendered.return{tag: 'h2',props: null,children: ['loading your image']};

☃️ Suspense in Action 💨

  • promise 가 throw 된 경우 resourceCache 에 담아 추적
  • 다음 번 loop 에서 resolved 되었다면 rerender 시에 반영됨
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// Handle when this promise is resolved/rejected.promise.then((value)=>{resourceCache[key]=value;reRender();});//..

🥝 Conclusion 🥥

  • 과제: 현재 구현으로는 resource 가 2 개 이상일 때 모든 resource 가 resolved 되어야 rendering 이 될 텐데 어떻게 병렬 처리를 할 것인가?

Lessons Learned

  • Concurrent React 는 중단 가능한 rendering 이다
    • try/catch 를 이런식으로 활용 할 줄은 몰랐는데.. 이건 업무로직에서도 활용 해볼 수 있을 것 같다
  • Suspense 는 async call 을 처리하기 위한 메커니즘이다
    • 그냥 성능을 위한 부가 기능으로만 생각했던 Suspense 의 동작 방식을 살펴볼 수 있어 좋았다
  • 단순한 구조의 resourceCache 만으로 Promise 를 추적할 수 있는게 흥미로웠지만,
    • 실제로는 더 복잡한 구조가 필요할 것 같다. eg) resource 가 실패했을 때 재시도 하는 로직

Question

  • fetchProfileData 예제는 오타인거 겠지?
  • Cache 관련 고민해보아야 할 것들
    • Cache invalidation은 어떻게 처리하나요?
    • Memory leak 방지를 위한 cleanup 전략은?
  • Concurrent Mode가 Fiber 아키텍처와 어떤 관련이 있나요?
  • SWR 은 데이터의 관점에서, Suspense 는 렌더링 관점에서 비동기 처리를 한거거라고 보면 될까?

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - study-react-from-scratch-hoco/henry · GitHub
Skip to content

Repository files navigation

my-react

Let’s build a React from scratch

Part 1 — VirtualDOM and Renderer

  • 개정: typescript 가 아닌 tsc 사용 해야함
-npx typescript --init+npx tsc --init
  • 하나의 스크립트로 묶기
"dev": "tsc -w & npx serve ."

Our First JSX

  • React 를 만들어줌
  • React.createElement 를 추가 해줌
  • HTML tag 를 작성하면 가장 하위부터 상위로 태그 개수만큼 createElement 를 호출함
constApp=(<divdraggable><h2>Hello React!</h2><p>I am a pargraph</p><inputtype="text"/></div>);
['h2',null,'Hello React!']['p',null,'I am a pargraph']['input',{}]['div',{},undefined,undefined,undefined]

Introducing Virtual DOM

  • tree 의 복제본을 저장하기 위해 tag, props, children 을 분리해서 받음
  • tag 가 fuction 인 경우 직접 실행해서 el 반환

Let’s Render our VirtualDOM (renderer)

  • <div id="myapp"></div> 을 virtual DOM 의 root node 로 잡고 실제 DOM 을 렌더링한다
  1. 해당 type의 실제 DOM node 생성
  2. props 복제
  3. children 존재하는 경우 1~2 를 반복하여 현재 DOM 하위로 append
  4. container.appendChild(domEl) 으로 브라우저 container 에 추가
  5. Text node 의 경우 별도 처리 필요

Lessons Learned

  • 리액트 처음 배울때는 초고수들만 직접 만들기 하는줄 알아서 겁먹었는데 생각 보다 간단하다.
  • 사실 JSX 가 너무 많은걸 해주는 것 같다. JSX 직접 만들기도 도전?
  • virtual DOM 의 한계에 대한 글들이 요즘 많이 보이는데, signal 기반도 직접 만들기 해보면 좋을 것 같다

Part 2 — State Management and React Hooks

🤹 Introduction to React Hooks (useState)🎪

  • 초기값을 받아 state 와 setter 를 return
constuseState=(initialState)=>{console.log("useState is initialized with value:",initialState);letstate=initialState;constsetState=(newState)=>{console.log("setState is called with newState value:",newState);state=newState;};return[state,setState];};

🌗 Introducing Re-render for our App 🌝

  • 모든 것을 다시 다 reRender 하기
    • onchange 마다 reRender 가 호출되고, useState 가 호출되면서 state 가 initialValue 로 reset 된다
    • redering 중에 새 값을 잃는다
    • 현재 render 함수는 append 만 수행

reRender 시에 rootNode.innerHTML 초기화

// ---- Library --- //constreRender=()=>{console.log("reRender-ing :)");constrootNode=document.getElementById("myapp");// reset/clean whatever is rendered alreadyrootNode.innerHTML="";// then render Freshrender(<App/>,rootNode);};

🍔 State-fulness and Global State Management 🍟

  • state 를 useState 밖에 두고 변경되었는지 확인하자
// ---- Library --- //letmyAppState;constuseState=(initialState)=>{// Check before setting AppState to initialState (reRender)myAppState=myAppState||initialState;console.log("useState is initialized with value:",myAppState);constsetState=(newState)=>{console.log("setState is called with newState value:",newState);myAppState=newState;// Render the UI fresh given state has changed.reRender();};return[myAppState,setState];};

👨🏻‍🎤 Managing Multiple States with useState() 😵‍💫

  • library 의 저자로서 얼마나 많은 state 가 어디에 필요할 지 모른다

  • 서로 다른 state 간에 overwrite 되어서는 안된다

  • cursor 로 관리하는 globalArray 생성

// ---- Library --- //
+const myAppState = [];+let myAppStateCursor = 0;
const useState = (initialState) => {
// get the cursor for this useState
+ const stateCursor = myAppStateCursor;
// Check before setting AppState to initialState (reRender)
+ myAppState[stateCursor] = myAppState[stateCursor] || initialState;
console.log(
+ `useState is initialized at cursor ${stateCursor} with value:`,
myAppState,
);
const setState = (newState) => {
console.log(
+ `setState is called at cursor ${stateCursor} with newState value:`,
newState,
);
+ myAppState[stateCursor] = newState;
// Render the UI fresh given state has changed.
reRender();
};
+ // prepare the cursor for the next state.+ myAppStateCursor++;+ console.log(`stateDump`, myAppState);+ return [myAppState[stateCursor], setState];
};
  • reRender 시에 myAppStateCursor 를 초기화
// ---- Library --- //
const reRender = () => {
// ..
rootNode.innerHTML = '';
+ // Reset the global state cursor+ myAppStateCursor = 0;
// then render Fresh
render(<App />, rootNode);
};

✌🏽 Why the rules of React 🤞🏽

Only Call Hooks at the Top Level

  • state 관리를 위한 global array 존재
  • 조건절이나 반복문 안에서 조건적으로 useState 와 같은 hook 이 호출된다면 cursor 추적하기가 어렵다

Lessons Learned

  • state 구현이 생각보다 쉬워서 놀랐다.
  • diffing 처리는 별도로 안하는건지 여기서만 단순화 시킨 건지 궁금하다.
  • multi state 처리할 때 꼭 cursor 를 별도로 둬야하나? map에대한 key 로 처리할 수는 없는지? 궁금하다

Question

  • 왜 input 의 onchange 를 정의 했는데도 우리가 흔히 하는 onChange 처럼 동작하지 않고 input 밖으로 포커스를 옮겨야만 반영되는것일까?

Part 3 — React Suspense and Concurrent Mode

  • React Suspense and Concurrent Mode

🦁 React Rendering Techniques 🐒

Approach 1: Fetch-on-Render (not using Suspense)

  • traditional way: fetching after the initial render
  • state 와 같은 방식으로 채움
functionProfilePage(){const[user,setUser]=useState(null);useEffect(()=>{fetchUser().then((u)=>setUser(u));},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimeline/></>);}
  • waterfall 단점: 의존된 데이터가 fetch 될 때마다 re-render 된다

Approach 2: Fetch-Then-Render (not using Suspense)

  • 컴포넌트에 대한 정보를 전용 function call 로 분리한다
  • render 를 trigger 하기 위해서 setState 는 여전히 사용
// Wrapping all data fetchingfunctionfetchProfileData(){returnPromise.all([fetchUser(),fetchPosts()]).then(([user,posts])=>{return{ user, posts };});}// Using it in our ComponentfunctionProfilePage(){const[user,setUser]=useState(null);const[posts,setPosts]=useState(null);useEffect(()=>{promise.then((data)=>{setUser(data.user);setPosts(data.posts);});},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimelineposts={posts}/></>);}
  1. Start fetching
  2. Finish fetching
  3. Start rendering
  • fetchProfileData 를 사용을 안하는데?

Approach 3: Render-as-You-Fetch (using Suspense)

  1. Start fetching
  2. Start rendering
  3. Finish fetching
  • fetching 최적화 고려할 필요가 없다
    • fetching 완료되면 한번만 렌더링 하면 된다
  • image, 다른페이지, 문서 등을 non-blocking 으로 가져올 수 있다

🦖 How does React Suspense Work? 🦇

  • : React render cycle 에서 async call 을 처리하기 위한 메커니즘
  • React 의 rendering 은 원래는 synchrounous
  • renderer 는 VirtualDOM 에만 적용
  • DOM 의 어떤 부분에 signal 을 주고 기다려야하는지 구분 필요
  • 모든 promise 들을 추적해서 작업이 끝나면 자동으로 rendering 수행

🐙 What is Concurrent Mode 🦑

  • 항상 부모-자식 구모가 아닌 경우를 처리하기 위해 try-catch block 의 컨셉을 차용하여 아직 로딩중인 VirtualDOM tree 정보를 전송

Concurrent React 는 중단 가능한 rendering 이다

🦈 Our own little remote API 🐋

  • simulate slow image fetching
  • 현재는 promise 처리를 못하기때문에 아래 코드가 에러 나는게 맞다
// ---- Remote API ---- //constphotoURL='https://picsum.photos/200';constgetMyAwesomePic=()=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>resolve(photoURL),1500);});};//..constApp=()=>{//..constphoto=getMyAwesomePic();return(<h2>Our Photo Album</h2><imgsrc={photo}alt="Photo"/>// ..

🌳 Suspense and Caching mechanisms with 🌴 createResource

  • createResource() 가 async call 을 추적
// ---- Library ---- //constresourceCache={};constcreateResource=(asyncTask,key)=>{// First check if the key is present in the cache.// if so simply return the cached value.if(resourceCache[key])returnresourceCache[key];// If not then we need handle the promise here// ....};
  • resourceCache 에 async task 보관
  • key 에 해당하는 resource 가 존재하면 해당 task 가 끝났다는 의미
  • key 없으면? 아직 resolved 되지 않았으므로 렌더링 하면 안됨

💫 Branching our VirtualDOM creation 🐲

// ---- Library ---- //
const resourceCache = {};
const createResource = (asyncTask, key) => {
// First check if the key is present in the cache.
// if so simply return the cached value.
if (resourceCache[key]) return resourceCache[key];
// If not
+ throw { promise: asyncTask(), key };
};
  • key 없으면 바로 throw 해버린다 -> virtual DOM tree 생성 중단
// ---- Application --- //constApp=()=>{//..constphoto=createResource(getMyAwesomePic,'photo');return(//..
  • 현재는 Uncaught error 나는게 정상
// ---- Library --- //constReact={createElement: (tag,props, ...children)=>{if(typeoftag==="function"){try{returntag(props, ...children);}catch({ promise, key }){console.log(promise);console.log(key);}}//..},};
  • catch 는 되었지만 여전히 Promise 어떻게 처리할 지 모름
  • h2 로 간단한 fallback UI 를 만들어보자
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// We branch off the VirtualDOM here// now this will be immediately be rendered.return{tag: 'h2',props: null,children: ['loading your image']};

☃️ Suspense in Action 💨

  • promise 가 throw 된 경우 resourceCache 에 담아 추적
  • 다음 번 loop 에서 resolved 되었다면 rerender 시에 반영됨
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// Handle when this promise is resolved/rejected.promise.then((value)=>{resourceCache[key]=value;reRender();});//..

🥝 Conclusion 🥥

  • 과제: 현재 구현으로는 resource 가 2 개 이상일 때 모든 resource 가 resolved 되어야 rendering 이 될 텐데 어떻게 병렬 처리를 할 것인가?

Lessons Learned

  • Concurrent React 는 중단 가능한 rendering 이다
    • try/catch 를 이런식으로 활용 할 줄은 몰랐는데.. 이건 업무로직에서도 활용 해볼 수 있을 것 같다
  • Suspense 는 async call 을 처리하기 위한 메커니즘이다
    • 그냥 성능을 위한 부가 기능으로만 생각했던 Suspense 의 동작 방식을 살펴볼 수 있어 좋았다
  • 단순한 구조의 resourceCache 만으로 Promise 를 추적할 수 있는게 흥미로웠지만,
    • 실제로는 더 복잡한 구조가 필요할 것 같다. eg) resource 가 실패했을 때 재시도 하는 로직

Question

  • fetchProfileData 예제는 오타인거 겠지?
  • Cache 관련 고민해보아야 할 것들
    • Cache invalidation은 어떻게 처리하나요?
    • Memory leak 방지를 위한 cleanup 전략은?
  • Concurrent Mode가 Fiber 아키텍처와 어떤 관련이 있나요?
  • SWR 은 데이터의 관점에서, Suspense 는 렌더링 관점에서 비동기 처리를 한거거라고 보면 될까?

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - study-react-from-scratch-hoco/henry · GitHub
Skip to content

Repository files navigation

my-react

Let’s build a React from scratch

Part 1 — VirtualDOM and Renderer

  • 개정: typescript 가 아닌 tsc 사용 해야함
-npx typescript --init+npx tsc --init
  • 하나의 스크립트로 묶기
"dev": "tsc -w & npx serve ."

Our First JSX

  • React 를 만들어줌
  • React.createElement 를 추가 해줌
  • HTML tag 를 작성하면 가장 하위부터 상위로 태그 개수만큼 createElement 를 호출함
constApp=(<divdraggable><h2>Hello React!</h2><p>I am a pargraph</p><inputtype="text"/></div>);
['h2',null,'Hello React!']['p',null,'I am a pargraph']['input',{}]['div',{},undefined,undefined,undefined]

Introducing Virtual DOM

  • tree 의 복제본을 저장하기 위해 tag, props, children 을 분리해서 받음
  • tag 가 fuction 인 경우 직접 실행해서 el 반환

Let’s Render our VirtualDOM (renderer)

  • <div id="myapp"></div> 을 virtual DOM 의 root node 로 잡고 실제 DOM 을 렌더링한다
  1. 해당 type의 실제 DOM node 생성
  2. props 복제
  3. children 존재하는 경우 1~2 를 반복하여 현재 DOM 하위로 append
  4. container.appendChild(domEl) 으로 브라우저 container 에 추가
  5. Text node 의 경우 별도 처리 필요

Lessons Learned

  • 리액트 처음 배울때는 초고수들만 직접 만들기 하는줄 알아서 겁먹었는데 생각 보다 간단하다.
  • 사실 JSX 가 너무 많은걸 해주는 것 같다. JSX 직접 만들기도 도전?
  • virtual DOM 의 한계에 대한 글들이 요즘 많이 보이는데, signal 기반도 직접 만들기 해보면 좋을 것 같다

Part 2 — State Management and React Hooks

🤹 Introduction to React Hooks (useState)🎪

  • 초기값을 받아 state 와 setter 를 return
constuseState=(initialState)=>{console.log("useState is initialized with value:",initialState);letstate=initialState;constsetState=(newState)=>{console.log("setState is called with newState value:",newState);state=newState;};return[state,setState];};

🌗 Introducing Re-render for our App 🌝

  • 모든 것을 다시 다 reRender 하기
    • onchange 마다 reRender 가 호출되고, useState 가 호출되면서 state 가 initialValue 로 reset 된다
    • redering 중에 새 값을 잃는다
    • 현재 render 함수는 append 만 수행

reRender 시에 rootNode.innerHTML 초기화

// ---- Library --- //constreRender=()=>{console.log("reRender-ing :)");constrootNode=document.getElementById("myapp");// reset/clean whatever is rendered alreadyrootNode.innerHTML="";// then render Freshrender(<App/>,rootNode);};

🍔 State-fulness and Global State Management 🍟

  • state 를 useState 밖에 두고 변경되었는지 확인하자
// ---- Library --- //letmyAppState;constuseState=(initialState)=>{// Check before setting AppState to initialState (reRender)myAppState=myAppState||initialState;console.log("useState is initialized with value:",myAppState);constsetState=(newState)=>{console.log("setState is called with newState value:",newState);myAppState=newState;// Render the UI fresh given state has changed.reRender();};return[myAppState,setState];};

👨🏻‍🎤 Managing Multiple States with useState() 😵‍💫

  • library 의 저자로서 얼마나 많은 state 가 어디에 필요할 지 모른다

  • 서로 다른 state 간에 overwrite 되어서는 안된다

  • cursor 로 관리하는 globalArray 생성

// ---- Library --- //
+const myAppState = [];+let myAppStateCursor = 0;
const useState = (initialState) => {
// get the cursor for this useState
+ const stateCursor = myAppStateCursor;
// Check before setting AppState to initialState (reRender)
+ myAppState[stateCursor] = myAppState[stateCursor] || initialState;
console.log(
+ `useState is initialized at cursor ${stateCursor} with value:`,
myAppState,
);
const setState = (newState) => {
console.log(
+ `setState is called at cursor ${stateCursor} with newState value:`,
newState,
);
+ myAppState[stateCursor] = newState;
// Render the UI fresh given state has changed.
reRender();
};
+ // prepare the cursor for the next state.+ myAppStateCursor++;+ console.log(`stateDump`, myAppState);+ return [myAppState[stateCursor], setState];
};
  • reRender 시에 myAppStateCursor 를 초기화
// ---- Library --- //
const reRender = () => {
// ..
rootNode.innerHTML = '';
+ // Reset the global state cursor+ myAppStateCursor = 0;
// then render Fresh
render(<App />, rootNode);
};

✌🏽 Why the rules of React 🤞🏽

Only Call Hooks at the Top Level

  • state 관리를 위한 global array 존재
  • 조건절이나 반복문 안에서 조건적으로 useState 와 같은 hook 이 호출된다면 cursor 추적하기가 어렵다

Lessons Learned

  • state 구현이 생각보다 쉬워서 놀랐다.
  • diffing 처리는 별도로 안하는건지 여기서만 단순화 시킨 건지 궁금하다.
  • multi state 처리할 때 꼭 cursor 를 별도로 둬야하나? map에대한 key 로 처리할 수는 없는지? 궁금하다

Question

  • 왜 input 의 onchange 를 정의 했는데도 우리가 흔히 하는 onChange 처럼 동작하지 않고 input 밖으로 포커스를 옮겨야만 반영되는것일까?

Part 3 — React Suspense and Concurrent Mode

  • React Suspense and Concurrent Mode

🦁 React Rendering Techniques 🐒

Approach 1: Fetch-on-Render (not using Suspense)

  • traditional way: fetching after the initial render
  • state 와 같은 방식으로 채움
functionProfilePage(){const[user,setUser]=useState(null);useEffect(()=>{fetchUser().then((u)=>setUser(u));},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimeline/></>);}
  • waterfall 단점: 의존된 데이터가 fetch 될 때마다 re-render 된다

Approach 2: Fetch-Then-Render (not using Suspense)

  • 컴포넌트에 대한 정보를 전용 function call 로 분리한다
  • render 를 trigger 하기 위해서 setState 는 여전히 사용
// Wrapping all data fetchingfunctionfetchProfileData(){returnPromise.all([fetchUser(),fetchPosts()]).then(([user,posts])=>{return{ user, posts };});}// Using it in our ComponentfunctionProfilePage(){const[user,setUser]=useState(null);const[posts,setPosts]=useState(null);useEffect(()=>{promise.then((data)=>{setUser(data.user);setPosts(data.posts);});},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimelineposts={posts}/></>);}
  1. Start fetching
  2. Finish fetching
  3. Start rendering
  • fetchProfileData 를 사용을 안하는데?

Approach 3: Render-as-You-Fetch (using Suspense)

  1. Start fetching
  2. Start rendering
  3. Finish fetching
  • fetching 최적화 고려할 필요가 없다
    • fetching 완료되면 한번만 렌더링 하면 된다
  • image, 다른페이지, 문서 등을 non-blocking 으로 가져올 수 있다

🦖 How does React Suspense Work? 🦇

  • : React render cycle 에서 async call 을 처리하기 위한 메커니즘
  • React 의 rendering 은 원래는 synchrounous
  • renderer 는 VirtualDOM 에만 적용
  • DOM 의 어떤 부분에 signal 을 주고 기다려야하는지 구분 필요
  • 모든 promise 들을 추적해서 작업이 끝나면 자동으로 rendering 수행

🐙 What is Concurrent Mode 🦑

  • 항상 부모-자식 구모가 아닌 경우를 처리하기 위해 try-catch block 의 컨셉을 차용하여 아직 로딩중인 VirtualDOM tree 정보를 전송

Concurrent React 는 중단 가능한 rendering 이다

🦈 Our own little remote API 🐋

  • simulate slow image fetching
  • 현재는 promise 처리를 못하기때문에 아래 코드가 에러 나는게 맞다
// ---- Remote API ---- //constphotoURL='https://picsum.photos/200';constgetMyAwesomePic=()=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>resolve(photoURL),1500);});};//..constApp=()=>{//..constphoto=getMyAwesomePic();return(<h2>Our Photo Album</h2><imgsrc={photo}alt="Photo"/>// ..

🌳 Suspense and Caching mechanisms with 🌴 createResource

  • createResource() 가 async call 을 추적
// ---- Library ---- //constresourceCache={};constcreateResource=(asyncTask,key)=>{// First check if the key is present in the cache.// if so simply return the cached value.if(resourceCache[key])returnresourceCache[key];// If not then we need handle the promise here// ....};
  • resourceCache 에 async task 보관
  • key 에 해당하는 resource 가 존재하면 해당 task 가 끝났다는 의미
  • key 없으면? 아직 resolved 되지 않았으므로 렌더링 하면 안됨

💫 Branching our VirtualDOM creation 🐲

// ---- Library ---- //
const resourceCache = {};
const createResource = (asyncTask, key) => {
// First check if the key is present in the cache.
// if so simply return the cached value.
if (resourceCache[key]) return resourceCache[key];
// If not
+ throw { promise: asyncTask(), key };
};
  • key 없으면 바로 throw 해버린다 -> virtual DOM tree 생성 중단
// ---- Application --- //constApp=()=>{//..constphoto=createResource(getMyAwesomePic,'photo');return(//..
  • 현재는 Uncaught error 나는게 정상
// ---- Library --- //constReact={createElement: (tag,props, ...children)=>{if(typeoftag==="function"){try{returntag(props, ...children);}catch({ promise, key }){console.log(promise);console.log(key);}}//..},};
  • catch 는 되었지만 여전히 Promise 어떻게 처리할 지 모름
  • h2 로 간단한 fallback UI 를 만들어보자
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// We branch off the VirtualDOM here// now this will be immediately be rendered.return{tag: 'h2',props: null,children: ['loading your image']};

☃️ Suspense in Action 💨

  • promise 가 throw 된 경우 resourceCache 에 담아 추적
  • 다음 번 loop 에서 resolved 되었다면 rerender 시에 반영됨
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// Handle when this promise is resolved/rejected.promise.then((value)=>{resourceCache[key]=value;reRender();});//..

🥝 Conclusion 🥥

  • 과제: 현재 구현으로는 resource 가 2 개 이상일 때 모든 resource 가 resolved 되어야 rendering 이 될 텐데 어떻게 병렬 처리를 할 것인가?

Lessons Learned

  • Concurrent React 는 중단 가능한 rendering 이다
    • try/catch 를 이런식으로 활용 할 줄은 몰랐는데.. 이건 업무로직에서도 활용 해볼 수 있을 것 같다
  • Suspense 는 async call 을 처리하기 위한 메커니즘이다
    • 그냥 성능을 위한 부가 기능으로만 생각했던 Suspense 의 동작 방식을 살펴볼 수 있어 좋았다
  • 단순한 구조의 resourceCache 만으로 Promise 를 추적할 수 있는게 흥미로웠지만,
    • 실제로는 더 복잡한 구조가 필요할 것 같다. eg) resource 가 실패했을 때 재시도 하는 로직

Question

  • fetchProfileData 예제는 오타인거 겠지?
  • Cache 관련 고민해보아야 할 것들
    • Cache invalidation은 어떻게 처리하나요?
    • Memory leak 방지를 위한 cleanup 전략은?
  • Concurrent Mode가 Fiber 아키텍처와 어떤 관련이 있나요?
  • SWR 은 데이터의 관점에서, Suspense 는 렌더링 관점에서 비동기 처리를 한거거라고 보면 될까?

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - study-react-from-scratch-hoco/henry · GitHub
Skip to content

Repository files navigation

my-react

Let’s build a React from scratch

Part 1 — VirtualDOM and Renderer

  • 개정: typescript 가 아닌 tsc 사용 해야함
-npx typescript --init+npx tsc --init
  • 하나의 스크립트로 묶기
"dev": "tsc -w & npx serve ."

Our First JSX

  • React 를 만들어줌
  • React.createElement 를 추가 해줌
  • HTML tag 를 작성하면 가장 하위부터 상위로 태그 개수만큼 createElement 를 호출함
constApp=(<divdraggable><h2>Hello React!</h2><p>I am a pargraph</p><inputtype="text"/></div>);
['h2',null,'Hello React!']['p',null,'I am a pargraph']['input',{}]['div',{},undefined,undefined,undefined]

Introducing Virtual DOM

  • tree 의 복제본을 저장하기 위해 tag, props, children 을 분리해서 받음
  • tag 가 fuction 인 경우 직접 실행해서 el 반환

Let’s Render our VirtualDOM (renderer)

  • <div id="myapp"></div> 을 virtual DOM 의 root node 로 잡고 실제 DOM 을 렌더링한다
  1. 해당 type의 실제 DOM node 생성
  2. props 복제
  3. children 존재하는 경우 1~2 를 반복하여 현재 DOM 하위로 append
  4. container.appendChild(domEl) 으로 브라우저 container 에 추가
  5. Text node 의 경우 별도 처리 필요

Lessons Learned

  • 리액트 처음 배울때는 초고수들만 직접 만들기 하는줄 알아서 겁먹었는데 생각 보다 간단하다.
  • 사실 JSX 가 너무 많은걸 해주는 것 같다. JSX 직접 만들기도 도전?
  • virtual DOM 의 한계에 대한 글들이 요즘 많이 보이는데, signal 기반도 직접 만들기 해보면 좋을 것 같다

Part 2 — State Management and React Hooks

🤹 Introduction to React Hooks (useState)🎪

  • 초기값을 받아 state 와 setter 를 return
constuseState=(initialState)=>{console.log("useState is initialized with value:",initialState);letstate=initialState;constsetState=(newState)=>{console.log("setState is called with newState value:",newState);state=newState;};return[state,setState];};

🌗 Introducing Re-render for our App 🌝

  • 모든 것을 다시 다 reRender 하기
    • onchange 마다 reRender 가 호출되고, useState 가 호출되면서 state 가 initialValue 로 reset 된다
    • redering 중에 새 값을 잃는다
    • 현재 render 함수는 append 만 수행

reRender 시에 rootNode.innerHTML 초기화

// ---- Library --- //constreRender=()=>{console.log("reRender-ing :)");constrootNode=document.getElementById("myapp");// reset/clean whatever is rendered alreadyrootNode.innerHTML="";// then render Freshrender(<App/>,rootNode);};

🍔 State-fulness and Global State Management 🍟

  • state 를 useState 밖에 두고 변경되었는지 확인하자
// ---- Library --- //letmyAppState;constuseState=(initialState)=>{// Check before setting AppState to initialState (reRender)myAppState=myAppState||initialState;console.log("useState is initialized with value:",myAppState);constsetState=(newState)=>{console.log("setState is called with newState value:",newState);myAppState=newState;// Render the UI fresh given state has changed.reRender();};return[myAppState,setState];};

👨🏻‍🎤 Managing Multiple States with useState() 😵‍💫

  • library 의 저자로서 얼마나 많은 state 가 어디에 필요할 지 모른다

  • 서로 다른 state 간에 overwrite 되어서는 안된다

  • cursor 로 관리하는 globalArray 생성

// ---- Library --- //
+const myAppState = [];+let myAppStateCursor = 0;
const useState = (initialState) => {
// get the cursor for this useState
+ const stateCursor = myAppStateCursor;
// Check before setting AppState to initialState (reRender)
+ myAppState[stateCursor] = myAppState[stateCursor] || initialState;
console.log(
+ `useState is initialized at cursor ${stateCursor} with value:`,
myAppState,
);
const setState = (newState) => {
console.log(
+ `setState is called at cursor ${stateCursor} with newState value:`,
newState,
);
+ myAppState[stateCursor] = newState;
// Render the UI fresh given state has changed.
reRender();
};
+ // prepare the cursor for the next state.+ myAppStateCursor++;+ console.log(`stateDump`, myAppState);+ return [myAppState[stateCursor], setState];
};
  • reRender 시에 myAppStateCursor 를 초기화
// ---- Library --- //
const reRender = () => {
// ..
rootNode.innerHTML = '';
+ // Reset the global state cursor+ myAppStateCursor = 0;
// then render Fresh
render(<App />, rootNode);
};

✌🏽 Why the rules of React 🤞🏽

Only Call Hooks at the Top Level

  • state 관리를 위한 global array 존재
  • 조건절이나 반복문 안에서 조건적으로 useState 와 같은 hook 이 호출된다면 cursor 추적하기가 어렵다

Lessons Learned

  • state 구현이 생각보다 쉬워서 놀랐다.
  • diffing 처리는 별도로 안하는건지 여기서만 단순화 시킨 건지 궁금하다.
  • multi state 처리할 때 꼭 cursor 를 별도로 둬야하나? map에대한 key 로 처리할 수는 없는지? 궁금하다

Question

  • 왜 input 의 onchange 를 정의 했는데도 우리가 흔히 하는 onChange 처럼 동작하지 않고 input 밖으로 포커스를 옮겨야만 반영되는것일까?

Part 3 — React Suspense and Concurrent Mode

  • React Suspense and Concurrent Mode

🦁 React Rendering Techniques 🐒

Approach 1: Fetch-on-Render (not using Suspense)

  • traditional way: fetching after the initial render
  • state 와 같은 방식으로 채움
functionProfilePage(){const[user,setUser]=useState(null);useEffect(()=>{fetchUser().then((u)=>setUser(u));},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimeline/></>);}
  • waterfall 단점: 의존된 데이터가 fetch 될 때마다 re-render 된다

Approach 2: Fetch-Then-Render (not using Suspense)

  • 컴포넌트에 대한 정보를 전용 function call 로 분리한다
  • render 를 trigger 하기 위해서 setState 는 여전히 사용
// Wrapping all data fetchingfunctionfetchProfileData(){returnPromise.all([fetchUser(),fetchPosts()]).then(([user,posts])=>{return{ user, posts };});}// Using it in our ComponentfunctionProfilePage(){const[user,setUser]=useState(null);const[posts,setPosts]=useState(null);useEffect(()=>{promise.then((data)=>{setUser(data.user);setPosts(data.posts);});},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimelineposts={posts}/></>);}
  1. Start fetching
  2. Finish fetching
  3. Start rendering
  • fetchProfileData 를 사용을 안하는데?

Approach 3: Render-as-You-Fetch (using Suspense)

  1. Start fetching
  2. Start rendering
  3. Finish fetching
  • fetching 최적화 고려할 필요가 없다
    • fetching 완료되면 한번만 렌더링 하면 된다
  • image, 다른페이지, 문서 등을 non-blocking 으로 가져올 수 있다

🦖 How does React Suspense Work? 🦇

  • : React render cycle 에서 async call 을 처리하기 위한 메커니즘
  • React 의 rendering 은 원래는 synchrounous
  • renderer 는 VirtualDOM 에만 적용
  • DOM 의 어떤 부분에 signal 을 주고 기다려야하는지 구분 필요
  • 모든 promise 들을 추적해서 작업이 끝나면 자동으로 rendering 수행

🐙 What is Concurrent Mode 🦑

  • 항상 부모-자식 구모가 아닌 경우를 처리하기 위해 try-catch block 의 컨셉을 차용하여 아직 로딩중인 VirtualDOM tree 정보를 전송

Concurrent React 는 중단 가능한 rendering 이다

🦈 Our own little remote API 🐋

  • simulate slow image fetching
  • 현재는 promise 처리를 못하기때문에 아래 코드가 에러 나는게 맞다
// ---- Remote API ---- //constphotoURL='https://picsum.photos/200';constgetMyAwesomePic=()=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>resolve(photoURL),1500);});};//..constApp=()=>{//..constphoto=getMyAwesomePic();return(<h2>Our Photo Album</h2><imgsrc={photo}alt="Photo"/>// ..

🌳 Suspense and Caching mechanisms with 🌴 createResource

  • createResource() 가 async call 을 추적
// ---- Library ---- //constresourceCache={};constcreateResource=(asyncTask,key)=>{// First check if the key is present in the cache.// if so simply return the cached value.if(resourceCache[key])returnresourceCache[key];// If not then we need handle the promise here// ....};
  • resourceCache 에 async task 보관
  • key 에 해당하는 resource 가 존재하면 해당 task 가 끝났다는 의미
  • key 없으면? 아직 resolved 되지 않았으므로 렌더링 하면 안됨

💫 Branching our VirtualDOM creation 🐲

// ---- Library ---- //
const resourceCache = {};
const createResource = (asyncTask, key) => {
// First check if the key is present in the cache.
// if so simply return the cached value.
if (resourceCache[key]) return resourceCache[key];
// If not
+ throw { promise: asyncTask(), key };
};
  • key 없으면 바로 throw 해버린다 -> virtual DOM tree 생성 중단
// ---- Application --- //constApp=()=>{//..constphoto=createResource(getMyAwesomePic,'photo');return(//..
  • 현재는 Uncaught error 나는게 정상
// ---- Library --- //constReact={createElement: (tag,props, ...children)=>{if(typeoftag==="function"){try{returntag(props, ...children);}catch({ promise, key }){console.log(promise);console.log(key);}}//..},};
  • catch 는 되었지만 여전히 Promise 어떻게 처리할 지 모름
  • h2 로 간단한 fallback UI 를 만들어보자
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// We branch off the VirtualDOM here// now this will be immediately be rendered.return{tag: 'h2',props: null,children: ['loading your image']};

☃️ Suspense in Action 💨

  • promise 가 throw 된 경우 resourceCache 에 담아 추적
  • 다음 번 loop 에서 resolved 되었다면 rerender 시에 반영됨
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// Handle when this promise is resolved/rejected.promise.then((value)=>{resourceCache[key]=value;reRender();});//..

🥝 Conclusion 🥥

  • 과제: 현재 구현으로는 resource 가 2 개 이상일 때 모든 resource 가 resolved 되어야 rendering 이 될 텐데 어떻게 병렬 처리를 할 것인가?

Lessons Learned

  • Concurrent React 는 중단 가능한 rendering 이다
    • try/catch 를 이런식으로 활용 할 줄은 몰랐는데.. 이건 업무로직에서도 활용 해볼 수 있을 것 같다
  • Suspense 는 async call 을 처리하기 위한 메커니즘이다
    • 그냥 성능을 위한 부가 기능으로만 생각했던 Suspense 의 동작 방식을 살펴볼 수 있어 좋았다
  • 단순한 구조의 resourceCache 만으로 Promise 를 추적할 수 있는게 흥미로웠지만,
    • 실제로는 더 복잡한 구조가 필요할 것 같다. eg) resource 가 실패했을 때 재시도 하는 로직

Question

  • fetchProfileData 예제는 오타인거 겠지?
  • Cache 관련 고민해보아야 할 것들
    • Cache invalidation은 어떻게 처리하나요?
    • Memory leak 방지를 위한 cleanup 전략은?
  • Concurrent Mode가 Fiber 아키텍처와 어떤 관련이 있나요?
  • SWR 은 데이터의 관점에서, Suspense 는 렌더링 관점에서 비동기 처리를 한거거라고 보면 될까?

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - study-react-from-scratch-hoco/henry · GitHub
Skip to content

Repository files navigation

my-react

Let’s build a React from scratch

Part 1 — VirtualDOM and Renderer

  • 개정: typescript 가 아닌 tsc 사용 해야함
-npx typescript --init+npx tsc --init
  • 하나의 스크립트로 묶기
"dev": "tsc -w & npx serve ."

Our First JSX

  • React 를 만들어줌
  • React.createElement 를 추가 해줌
  • HTML tag 를 작성하면 가장 하위부터 상위로 태그 개수만큼 createElement 를 호출함
constApp=(<divdraggable><h2>Hello React!</h2><p>I am a pargraph</p><inputtype="text"/></div>);
['h2',null,'Hello React!']['p',null,'I am a pargraph']['input',{}]['div',{},undefined,undefined,undefined]

Introducing Virtual DOM

  • tree 의 복제본을 저장하기 위해 tag, props, children 을 분리해서 받음
  • tag 가 fuction 인 경우 직접 실행해서 el 반환

Let’s Render our VirtualDOM (renderer)

  • <div id="myapp"></div> 을 virtual DOM 의 root node 로 잡고 실제 DOM 을 렌더링한다
  1. 해당 type의 실제 DOM node 생성
  2. props 복제
  3. children 존재하는 경우 1~2 를 반복하여 현재 DOM 하위로 append
  4. container.appendChild(domEl) 으로 브라우저 container 에 추가
  5. Text node 의 경우 별도 처리 필요

Lessons Learned

  • 리액트 처음 배울때는 초고수들만 직접 만들기 하는줄 알아서 겁먹었는데 생각 보다 간단하다.
  • 사실 JSX 가 너무 많은걸 해주는 것 같다. JSX 직접 만들기도 도전?
  • virtual DOM 의 한계에 대한 글들이 요즘 많이 보이는데, signal 기반도 직접 만들기 해보면 좋을 것 같다

Part 2 — State Management and React Hooks

🤹 Introduction to React Hooks (useState)🎪

  • 초기값을 받아 state 와 setter 를 return
constuseState=(initialState)=>{console.log("useState is initialized with value:",initialState);letstate=initialState;constsetState=(newState)=>{console.log("setState is called with newState value:",newState);state=newState;};return[state,setState];};

🌗 Introducing Re-render for our App 🌝

  • 모든 것을 다시 다 reRender 하기
    • onchange 마다 reRender 가 호출되고, useState 가 호출되면서 state 가 initialValue 로 reset 된다
    • redering 중에 새 값을 잃는다
    • 현재 render 함수는 append 만 수행

reRender 시에 rootNode.innerHTML 초기화

// ---- Library --- //constreRender=()=>{console.log("reRender-ing :)");constrootNode=document.getElementById("myapp");// reset/clean whatever is rendered alreadyrootNode.innerHTML="";// then render Freshrender(<App/>,rootNode);};

🍔 State-fulness and Global State Management 🍟

  • state 를 useState 밖에 두고 변경되었는지 확인하자
// ---- Library --- //letmyAppState;constuseState=(initialState)=>{// Check before setting AppState to initialState (reRender)myAppState=myAppState||initialState;console.log("useState is initialized with value:",myAppState);constsetState=(newState)=>{console.log("setState is called with newState value:",newState);myAppState=newState;// Render the UI fresh given state has changed.reRender();};return[myAppState,setState];};

👨🏻‍🎤 Managing Multiple States with useState() 😵‍💫

  • library 의 저자로서 얼마나 많은 state 가 어디에 필요할 지 모른다

  • 서로 다른 state 간에 overwrite 되어서는 안된다

  • cursor 로 관리하는 globalArray 생성

// ---- Library --- //
+const myAppState = [];+let myAppStateCursor = 0;
const useState = (initialState) => {
// get the cursor for this useState
+ const stateCursor = myAppStateCursor;
// Check before setting AppState to initialState (reRender)
+ myAppState[stateCursor] = myAppState[stateCursor] || initialState;
console.log(
+ `useState is initialized at cursor ${stateCursor} with value:`,
myAppState,
);
const setState = (newState) => {
console.log(
+ `setState is called at cursor ${stateCursor} with newState value:`,
newState,
);
+ myAppState[stateCursor] = newState;
// Render the UI fresh given state has changed.
reRender();
};
+ // prepare the cursor for the next state.+ myAppStateCursor++;+ console.log(`stateDump`, myAppState);+ return [myAppState[stateCursor], setState];
};
  • reRender 시에 myAppStateCursor 를 초기화
// ---- Library --- //
const reRender = () => {
// ..
rootNode.innerHTML = '';
+ // Reset the global state cursor+ myAppStateCursor = 0;
// then render Fresh
render(<App />, rootNode);
};

✌🏽 Why the rules of React 🤞🏽

Only Call Hooks at the Top Level

  • state 관리를 위한 global array 존재
  • 조건절이나 반복문 안에서 조건적으로 useState 와 같은 hook 이 호출된다면 cursor 추적하기가 어렵다

Lessons Learned

  • state 구현이 생각보다 쉬워서 놀랐다.
  • diffing 처리는 별도로 안하는건지 여기서만 단순화 시킨 건지 궁금하다.
  • multi state 처리할 때 꼭 cursor 를 별도로 둬야하나? map에대한 key 로 처리할 수는 없는지? 궁금하다

Question

  • 왜 input 의 onchange 를 정의 했는데도 우리가 흔히 하는 onChange 처럼 동작하지 않고 input 밖으로 포커스를 옮겨야만 반영되는것일까?

Part 3 — React Suspense and Concurrent Mode

  • React Suspense and Concurrent Mode

🦁 React Rendering Techniques 🐒

Approach 1: Fetch-on-Render (not using Suspense)

  • traditional way: fetching after the initial render
  • state 와 같은 방식으로 채움
functionProfilePage(){const[user,setUser]=useState(null);useEffect(()=>{fetchUser().then((u)=>setUser(u));},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimeline/></>);}
  • waterfall 단점: 의존된 데이터가 fetch 될 때마다 re-render 된다

Approach 2: Fetch-Then-Render (not using Suspense)

  • 컴포넌트에 대한 정보를 전용 function call 로 분리한다
  • render 를 trigger 하기 위해서 setState 는 여전히 사용
// Wrapping all data fetchingfunctionfetchProfileData(){returnPromise.all([fetchUser(),fetchPosts()]).then(([user,posts])=>{return{ user, posts };});}// Using it in our ComponentfunctionProfilePage(){const[user,setUser]=useState(null);const[posts,setPosts]=useState(null);useEffect(()=>{promise.then((data)=>{setUser(data.user);setPosts(data.posts);});},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimelineposts={posts}/></>);}
  1. Start fetching
  2. Finish fetching
  3. Start rendering
  • fetchProfileData 를 사용을 안하는데?

Approach 3: Render-as-You-Fetch (using Suspense)

  1. Start fetching
  2. Start rendering
  3. Finish fetching
  • fetching 최적화 고려할 필요가 없다
    • fetching 완료되면 한번만 렌더링 하면 된다
  • image, 다른페이지, 문서 등을 non-blocking 으로 가져올 수 있다

🦖 How does React Suspense Work? 🦇

  • : React render cycle 에서 async call 을 처리하기 위한 메커니즘
  • React 의 rendering 은 원래는 synchrounous
  • renderer 는 VirtualDOM 에만 적용
  • DOM 의 어떤 부분에 signal 을 주고 기다려야하는지 구분 필요
  • 모든 promise 들을 추적해서 작업이 끝나면 자동으로 rendering 수행

🐙 What is Concurrent Mode 🦑

  • 항상 부모-자식 구모가 아닌 경우를 처리하기 위해 try-catch block 의 컨셉을 차용하여 아직 로딩중인 VirtualDOM tree 정보를 전송

Concurrent React 는 중단 가능한 rendering 이다

🦈 Our own little remote API 🐋

  • simulate slow image fetching
  • 현재는 promise 처리를 못하기때문에 아래 코드가 에러 나는게 맞다
// ---- Remote API ---- //constphotoURL='https://picsum.photos/200';constgetMyAwesomePic=()=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>resolve(photoURL),1500);});};//..constApp=()=>{//..constphoto=getMyAwesomePic();return(<h2>Our Photo Album</h2><imgsrc={photo}alt="Photo"/>// ..

🌳 Suspense and Caching mechanisms with 🌴 createResource

  • createResource() 가 async call 을 추적
// ---- Library ---- //constresourceCache={};constcreateResource=(asyncTask,key)=>{// First check if the key is present in the cache.// if so simply return the cached value.if(resourceCache[key])returnresourceCache[key];// If not then we need handle the promise here// ....};
  • resourceCache 에 async task 보관
  • key 에 해당하는 resource 가 존재하면 해당 task 가 끝났다는 의미
  • key 없으면? 아직 resolved 되지 않았으므로 렌더링 하면 안됨

💫 Branching our VirtualDOM creation 🐲

// ---- Library ---- //
const resourceCache = {};
const createResource = (asyncTask, key) => {
// First check if the key is present in the cache.
// if so simply return the cached value.
if (resourceCache[key]) return resourceCache[key];
// If not
+ throw { promise: asyncTask(), key };
};
  • key 없으면 바로 throw 해버린다 -> virtual DOM tree 생성 중단
// ---- Application --- //constApp=()=>{//..constphoto=createResource(getMyAwesomePic,'photo');return(//..
  • 현재는 Uncaught error 나는게 정상
// ---- Library --- //constReact={createElement: (tag,props, ...children)=>{if(typeoftag==="function"){try{returntag(props, ...children);}catch({ promise, key }){console.log(promise);console.log(key);}}//..},};
  • catch 는 되었지만 여전히 Promise 어떻게 처리할 지 모름
  • h2 로 간단한 fallback UI 를 만들어보자
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// We branch off the VirtualDOM here// now this will be immediately be rendered.return{tag: 'h2',props: null,children: ['loading your image']};

☃️ Suspense in Action 💨

  • promise 가 throw 된 경우 resourceCache 에 담아 추적
  • 다음 번 loop 에서 resolved 되었다면 rerender 시에 반영됨
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// Handle when this promise is resolved/rejected.promise.then((value)=>{resourceCache[key]=value;reRender();});//..

🥝 Conclusion 🥥

  • 과제: 현재 구현으로는 resource 가 2 개 이상일 때 모든 resource 가 resolved 되어야 rendering 이 될 텐데 어떻게 병렬 처리를 할 것인가?

Lessons Learned

  • Concurrent React 는 중단 가능한 rendering 이다
    • try/catch 를 이런식으로 활용 할 줄은 몰랐는데.. 이건 업무로직에서도 활용 해볼 수 있을 것 같다
  • Suspense 는 async call 을 처리하기 위한 메커니즘이다
    • 그냥 성능을 위한 부가 기능으로만 생각했던 Suspense 의 동작 방식을 살펴볼 수 있어 좋았다
  • 단순한 구조의 resourceCache 만으로 Promise 를 추적할 수 있는게 흥미로웠지만,
    • 실제로는 더 복잡한 구조가 필요할 것 같다. eg) resource 가 실패했을 때 재시도 하는 로직

Question

  • fetchProfileData 예제는 오타인거 겠지?
  • Cache 관련 고민해보아야 할 것들
    • Cache invalidation은 어떻게 처리하나요?
    • Memory leak 방지를 위한 cleanup 전략은?
  • Concurrent Mode가 Fiber 아키텍처와 어떤 관련이 있나요?
  • SWR 은 데이터의 관점에서, Suspense 는 렌더링 관점에서 비동기 처리를 한거거라고 보면 될까?

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - study-react-from-scratch-hoco/henry · GitHub
Skip to content

Repository files navigation

my-react

Let’s build a React from scratch

Part 1 — VirtualDOM and Renderer

  • 개정: typescript 가 아닌 tsc 사용 해야함
-npx typescript --init+npx tsc --init
  • 하나의 스크립트로 묶기
"dev": "tsc -w & npx serve ."

Our First JSX

  • React 를 만들어줌
  • React.createElement 를 추가 해줌
  • HTML tag 를 작성하면 가장 하위부터 상위로 태그 개수만큼 createElement 를 호출함
constApp=(<divdraggable><h2>Hello React!</h2><p>I am a pargraph</p><inputtype="text"/></div>);
['h2',null,'Hello React!']['p',null,'I am a pargraph']['input',{}]['div',{},undefined,undefined,undefined]

Introducing Virtual DOM

  • tree 의 복제본을 저장하기 위해 tag, props, children 을 분리해서 받음
  • tag 가 fuction 인 경우 직접 실행해서 el 반환

Let’s Render our VirtualDOM (renderer)

  • <div id="myapp"></div> 을 virtual DOM 의 root node 로 잡고 실제 DOM 을 렌더링한다
  1. 해당 type의 실제 DOM node 생성
  2. props 복제
  3. children 존재하는 경우 1~2 를 반복하여 현재 DOM 하위로 append
  4. container.appendChild(domEl) 으로 브라우저 container 에 추가
  5. Text node 의 경우 별도 처리 필요

Lessons Learned

  • 리액트 처음 배울때는 초고수들만 직접 만들기 하는줄 알아서 겁먹었는데 생각 보다 간단하다.
  • 사실 JSX 가 너무 많은걸 해주는 것 같다. JSX 직접 만들기도 도전?
  • virtual DOM 의 한계에 대한 글들이 요즘 많이 보이는데, signal 기반도 직접 만들기 해보면 좋을 것 같다

Part 2 — State Management and React Hooks

🤹 Introduction to React Hooks (useState)🎪

  • 초기값을 받아 state 와 setter 를 return
constuseState=(initialState)=>{console.log("useState is initialized with value:",initialState);letstate=initialState;constsetState=(newState)=>{console.log("setState is called with newState value:",newState);state=newState;};return[state,setState];};

🌗 Introducing Re-render for our App 🌝

  • 모든 것을 다시 다 reRender 하기
    • onchange 마다 reRender 가 호출되고, useState 가 호출되면서 state 가 initialValue 로 reset 된다
    • redering 중에 새 값을 잃는다
    • 현재 render 함수는 append 만 수행

reRender 시에 rootNode.innerHTML 초기화

// ---- Library --- //constreRender=()=>{console.log("reRender-ing :)");constrootNode=document.getElementById("myapp");// reset/clean whatever is rendered alreadyrootNode.innerHTML="";// then render Freshrender(<App/>,rootNode);};

🍔 State-fulness and Global State Management 🍟

  • state 를 useState 밖에 두고 변경되었는지 확인하자
// ---- Library --- //letmyAppState;constuseState=(initialState)=>{// Check before setting AppState to initialState (reRender)myAppState=myAppState||initialState;console.log("useState is initialized with value:",myAppState);constsetState=(newState)=>{console.log("setState is called with newState value:",newState);myAppState=newState;// Render the UI fresh given state has changed.reRender();};return[myAppState,setState];};

👨🏻‍🎤 Managing Multiple States with useState() 😵‍💫

  • library 의 저자로서 얼마나 많은 state 가 어디에 필요할 지 모른다

  • 서로 다른 state 간에 overwrite 되어서는 안된다

  • cursor 로 관리하는 globalArray 생성

// ---- Library --- //
+const myAppState = [];+let myAppStateCursor = 0;
const useState = (initialState) => {
// get the cursor for this useState
+ const stateCursor = myAppStateCursor;
// Check before setting AppState to initialState (reRender)
+ myAppState[stateCursor] = myAppState[stateCursor] || initialState;
console.log(
+ `useState is initialized at cursor ${stateCursor} with value:`,
myAppState,
);
const setState = (newState) => {
console.log(
+ `setState is called at cursor ${stateCursor} with newState value:`,
newState,
);
+ myAppState[stateCursor] = newState;
// Render the UI fresh given state has changed.
reRender();
};
+ // prepare the cursor for the next state.+ myAppStateCursor++;+ console.log(`stateDump`, myAppState);+ return [myAppState[stateCursor], setState];
};
  • reRender 시에 myAppStateCursor 를 초기화
// ---- Library --- //
const reRender = () => {
// ..
rootNode.innerHTML = '';
+ // Reset the global state cursor+ myAppStateCursor = 0;
// then render Fresh
render(<App />, rootNode);
};

✌🏽 Why the rules of React 🤞🏽

Only Call Hooks at the Top Level

  • state 관리를 위한 global array 존재
  • 조건절이나 반복문 안에서 조건적으로 useState 와 같은 hook 이 호출된다면 cursor 추적하기가 어렵다

Lessons Learned

  • state 구현이 생각보다 쉬워서 놀랐다.
  • diffing 처리는 별도로 안하는건지 여기서만 단순화 시킨 건지 궁금하다.
  • multi state 처리할 때 꼭 cursor 를 별도로 둬야하나? map에대한 key 로 처리할 수는 없는지? 궁금하다

Question

  • 왜 input 의 onchange 를 정의 했는데도 우리가 흔히 하는 onChange 처럼 동작하지 않고 input 밖으로 포커스를 옮겨야만 반영되는것일까?

Part 3 — React Suspense and Concurrent Mode

  • React Suspense and Concurrent Mode

🦁 React Rendering Techniques 🐒

Approach 1: Fetch-on-Render (not using Suspense)

  • traditional way: fetching after the initial render
  • state 와 같은 방식으로 채움
functionProfilePage(){const[user,setUser]=useState(null);useEffect(()=>{fetchUser().then((u)=>setUser(u));},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimeline/></>);}
  • waterfall 단점: 의존된 데이터가 fetch 될 때마다 re-render 된다

Approach 2: Fetch-Then-Render (not using Suspense)

  • 컴포넌트에 대한 정보를 전용 function call 로 분리한다
  • render 를 trigger 하기 위해서 setState 는 여전히 사용
// Wrapping all data fetchingfunctionfetchProfileData(){returnPromise.all([fetchUser(),fetchPosts()]).then(([user,posts])=>{return{ user, posts };});}// Using it in our ComponentfunctionProfilePage(){const[user,setUser]=useState(null);const[posts,setPosts]=useState(null);useEffect(()=>{promise.then((data)=>{setUser(data.user);setPosts(data.posts);});},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimelineposts={posts}/></>);}
  1. Start fetching
  2. Finish fetching
  3. Start rendering
  • fetchProfileData 를 사용을 안하는데?

Approach 3: Render-as-You-Fetch (using Suspense)

  1. Start fetching
  2. Start rendering
  3. Finish fetching
  • fetching 최적화 고려할 필요가 없다
    • fetching 완료되면 한번만 렌더링 하면 된다
  • image, 다른페이지, 문서 등을 non-blocking 으로 가져올 수 있다

🦖 How does React Suspense Work? 🦇

  • : React render cycle 에서 async call 을 처리하기 위한 메커니즘
  • React 의 rendering 은 원래는 synchrounous
  • renderer 는 VirtualDOM 에만 적용
  • DOM 의 어떤 부분에 signal 을 주고 기다려야하는지 구분 필요
  • 모든 promise 들을 추적해서 작업이 끝나면 자동으로 rendering 수행

🐙 What is Concurrent Mode 🦑

  • 항상 부모-자식 구모가 아닌 경우를 처리하기 위해 try-catch block 의 컨셉을 차용하여 아직 로딩중인 VirtualDOM tree 정보를 전송

Concurrent React 는 중단 가능한 rendering 이다

🦈 Our own little remote API 🐋

  • simulate slow image fetching
  • 현재는 promise 처리를 못하기때문에 아래 코드가 에러 나는게 맞다
// ---- Remote API ---- //constphotoURL='https://picsum.photos/200';constgetMyAwesomePic=()=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>resolve(photoURL),1500);});};//..constApp=()=>{//..constphoto=getMyAwesomePic();return(<h2>Our Photo Album</h2><imgsrc={photo}alt="Photo"/>// ..

🌳 Suspense and Caching mechanisms with 🌴 createResource

  • createResource() 가 async call 을 추적
// ---- Library ---- //constresourceCache={};constcreateResource=(asyncTask,key)=>{// First check if the key is present in the cache.// if so simply return the cached value.if(resourceCache[key])returnresourceCache[key];// If not then we need handle the promise here// ....};
  • resourceCache 에 async task 보관
  • key 에 해당하는 resource 가 존재하면 해당 task 가 끝났다는 의미
  • key 없으면? 아직 resolved 되지 않았으므로 렌더링 하면 안됨

💫 Branching our VirtualDOM creation 🐲

// ---- Library ---- //
const resourceCache = {};
const createResource = (asyncTask, key) => {
// First check if the key is present in the cache.
// if so simply return the cached value.
if (resourceCache[key]) return resourceCache[key];
// If not
+ throw { promise: asyncTask(), key };
};
  • key 없으면 바로 throw 해버린다 -> virtual DOM tree 생성 중단
// ---- Application --- //constApp=()=>{//..constphoto=createResource(getMyAwesomePic,'photo');return(//..
  • 현재는 Uncaught error 나는게 정상
// ---- Library --- //constReact={createElement: (tag,props, ...children)=>{if(typeoftag==="function"){try{returntag(props, ...children);}catch({ promise, key }){console.log(promise);console.log(key);}}//..},};
  • catch 는 되었지만 여전히 Promise 어떻게 처리할 지 모름
  • h2 로 간단한 fallback UI 를 만들어보자
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// We branch off the VirtualDOM here// now this will be immediately be rendered.return{tag: 'h2',props: null,children: ['loading your image']};

☃️ Suspense in Action 💨

  • promise 가 throw 된 경우 resourceCache 에 담아 추적
  • 다음 번 loop 에서 resolved 되었다면 rerender 시에 반영됨
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// Handle when this promise is resolved/rejected.promise.then((value)=>{resourceCache[key]=value;reRender();});//..

🥝 Conclusion 🥥

  • 과제: 현재 구현으로는 resource 가 2 개 이상일 때 모든 resource 가 resolved 되어야 rendering 이 될 텐데 어떻게 병렬 처리를 할 것인가?

Lessons Learned

  • Concurrent React 는 중단 가능한 rendering 이다
    • try/catch 를 이런식으로 활용 할 줄은 몰랐는데.. 이건 업무로직에서도 활용 해볼 수 있을 것 같다
  • Suspense 는 async call 을 처리하기 위한 메커니즘이다
    • 그냥 성능을 위한 부가 기능으로만 생각했던 Suspense 의 동작 방식을 살펴볼 수 있어 좋았다
  • 단순한 구조의 resourceCache 만으로 Promise 를 추적할 수 있는게 흥미로웠지만,
    • 실제로는 더 복잡한 구조가 필요할 것 같다. eg) resource 가 실패했을 때 재시도 하는 로직

Question

  • fetchProfileData 예제는 오타인거 겠지?
  • Cache 관련 고민해보아야 할 것들
    • Cache invalidation은 어떻게 처리하나요?
    • Memory leak 방지를 위한 cleanup 전략은?
  • Concurrent Mode가 Fiber 아키텍처와 어떤 관련이 있나요?
  • SWR 은 데이터의 관점에서, Suspense 는 렌더링 관점에서 비동기 처리를 한거거라고 보면 될까?

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - study-react-from-scratch-hoco/henry · GitHub
Skip to content

Repository files navigation

my-react

Let’s build a React from scratch

Part 1 — VirtualDOM and Renderer

  • 개정: typescript 가 아닌 tsc 사용 해야함
-npx typescript --init+npx tsc --init
  • 하나의 스크립트로 묶기
"dev": "tsc -w & npx serve ."

Our First JSX

  • React 를 만들어줌
  • React.createElement 를 추가 해줌
  • HTML tag 를 작성하면 가장 하위부터 상위로 태그 개수만큼 createElement 를 호출함
constApp=(<divdraggable><h2>Hello React!</h2><p>I am a pargraph</p><inputtype="text"/></div>);
['h2',null,'Hello React!']['p',null,'I am a pargraph']['input',{}]['div',{},undefined,undefined,undefined]

Introducing Virtual DOM

  • tree 의 복제본을 저장하기 위해 tag, props, children 을 분리해서 받음
  • tag 가 fuction 인 경우 직접 실행해서 el 반환

Let’s Render our VirtualDOM (renderer)

  • <div id="myapp"></div> 을 virtual DOM 의 root node 로 잡고 실제 DOM 을 렌더링한다
  1. 해당 type의 실제 DOM node 생성
  2. props 복제
  3. children 존재하는 경우 1~2 를 반복하여 현재 DOM 하위로 append
  4. container.appendChild(domEl) 으로 브라우저 container 에 추가
  5. Text node 의 경우 별도 처리 필요

Lessons Learned

  • 리액트 처음 배울때는 초고수들만 직접 만들기 하는줄 알아서 겁먹었는데 생각 보다 간단하다.
  • 사실 JSX 가 너무 많은걸 해주는 것 같다. JSX 직접 만들기도 도전?
  • virtual DOM 의 한계에 대한 글들이 요즘 많이 보이는데, signal 기반도 직접 만들기 해보면 좋을 것 같다

Part 2 — State Management and React Hooks

🤹 Introduction to React Hooks (useState)🎪

  • 초기값을 받아 state 와 setter 를 return
constuseState=(initialState)=>{console.log("useState is initialized with value:",initialState);letstate=initialState;constsetState=(newState)=>{console.log("setState is called with newState value:",newState);state=newState;};return[state,setState];};

🌗 Introducing Re-render for our App 🌝

  • 모든 것을 다시 다 reRender 하기
    • onchange 마다 reRender 가 호출되고, useState 가 호출되면서 state 가 initialValue 로 reset 된다
    • redering 중에 새 값을 잃는다
    • 현재 render 함수는 append 만 수행

reRender 시에 rootNode.innerHTML 초기화

// ---- Library --- //constreRender=()=>{console.log("reRender-ing :)");constrootNode=document.getElementById("myapp");// reset/clean whatever is rendered alreadyrootNode.innerHTML="";// then render Freshrender(<App/>,rootNode);};

🍔 State-fulness and Global State Management 🍟

  • state 를 useState 밖에 두고 변경되었는지 확인하자
// ---- Library --- //letmyAppState;constuseState=(initialState)=>{// Check before setting AppState to initialState (reRender)myAppState=myAppState||initialState;console.log("useState is initialized with value:",myAppState);constsetState=(newState)=>{console.log("setState is called with newState value:",newState);myAppState=newState;// Render the UI fresh given state has changed.reRender();};return[myAppState,setState];};

👨🏻‍🎤 Managing Multiple States with useState() 😵‍💫

  • library 의 저자로서 얼마나 많은 state 가 어디에 필요할 지 모른다

  • 서로 다른 state 간에 overwrite 되어서는 안된다

  • cursor 로 관리하는 globalArray 생성

// ---- Library --- //
+const myAppState = [];+let myAppStateCursor = 0;
const useState = (initialState) => {
// get the cursor for this useState
+ const stateCursor = myAppStateCursor;
// Check before setting AppState to initialState (reRender)
+ myAppState[stateCursor] = myAppState[stateCursor] || initialState;
console.log(
+ `useState is initialized at cursor ${stateCursor} with value:`,
myAppState,
);
const setState = (newState) => {
console.log(
+ `setState is called at cursor ${stateCursor} with newState value:`,
newState,
);
+ myAppState[stateCursor] = newState;
// Render the UI fresh given state has changed.
reRender();
};
+ // prepare the cursor for the next state.+ myAppStateCursor++;+ console.log(`stateDump`, myAppState);+ return [myAppState[stateCursor], setState];
};
  • reRender 시에 myAppStateCursor 를 초기화
// ---- Library --- //
const reRender = () => {
// ..
rootNode.innerHTML = '';
+ // Reset the global state cursor+ myAppStateCursor = 0;
// then render Fresh
render(<App />, rootNode);
};

✌🏽 Why the rules of React 🤞🏽

Only Call Hooks at the Top Level

  • state 관리를 위한 global array 존재
  • 조건절이나 반복문 안에서 조건적으로 useState 와 같은 hook 이 호출된다면 cursor 추적하기가 어렵다

Lessons Learned

  • state 구현이 생각보다 쉬워서 놀랐다.
  • diffing 처리는 별도로 안하는건지 여기서만 단순화 시킨 건지 궁금하다.
  • multi state 처리할 때 꼭 cursor 를 별도로 둬야하나? map에대한 key 로 처리할 수는 없는지? 궁금하다

Question

  • 왜 input 의 onchange 를 정의 했는데도 우리가 흔히 하는 onChange 처럼 동작하지 않고 input 밖으로 포커스를 옮겨야만 반영되는것일까?

Part 3 — React Suspense and Concurrent Mode

  • React Suspense and Concurrent Mode

🦁 React Rendering Techniques 🐒

Approach 1: Fetch-on-Render (not using Suspense)

  • traditional way: fetching after the initial render
  • state 와 같은 방식으로 채움
functionProfilePage(){const[user,setUser]=useState(null);useEffect(()=>{fetchUser().then((u)=>setUser(u));},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimeline/></>);}
  • waterfall 단점: 의존된 데이터가 fetch 될 때마다 re-render 된다

Approach 2: Fetch-Then-Render (not using Suspense)

  • 컴포넌트에 대한 정보를 전용 function call 로 분리한다
  • render 를 trigger 하기 위해서 setState 는 여전히 사용
// Wrapping all data fetchingfunctionfetchProfileData(){returnPromise.all([fetchUser(),fetchPosts()]).then(([user,posts])=>{return{ user, posts };});}// Using it in our ComponentfunctionProfilePage(){const[user,setUser]=useState(null);const[posts,setPosts]=useState(null);useEffect(()=>{promise.then((data)=>{setUser(data.user);setPosts(data.posts);});},[]);if(user===null){return<p>Loading profile...</p>;}return(<><h1>{user.name}</h1><ProfileTimelineposts={posts}/></>);}
  1. Start fetching
  2. Finish fetching
  3. Start rendering
  • fetchProfileData 를 사용을 안하는데?

Approach 3: Render-as-You-Fetch (using Suspense)

  1. Start fetching
  2. Start rendering
  3. Finish fetching
  • fetching 최적화 고려할 필요가 없다
    • fetching 완료되면 한번만 렌더링 하면 된다
  • image, 다른페이지, 문서 등을 non-blocking 으로 가져올 수 있다

🦖 How does React Suspense Work? 🦇

  • : React render cycle 에서 async call 을 처리하기 위한 메커니즘
  • React 의 rendering 은 원래는 synchrounous
  • renderer 는 VirtualDOM 에만 적용
  • DOM 의 어떤 부분에 signal 을 주고 기다려야하는지 구분 필요
  • 모든 promise 들을 추적해서 작업이 끝나면 자동으로 rendering 수행

🐙 What is Concurrent Mode 🦑

  • 항상 부모-자식 구모가 아닌 경우를 처리하기 위해 try-catch block 의 컨셉을 차용하여 아직 로딩중인 VirtualDOM tree 정보를 전송

Concurrent React 는 중단 가능한 rendering 이다

🦈 Our own little remote API 🐋

  • simulate slow image fetching
  • 현재는 promise 처리를 못하기때문에 아래 코드가 에러 나는게 맞다
// ---- Remote API ---- //constphotoURL='https://picsum.photos/200';constgetMyAwesomePic=()=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>resolve(photoURL),1500);});};//..constApp=()=>{//..constphoto=getMyAwesomePic();return(<h2>Our Photo Album</h2><imgsrc={photo}alt="Photo"/>// ..

🌳 Suspense and Caching mechanisms with 🌴 createResource

  • createResource() 가 async call 을 추적
// ---- Library ---- //constresourceCache={};constcreateResource=(asyncTask,key)=>{// First check if the key is present in the cache.// if so simply return the cached value.if(resourceCache[key])returnresourceCache[key];// If not then we need handle the promise here// ....};
  • resourceCache 에 async task 보관
  • key 에 해당하는 resource 가 존재하면 해당 task 가 끝났다는 의미
  • key 없으면? 아직 resolved 되지 않았으므로 렌더링 하면 안됨

💫 Branching our VirtualDOM creation 🐲

// ---- Library ---- //
const resourceCache = {};
const createResource = (asyncTask, key) => {
// First check if the key is present in the cache.
// if so simply return the cached value.
if (resourceCache[key]) return resourceCache[key];
// If not
+ throw { promise: asyncTask(), key };
};
  • key 없으면 바로 throw 해버린다 -> virtual DOM tree 생성 중단
// ---- Application --- //constApp=()=>{//..constphoto=createResource(getMyAwesomePic,'photo');return(//..
  • 현재는 Uncaught error 나는게 정상
// ---- Library --- //constReact={createElement: (tag,props, ...children)=>{if(typeoftag==="function"){try{returntag(props, ...children);}catch({ promise, key }){console.log(promise);console.log(key);}}//..},};
  • catch 는 되었지만 여전히 Promise 어떻게 처리할 지 모름
  • h2 로 간단한 fallback UI 를 만들어보자
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// We branch off the VirtualDOM here// now this will be immediately be rendered.return{tag: 'h2',props: null,children: ['loading your image']};

☃️ Suspense in Action 💨

  • promise 가 throw 된 경우 resourceCache 에 담아 추적
  • 다음 번 loop 에서 resolved 되었다면 rerender 시에 반영됨
// ---- Library --- //createElement: (tag,props, ...children)=>{//..}catch({ promise, key }){// Handle when this promise is resolved/rejected.promise.then((value)=>{resourceCache[key]=value;reRender();});//..

🥝 Conclusion 🥥

  • 과제: 현재 구현으로는 resource 가 2 개 이상일 때 모든 resource 가 resolved 되어야 rendering 이 될 텐데 어떻게 병렬 처리를 할 것인가?

Lessons Learned

  • Concurrent React 는 중단 가능한 rendering 이다
    • try/catch 를 이런식으로 활용 할 줄은 몰랐는데.. 이건 업무로직에서도 활용 해볼 수 있을 것 같다
  • Suspense 는 async call 을 처리하기 위한 메커니즘이다
    • 그냥 성능을 위한 부가 기능으로만 생각했던 Suspense 의 동작 방식을 살펴볼 수 있어 좋았다
  • 단순한 구조의 resourceCache 만으로 Promise 를 추적할 수 있는게 흥미로웠지만,
    • 실제로는 더 복잡한 구조가 필요할 것 같다. eg) resource 가 실패했을 때 재시도 하는 로직

Question

  • fetchProfileData 예제는 오타인거 겠지?
  • Cache 관련 고민해보아야 할 것들
    • Cache invalidation은 어떻게 처리하나요?
    • Memory leak 방지를 위한 cleanup 전략은?
  • Concurrent Mode가 Fiber 아키텍처와 어떤 관련이 있나요?
  • SWR 은 데이터의 관점에서, Suspense 는 렌더링 관점에서 비동기 처리를 한거거라고 보면 될까?

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages