Skip to content

study-react-from-scratch-hoco/henry

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

19 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

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 ๋ฅผ ํ˜ธ์ถœํ•จ
const App = (
  <div draggable>
    <h2>Hello React!</h2>
    <p>I am a pargraph</p>
    <input type="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
const useState = (initialState) => {
  console.log("useState is initialized with value:", initialState);
  let state = initialState;
  const setState = (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 --- //
const reRender = () => {
  console.log("reRender-ing :)");
  const rootNode = document.getElementById("myapp");
  // reset/clean whatever is rendered already
  rootNode.innerHTML = "";
  // then render Fresh
  render(<App />, rootNode);
};

๐Ÿ” State-fulness and Global State Management ๐ŸŸ

  • state ๋ฅผ useState ๋ฐ–์— ๋‘๊ณ  ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์ž
// ---- Library --- //
let myAppState;
const useState = (initialState) => {
  // Check before setting AppState to initialState (reRender)
  myAppState = myAppState || initialState;
  console.log("useState is initialized with value:", myAppState);
  const setState = (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 ์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ฑ„์›€
function ProfilePage() {
  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 fetching
function fetchProfileData() {
  return Promise.all([fetchUser(), fetchPosts()]).then(([user, posts]) => {
    return { user, posts };
  });
}
// Using it in our Component
function ProfilePage() {
  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>
      <ProfileTimeline posts={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 ---- //
const photoURL = 'https://picsum.photos/200';
const getMyAwesomePic = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(photoURL), 1500);
  });
};
//..
const App = () => {
//..
  const photo = getMyAwesomePic();
return (
      <h2>Our Photo Album</h2>
      <img src={photo} alt="Photo" />
// ..

๐ŸŒณ Suspense and Caching mechanisms with ๐ŸŒด createResource

  • createResource() ๊ฐ€ async call ์„ ์ถ”์ 
// ---- 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 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 --- //
const App = () => {
//..
  const photo = createResource(getMyAwesomePic, 'photo');
return (
//..
  • ํ˜„์žฌ๋Š” Uncaught error ๋‚˜๋Š”๊ฒŒ ์ •์ƒ
// ---- Library --- //
const React = {
  createElement: (tag, props, ...children) => {
    if (typeof tag === "function") {
      try {
        return tag(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

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors