From 033f5cee543251e477e1b24ece99d2330123b7e4 Mon Sep 17 00:00:00 2001 From: d5ng Date: Sun, 22 Dec 2024 13:15:34 +0900 Subject: [PATCH 01/16] =?UTF-8?q?=F0=9F=93=9D=20Docs:=20Readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 readme.md diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c77483d --- /dev/null +++ b/readme.md @@ -0,0 +1,147 @@ +# Vanilla Javascript로 Virtual DOM 만들기 + +황준일님의 Vanilla Javascript로 Virtual DOM 만들기 아티클을 참고하여, 브라우저 렌더링 과정에 대해 이해하고, 성능적인 고민과 Virtual DOM이 해결하려는 문제를 파악하자 + +- 구현하면서 느낀 점들을 작성하며 회고하자. +- Virtual DOM이 해결하려는 문제를 인지하면서 구현해보자. +- 모르는 것들이나 새롭게 알게 된 것들이 있다면 적극적으로 공유하며 서로의 학습을 확장해 보자. + +[황준일님 Vanilla Javascript로 Virtual DOM 만들기 링크](https://junilhwang.github.io/TIL/Javascript/Design/Vanilla-JS-Virtual-DOM/#_1-virtualdom%E1%84%8B%E1%85%B5-%E1%84%81%E1%85%A9%E1%86%A8-%E1%84%91%E1%85%B5%E1%86%AF%E1%84%8B%E1%85%AD%E1%84%92%E1%85%A1%E1%86%AB%E1%84%80%E1%85%A1) + +## 🚀 학습 목표 + +1. 브라우저 렌더링 과정에 대해 명확히 이해하자. +2. Virtual DOM이 해결하려는 문제를 파악하고, 어떻게 개선할 수 있는지를 파악해보자. +3. Diff 알고리즘이 어떤 방식으로 동작하는 이해하자. +4. Virtual DOM을 사용했을 때와 사용하지 않았을 때를 비교해보면서 느껴보자. + +## 📝 Pull Request 주의할 점. + +- **학습과 고민의 흔적 기록** + - 본인이 배우고 고민했던 점들을 최대한 꼼꼼하게 작성해주세요. + - 작성된 내용은 리뷰어가 의도를 이해하고 더 나은 피드백을 제공하는 데 큰 도움이 됩니다. +- **코드리뷰는 존중과 솔직함을 기반으로** + - 서로 상처받지 않도록 배려하며 리뷰를 작성해주세요. + - 하지만, 서로의 발전을 위해 솔직한 피드백을 주고받는 문화를 지향합시다. +- **PR 방식** + - [본인 이름] Virtual DOM (본인 이름 브랜치에 PR 올려주세요) + - 위와 같은 방식으로 PR 올려주세요. + +## 🤖 GPT의 아티클 요약 + +# 브라우저의 렌더링 과정 + +## 1. HTML과 CSS 파싱 + +- 브라우저는 HTML과 CSS를 읽어 DOM 트리와 CSSOM 트리를 생성합니다. +- 이후, DOM과 CSSOM을 결합하여 렌더 트리를 구성합니다. + +## 2. 레이아웃과 페인트 + +- **레이아웃**: 각 요소의 위치와 크기를 계산합니다. +- **페인트**: 렌더 트리를 픽셀로 변환하여 화면에 표시합니다. + +## 3. 성능 문제 + +- DOM이나 CSS에 변화가 생길 경우, 브라우저는 **reflow(레이아웃 다시 계산)**나 **repaint(화면 다시 그림)**를 실행합니다. +- 이러한 작업은 성능 저하의 주요 원인이 됩니다. + +--- + +# 가상 DOM(Virtual DOM)의 필요성 + +- DOM 조작이 복잡하고 잦아질수록 브라우저의 렌더링 성능이 저하됩니다. +- **가상 DOM**은 이러한 문제를 해결하기 위해 도입된 개념입니다. + - 메모리 상에서 DOM 구조를 본뜬 **자바스크립트 객체**를 생성합니다. + - 변경 사항을 가상 DOM에서 비교(diff)하고, 실제 DOM에 **최소한의 업데이트**만 적용합니다. + +--- + +# 가상 DOM의 기본 원리 + +## 1. 가상 DOM 생성 + +- 실제 DOM을 대신할 **자바스크립트 객체**를 만듭니다. +- 각 요소는 객체로 표현되며, 속성, 자식 요소, 이벤트 등을 포함합니다. + +## 2. 가상 DOM에서 실제 DOM 생성 + +- 가상 DOM 객체를 기반으로 실제 DOM 요소를 동적으로 생성합니다. +- 이 과정은 초기 렌더링 시에만 수행됩니다. + +## 3. Diff 알고리즘 + +- 이전 가상 DOM과 새로운 가상 DOM을 비교합니다. +- 변경된 부분만 찾아 실제 DOM에 업데이트합니다. +- 이를 통해 불필요한 렌더링 작업을 줄이고 성능을 최적화합니다. + +## 4. 최소 DOM 조작 + +- 가상 DOM에서 찾은 차이점만 실제 DOM에 반영합니다. +- 이를 통해 브라우저의 렌더링 부담을 줄이고 효율성을 높입니다. + +--- + +# Diff 알고리즘의 작동 방식 + +## 1. 요소 비교 + +- 두 가상 DOM 객체를 비교하여 같은 요소인지 판단합니다. +- 만약 타입이 다르면 해당 요소를 통째로 교체합니다. + +## 2. 속성 비교 + +- 두 요소의 속성을 비교하여 변경된 속성만 업데이트합니다. +- 속성이 제거된 경우, 실제 DOM에서도 제거합니다. + +## 3. 자식 노드 비교 + +- 자식 노드를 순회하며 재귀적으로 diff를 수행합니다. +- 추가되거나 삭제된 자식 노드만 업데이트합니다. + +--- + +# 가상 DOM의 장점 + +## 1. 성능 최적화 + +- 변경된 부분만 DOM에 반영하므로, 브라우저의 작업량을 줄일 수 있습니다. +- 렌더링 과정에서 발생하는 reflow와 repaint를 최소화합니다. + +## 2. 코드 유지보수성 향상 + +- DOM 조작 코드의 복잡성을 줄이고, 선언형 프로그래밍 방식을 채택할 수 있습니다. +- React나 Vue와 같은 라이브러리가 이러한 방식을 활용합니다. + +## 3. 추상화 + +- 개발자는 DOM 조작의 세부 사항을 신경 쓰지 않아도 됩니다. +- 변경 사항을 선언적으로 표현하면, 가상 DOM이 알아서 처리합니다. + +--- + +# 바닐라 자바스크립트로 구현하는 방법 + +- 가상 DOM을 이해하기 위해 바닐라 자바스크립트로 간단한 버전을 구현할 수 있습니다. +- 주요 단계는 다음과 같습니다: + 1. **가상 DOM 구조 정의**: 가상 DOM을 표현하는 데이터 구조를 설계합니다. + 2. **가상 DOM을 실제 DOM으로 변환**: 가상 DOM 객체를 기반으로 실제 DOM 요소를 생성합니다. + 3. **Diff 알고리즘 구현**: 이전 상태와 새로운 상태를 비교하여 변경 사항을 찾습니다. + 4. **DOM 업데이트**: Diff 결과를 바탕으로 최소한의 DOM 조작을 수행합니다. + +--- + +# 컴포넌트와 상태 관리의 통합 + +- 가상 DOM은 **컴포넌트 기반 개발**과 **상태 관리**에도 적합합니다. + - 각 컴포넌트는 자신의 상태(state)를 관리합니다. + - 상태 변화가 발생하면 해당 컴포넌트만 다시 렌더링합니다. +- 이를 통해 애플리케이션의 **구조화**와 **성능 최적화**가 동시에 이루어집니다. + +--- + +# 결론 + +- 가상 DOM은 복잡한 DOM 조작 문제를 해결하기 위한 강력한 도구입니다. +- React와 Vue 같은 라이브러리가 이를 활용하여 선언형 프로그래밍과 성능 최적화를 제공합니다. +- 바닐라 자바스크립트로 간단한 가상 DOM을 구현해 보면, 가상 DOM의 내부 원리를 이해하고 DOM 조작에 대한 깊은 통찰을 얻을 수 있습니다. From 96346aeec08e87bb450d63729543d1439a046012 Mon Sep 17 00:00:00 2001 From: d5ng Date: Thu, 26 Dec 2024 11:04:31 +0900 Subject: [PATCH 02/16] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EA=B0=80=EC=83=81?= =?UTF-8?q?=EB=8F=94=20=EB=A7=8C=EB=93=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ src/core/v-dom.ts | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/core/v-dom.ts diff --git a/.gitignore b/.gitignore index a547bf3..a88a1bf 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ dist-ssr *.njsproj *.sln *.sw? + +yarn.lock +install-state.gz \ No newline at end of file diff --git a/src/core/v-dom.ts b/src/core/v-dom.ts new file mode 100644 index 0000000..8bb3170 --- /dev/null +++ b/src/core/v-dom.ts @@ -0,0 +1,39 @@ +type Props = Record | null; +type Children = (string | VNode)[]; + +interface VNode { + type: keyof HTMLElementTagNameMap; + props: Props; + children: Children; +} + +function h(type: keyof HTMLElementTagNameMap, props: Props, ...children: Children): VNode { + return { type, props, children }; +} + +console.log( + h( + 'div', + { id: 'app' }, + h( + 'ul', + null, + h( + 'li', + null, + h('input', { type: 'checkbox', className: 'toggle' }), + 'todo list item 1', + h('button', { className: 'remove' }, '삭제'), + 'Hello World', + ), + h( + 'li', + { className: 'completed' }, + h('input', { type: 'checkbox', className: 'toggle', checked: true }), + 'todo list item 2', + h('button', { className: 'remove' }, '삭제'), + ), + ), + h('form', h('input', { type: 'text' }), h('button', { type: 'submit' }, '추가')), + ), +); From 83cfe1c3e032f23681b70c7ff5ff6876fd5d4411 Mon Sep 17 00:00:00 2001 From: d5ng Date: Sat, 28 Dec 2024 13:53:17 +0900 Subject: [PATCH 03/16] =?UTF-8?q?=F0=9F=94=A7=20Chore:=20esbuild=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20=EC=B6=94=EA=B0=80(JSX=20=ED=8A=B8?= =?UTF-8?q?=EB=9E=9C=EC=8A=A4=ED=8C=8C=EC=9D=BC=EB=A7=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vite.config.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 vite.config.ts diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..76e116b --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [tsconfigPaths()], + esbuild: { + jsx: 'transform', + jsxInject: `import { h, Fragment } from '@/lib/jsx/jsx-runtime'`, + jsxFactory: 'h', + jsxFragment: 'Fragment', + }, +}); From c6e17b966da1daabedaeae9f70ccb71c8bf007e8 Mon Sep 17 00:00:00 2001 From: d5ng Date: Sat, 28 Dec 2024 13:53:41 +0900 Subject: [PATCH 04/16] =?UTF-8?q?=F0=9F=94=A7=20Chore:=20tsconfig=20path?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 ++- tsconfig.json | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ff687e2..088e348 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "devDependencies": { "typescript": "~5.6.2", - "vite": "^6.0.3" + "vite": "^6.0.3", + "vite-tsconfig-paths": "^5.1.4" } } diff --git a/tsconfig.json b/tsconfig.json index a4883f2..7583167 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,13 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noUncheckedSideEffectImports": true, + "jsx": "react-jsx", + "jsxImportSource": "@/lib/jsx", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } }, "include": ["src"] } From 1d7e51e53adddf5b03f8f2f6f831708b87095cff Mon Sep 17 00:00:00 2001 From: d5ng Date: Sat, 28 Dec 2024 13:54:12 +0900 Subject: [PATCH 05/16] =?UTF-8?q?=F0=9F=9A=9A=20Rename:=20=ED=99=95?= =?UTF-8?q?=EC=9E=A5=EC=9E=90=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.ts | 1 - src/main.tsx | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) delete mode 100644 src/main.ts create mode 100644 src/main.tsx diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index 62ed0f3..0000000 --- a/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -// Your Code diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..9220247 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,7 @@ +import { createElement } from '@/lib/jsx/jsx-runtime'; +import App from './App'; + +const app = document.getElementById('app')!; + +app.appendChild(createElement()!); +console.log(); From 467dc24c8f8dc1605ca7d476f0decd686617536c Mon Sep 17 00:00:00 2001 From: d5ng Date: Sat, 28 Dec 2024 13:55:36 +0900 Subject: [PATCH 06/16] =?UTF-8?q?=E2=9C=A8=20Feat:=20VDOM=EC=9D=84=20RealD?= =?UTF-8?q?OM=EC=9C=BC=EB=A1=9C=20=EB=B3=80=ED=99=98=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 2 +- src/App.tsx | 16 ++++++++++++++++ src/core/v-dom.ts | 39 -------------------------------------- src/global.d.ts | 5 +++++ src/lib/jsx/jsx-runtime.ts | 39 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 40 deletions(-) create mode 100644 src/App.tsx delete mode 100644 src/core/v-dom.ts create mode 100644 src/global.d.ts create mode 100644 src/lib/jsx/jsx-runtime.ts diff --git a/index.html b/index.html index 4c93138..134130a 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,6 @@
- + diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..c63c58e --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,16 @@ +export function Custom({ className }: { className: string }) { + return ( +
+ Custom +
+ ); +} + +export default function App({ count }: { count: number }) { + return ( +
+ + Hello World {count} +
+ ); +} diff --git a/src/core/v-dom.ts b/src/core/v-dom.ts deleted file mode 100644 index 8bb3170..0000000 --- a/src/core/v-dom.ts +++ /dev/null @@ -1,39 +0,0 @@ -type Props = Record | null; -type Children = (string | VNode)[]; - -interface VNode { - type: keyof HTMLElementTagNameMap; - props: Props; - children: Children; -} - -function h(type: keyof HTMLElementTagNameMap, props: Props, ...children: Children): VNode { - return { type, props, children }; -} - -console.log( - h( - 'div', - { id: 'app' }, - h( - 'ul', - null, - h( - 'li', - null, - h('input', { type: 'checkbox', className: 'toggle' }), - 'todo list item 1', - h('button', { className: 'remove' }, '삭제'), - 'Hello World', - ), - h( - 'li', - { className: 'completed' }, - h('input', { type: 'checkbox', className: 'toggle', checked: true }), - 'todo list item 2', - h('button', { className: 'remove' }, '삭제'), - ), - ), - h('form', h('input', { type: 'text' }), h('button', { type: 'submit' }, '추가')), - ), -); diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..c39c2cf --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,5 @@ +declare namespace JSX { + interface IntrinsicElements { + [elemName: string]: any; + } +} diff --git a/src/lib/jsx/jsx-runtime.ts b/src/lib/jsx/jsx-runtime.ts new file mode 100644 index 0000000..4d6c5dd --- /dev/null +++ b/src/lib/jsx/jsx-runtime.ts @@ -0,0 +1,39 @@ +type Type = keyof HTMLElementTagNameMap | Component; +type Props = Record | null; +type VNode = string | number | null | undefined | VDOM; +type Component = (props: Props) => VDOM; + +interface VDOM { + type?: Type; + props: Props; + children: VNode[]; +} + +export function h(type: Type, props: Props, ...children: VNode[]): VDOM { + if (typeof type === 'function') return type(props); + return { type, props, children: children.flat() }; +} + +export function createElement(node: VNode) { + if (node === null || node === undefined) { + return document.createDocumentFragment(); + } + + if (typeof node === 'string' || typeof node === 'number') { + return document.createTextNode(String(node)); + } + + node.props = node.props || {}; + + const element = document.createElement(node.type as keyof HTMLElementTagNameMap); + + Object.entries(node.props) + .filter(([_, value]) => value) + .forEach(([attr, value]) => { + element.setAttribute(attr, value); + }); + + node.children.map(createElement).forEach((child) => element.appendChild(child)); + + return element; +} From 595326c2b67fe9842e15e83bcdae894349332374 Mon Sep 17 00:00:00 2001 From: d5ng Date: Sat, 28 Dec 2024 13:59:05 +0900 Subject: [PATCH 07/16] =?UTF-8?q?=E2=9C=A8=20Feat:=20Fragment=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 4 ++-- src/lib/jsx/jsx-runtime.ts | 11 +++++++++-- vite.config.ts | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index c63c58e..b7eaed0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,9 +8,9 @@ export function Custom({ className }: { className: string }) { export default function App({ count }: { count: number }) { return ( -
+ <> Hello World {count} -
+ ); } diff --git a/src/lib/jsx/jsx-runtime.ts b/src/lib/jsx/jsx-runtime.ts index 4d6c5dd..166a413 100644 --- a/src/lib/jsx/jsx-runtime.ts +++ b/src/lib/jsx/jsx-runtime.ts @@ -1,4 +1,7 @@ -type Type = keyof HTMLElementTagNameMap | Component; +export const FRAGMENT = 'FRAGMENT' as const; + +type Fragment = typeof FRAGMENT; +type Type = keyof HTMLElementTagNameMap | Component | Fragment; type Props = Record | null; type VNode = string | number | null | undefined | VDOM; type Component = (props: Props) => VDOM; @@ -25,11 +28,15 @@ export function createElement(node: VNode) { node.props = node.props || {}; - const element = document.createElement(node.type as keyof HTMLElementTagNameMap); + const element = + node.type === FRAGMENT + ? document.createDocumentFragment() + : document.createElement(node.type as keyof HTMLElementTagNameMap); Object.entries(node.props) .filter(([_, value]) => value) .forEach(([attr, value]) => { + if (element instanceof DocumentFragment) return; element.setAttribute(attr, value); }); diff --git a/vite.config.ts b/vite.config.ts index 76e116b..0855e7e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,8 +5,8 @@ export default defineConfig({ plugins: [tsconfigPaths()], esbuild: { jsx: 'transform', - jsxInject: `import { h, Fragment } from '@/lib/jsx/jsx-runtime'`, + jsxInject: `import { h, FRAGMENT } from '@/lib/jsx/jsx-runtime'`, jsxFactory: 'h', - jsxFragment: 'Fragment', + jsxFragment: 'FRAGMENT', }, }); From 01b746275c8f529118ca5c355b44663c62389efa Mon Sep 17 00:00:00 2001 From: d5ng Date: Sat, 28 Dec 2024 14:12:46 +0900 Subject: [PATCH 08/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20createEl?= =?UTF-8?q?ement=20=ED=95=A8=EC=88=98=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/jsx/jsx-runtime.ts | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/lib/jsx/jsx-runtime.ts b/src/lib/jsx/jsx-runtime.ts index 166a413..9ffd04c 100644 --- a/src/lib/jsx/jsx-runtime.ts +++ b/src/lib/jsx/jsx-runtime.ts @@ -2,7 +2,7 @@ export const FRAGMENT = 'FRAGMENT' as const; type Fragment = typeof FRAGMENT; type Type = keyof HTMLElementTagNameMap | Component | Fragment; -type Props = Record | null; +type Props = Record; type VNode = string | number | null | undefined | VDOM; type Component = (props: Props) => VDOM; @@ -18,11 +18,11 @@ export function h(type: Type, props: Props, ...children: VNode[]): VDOM { } export function createElement(node: VNode) { - if (node === null || node === undefined) { + if (isNullOrUndefined(node)) { return document.createDocumentFragment(); } - if (typeof node === 'string' || typeof node === 'number') { + if (isPrimitive(node)) { return document.createTextNode(String(node)); } @@ -33,14 +33,31 @@ export function createElement(node: VNode) { ? document.createDocumentFragment() : document.createElement(node.type as keyof HTMLElementTagNameMap); - Object.entries(node.props) + if (isHTMLElement(element)) { + elementSetAttribute(element, node.props); + } + + node.children.map(createElement).forEach((child) => element.appendChild(child)); + + return element; +} + +function elementSetAttribute(element: HTMLElement, props: Props = {}) { + Object.entries(props) .filter(([_, value]) => value) .forEach(([attr, value]) => { - if (element instanceof DocumentFragment) return; element.setAttribute(attr, value); }); +} - node.children.map(createElement).forEach((child) => element.appendChild(child)); +function isHTMLElement(element: DocumentFragment | HTMLElement) { + return element instanceof HTMLElement; +} - return element; +function isNullOrUndefined(node: VNode) { + return node === null || node === undefined; +} + +function isPrimitive(node: VNode) { + return typeof node === 'string' || typeof node === 'number'; } From e76ef7b55954578b9d99dcbc04d6ce3567b97f95 Mon Sep 17 00:00:00 2001 From: d5ng Date: Sat, 28 Dec 2024 14:17:06 +0900 Subject: [PATCH 09/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EB=84=90=EB=A6=AC=EC=89=AC=20=EC=97=B0=EC=82=B0=EC=9E=90?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=B4=20props=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=EA=B0=92=20=EC=A7=80=EC=A0=95=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/jsx/jsx-runtime.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/jsx/jsx-runtime.ts b/src/lib/jsx/jsx-runtime.ts index 9ffd04c..fc5ef39 100644 --- a/src/lib/jsx/jsx-runtime.ts +++ b/src/lib/jsx/jsx-runtime.ts @@ -13,6 +13,7 @@ interface VDOM { } export function h(type: Type, props: Props, ...children: VNode[]): VDOM { + props = props ?? {}; if (typeof type === 'function') return type(props); return { type, props, children: children.flat() }; } @@ -26,8 +27,6 @@ export function createElement(node: VNode) { return document.createTextNode(String(node)); } - node.props = node.props || {}; - const element = node.type === FRAGMENT ? document.createDocumentFragment() From ec3b3dc3ecc9073fde4bbfe9ec365938b3c433c0 Mon Sep 17 00:00:00 2001 From: d5ng Date: Sat, 28 Dec 2024 14:18:35 +0900 Subject: [PATCH 10/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=ED=97=AC=ED=8D=BC=20=ED=95=A8=EC=88=98=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/jsx/jsx-runtime.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/jsx/jsx-runtime.ts b/src/lib/jsx/jsx-runtime.ts index fc5ef39..a3e1176 100644 --- a/src/lib/jsx/jsx-runtime.ts +++ b/src/lib/jsx/jsx-runtime.ts @@ -49,6 +49,9 @@ function elementSetAttribute(element: HTMLElement, props: Props = {}) { }); } +/** + * NOTE: createElement Helper Function + */ function isHTMLElement(element: DocumentFragment | HTMLElement) { return element instanceof HTMLElement; } From f03f1c662e7b70ae25d5db7064c5220e12114a4c Mon Sep 17 00:00:00 2001 From: d5ng Date: Sun, 29 Dec 2024 17:44:03 +0900 Subject: [PATCH 11/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20jsx-runt?= =?UTF-8?q?ime=20=ED=8C=8C=EC=9D=BC=20=EB=8B=A8=EC=9C=84=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/jsx/jsx-runtime.constant.ts | 1 + src/lib/jsx/jsx-runtime.ts | 31 +++-------------------------- src/lib/jsx/jsx-runtime.type.ts | 13 ++++++++++++ src/lib/jsx/jsx-runtime.util.ts | 16 +++++++++++++++ 4 files changed, 33 insertions(+), 28 deletions(-) create mode 100644 src/lib/jsx/jsx-runtime.constant.ts create mode 100644 src/lib/jsx/jsx-runtime.type.ts create mode 100644 src/lib/jsx/jsx-runtime.util.ts diff --git a/src/lib/jsx/jsx-runtime.constant.ts b/src/lib/jsx/jsx-runtime.constant.ts new file mode 100644 index 0000000..dae6768 --- /dev/null +++ b/src/lib/jsx/jsx-runtime.constant.ts @@ -0,0 +1 @@ +export const FRAGMENT = 'FRAGMENT' as const; diff --git a/src/lib/jsx/jsx-runtime.ts b/src/lib/jsx/jsx-runtime.ts index a3e1176..5083384 100644 --- a/src/lib/jsx/jsx-runtime.ts +++ b/src/lib/jsx/jsx-runtime.ts @@ -1,16 +1,6 @@ -export const FRAGMENT = 'FRAGMENT' as const; - -type Fragment = typeof FRAGMENT; -type Type = keyof HTMLElementTagNameMap | Component | Fragment; -type Props = Record; -type VNode = string | number | null | undefined | VDOM; -type Component = (props: Props) => VDOM; - -interface VDOM { - type?: Type; - props: Props; - children: VNode[]; -} +import { isHTMLElement, isNullOrUndefined, isPrimitive } from './jsx-runtime.util'; +import { FRAGMENT } from './jsx-runtime.constant'; +import type { Type, Props, VNode, VDOM } from './jsx-runtime.type'; export function h(type: Type, props: Props, ...children: VNode[]): VDOM { props = props ?? {}; @@ -48,18 +38,3 @@ function elementSetAttribute(element: HTMLElement, props: Props = {}) { element.setAttribute(attr, value); }); } - -/** - * NOTE: createElement Helper Function - */ -function isHTMLElement(element: DocumentFragment | HTMLElement) { - return element instanceof HTMLElement; -} - -function isNullOrUndefined(node: VNode) { - return node === null || node === undefined; -} - -function isPrimitive(node: VNode) { - return typeof node === 'string' || typeof node === 'number'; -} diff --git a/src/lib/jsx/jsx-runtime.type.ts b/src/lib/jsx/jsx-runtime.type.ts new file mode 100644 index 0000000..fc92286 --- /dev/null +++ b/src/lib/jsx/jsx-runtime.type.ts @@ -0,0 +1,13 @@ +import { FRAGMENT } from './jsx-runtime.constant'; + +export type Fragment = typeof FRAGMENT; +export type Type = keyof HTMLElementTagNameMap | Component | Fragment; +export type Props = Record; +export type VNode = string | number | null | undefined | VDOM; +export type Component = (props: Props) => VDOM; + +export interface VDOM { + type?: Type; + props: Props; + children: VNode[]; +} diff --git a/src/lib/jsx/jsx-runtime.util.ts b/src/lib/jsx/jsx-runtime.util.ts new file mode 100644 index 0000000..9397e27 --- /dev/null +++ b/src/lib/jsx/jsx-runtime.util.ts @@ -0,0 +1,16 @@ +import type { VNode } from './jsx-runtime.type'; + +/** + * NOTE: createElement Helper Function + */ +export function isHTMLElement(element: DocumentFragment | HTMLElement) { + return element instanceof HTMLElement; +} + +export function isNullOrUndefined(node: VNode) { + return node === null || node === undefined; +} + +export function isPrimitive(node: VNode) { + return typeof node === 'string' || typeof node === 'number'; +} From 49432e13311fa748e9a44c125e049449d4b2f082 Mon Sep 17 00:00:00 2001 From: d5ng Date: Fri, 3 Jan 2025 10:02:11 +0900 Subject: [PATCH 12/16] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20=EB=A0=8C=EB=8D=94?= =?UTF-8?q?=EB=A7=81=20=EC=B5=9C=EC=A0=81=ED=99=94=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 23 ++++++------ src/lib/jsx/index.ts | 4 ++ src/lib/jsx/jsx-runtime.ts | 65 ++++++++++++++++++++++++++++++++- src/lib/jsx/jsx-runtime.type.ts | 4 +- src/lib/jsx/jsx-runtime.util.ts | 4 ++ src/lib/render.ts | 49 +++++++++++++++++++++++++ src/main.tsx | 5 +-- vite.config.ts | 2 +- 8 files changed, 137 insertions(+), 19 deletions(-) create mode 100644 src/lib/jsx/index.ts create mode 100644 src/lib/render.ts diff --git a/src/App.tsx b/src/App.tsx index b7eaed0..9b8a8bc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,15 @@ -export function Custom({ className }: { className: string }) { - return ( -
- Custom -
- ); -} +import { useState } from './lib/render'; + +export default function App() { + const [count, setCount] = useState(1); + const handleIncrease = () => { + setCount(count + 1); + }; -export default function App({ count }: { count: number }) { return ( - <> - - Hello World {count} - +
+

{count}

+ +
); } diff --git a/src/lib/jsx/index.ts b/src/lib/jsx/index.ts new file mode 100644 index 0000000..48ad347 --- /dev/null +++ b/src/lib/jsx/index.ts @@ -0,0 +1,4 @@ +import { h } from '@/lib/jsx/jsx-runtime'; +import { FRAGMENT } from './jsx-runtime.constant'; + +export { h, FRAGMENT }; diff --git a/src/lib/jsx/jsx-runtime.ts b/src/lib/jsx/jsx-runtime.ts index 5083384..3a981f6 100644 --- a/src/lib/jsx/jsx-runtime.ts +++ b/src/lib/jsx/jsx-runtime.ts @@ -1,4 +1,4 @@ -import { isHTMLElement, isNullOrUndefined, isPrimitive } from './jsx-runtime.util'; +import { isHTMLElement, isNullOrUndefined, isPrimitive, isVDOM } from './jsx-runtime.util'; import { FRAGMENT } from './jsx-runtime.constant'; import type { Type, Props, VNode, VDOM } from './jsx-runtime.type'; @@ -35,6 +35,69 @@ function elementSetAttribute(element: HTMLElement, props: Props = {}) { Object.entries(props) .filter(([_, value]) => value) .forEach(([attr, value]) => { + if (attr.startsWith('on') && typeof props[attr] === 'function') { + const eventType = attr.slice(2).toLowerCase(); + element.addEventListener(eventType, props[attr]); + } + element.setAttribute(attr, value); }); } + +const diffTextVDOM = (newVDOM: VNode, currentVDOM: VNode) => { + if (JSON.stringify(newVDOM) === JSON.stringify(currentVDOM)) return false; + if (typeof newVDOM === 'object' || typeof currentVDOM === 'object') return false; + return true; +}; + +export function updateElement(parent: HTMLElement, newNode?: VNode, oldNode?: VNode, index: number = 0) { + if (!newNode && oldNode) { + parent.removeChild(parent.childNodes[index]); + return; + } + + if (newNode && !oldNode) { + parent.appendChild(createElement(newNode)); + return; + } + + if (diffTextVDOM(newNode, oldNode)) { + parent.replaceChild(createElement(newNode), parent.childNodes[index]); + return; + } + + if (!isVDOM(oldNode) || !isVDOM(newNode)) return; + + if (newNode.type !== oldNode.type) { + parent.replaceChild(createElement(newNode), parent.childNodes[index]); + return; + } + + if (parent.childNodes.length) { + updateAttributes(parent.childNodes[index] as HTMLElement, newNode.props, oldNode.props); + } + + const maxLength = Math.max(newNode!.children.length, oldNode!.children.length); + + for (let i = 0; i < maxLength; i++) { + updateElement(parent.childNodes[index] as HTMLElement, newNode.children[i], oldNode.children[i], i); + } +} + +function updateAttributes(target: HTMLElement, newProps: Props = {}, oldProps: Props = {}) { + for (const [attr, value] of Object.entries(newProps)) { + if (oldProps[attr] === newProps[attr]) continue; + if (attr.startsWith('on') && typeof newProps[attr] === 'function') { + const eventType = attr.slice(2).toLowerCase(); + target.removeEventListener(eventType, oldProps[attr]); + target.addEventListener(eventType, newProps[attr]); + } + + target.setAttribute(attr, value); + } + + for (const attr of Object.keys(oldProps)) { + if (newProps[attr] !== undefined) continue; + target.removeAttribute(attr); + } +} diff --git a/src/lib/jsx/jsx-runtime.type.ts b/src/lib/jsx/jsx-runtime.type.ts index fc92286..b5552ca 100644 --- a/src/lib/jsx/jsx-runtime.type.ts +++ b/src/lib/jsx/jsx-runtime.type.ts @@ -4,10 +4,10 @@ export type Fragment = typeof FRAGMENT; export type Type = keyof HTMLElementTagNameMap | Component | Fragment; export type Props = Record; export type VNode = string | number | null | undefined | VDOM; -export type Component = (props: Props) => VDOM; +export type Component = (props?: Props) => VDOM; export interface VDOM { - type?: Type; + type: Type; props: Props; children: VNode[]; } diff --git a/src/lib/jsx/jsx-runtime.util.ts b/src/lib/jsx/jsx-runtime.util.ts index 9397e27..60061fd 100644 --- a/src/lib/jsx/jsx-runtime.util.ts +++ b/src/lib/jsx/jsx-runtime.util.ts @@ -14,3 +14,7 @@ export function isNullOrUndefined(node: VNode) { export function isPrimitive(node: VNode) { return typeof node === 'string' || typeof node === 'number'; } + +export function isVDOM(node: VNode) { + return typeof node === 'object' && node !== null && 'type' in node; +} diff --git a/src/lib/render.ts b/src/lib/render.ts new file mode 100644 index 0000000..0a351f5 --- /dev/null +++ b/src/lib/render.ts @@ -0,0 +1,49 @@ +import { updateElement } from '@/lib/jsx/jsx-runtime'; +import { VDOM, Component } from './jsx/jsx-runtime.type'; + +interface Global { + rootElement: HTMLElement | null; + rootComponent: Component | null; + currentVDOM: null | VDOM; + states: any[]; + hookIndex: number; +} + +export const { useState, render } = (function () { + const global: Global = { + rootElement: null, + rootComponent: null, + currentVDOM: null, + states: [], + hookIndex: 0, + }; + + const render = (rootElement: HTMLElement, component: any) => { + global.rootElement = rootElement; + global.rootComponent = component; + _render(); + }; + + const _render = () => { + const newVDOM = global.rootComponent!(); + updateElement(global.rootElement!, newVDOM, global.currentVDOM); + global.hookIndex = 0; + global.currentVDOM = newVDOM; + }; + + const useState = (initialState: T) => { + const index = global.hookIndex; + const state = global.states[index] ?? initialState; + + const setState = (newState: T) => { + global.states[index] = newState; + _render(); + }; + + global.hookIndex++; + + return [state, setState]; + }; + + return { useState, render }; +})(); diff --git a/src/main.tsx b/src/main.tsx index 9220247..9f282fc 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,6 @@ -import { createElement } from '@/lib/jsx/jsx-runtime'; import App from './App'; +import { render } from './lib/render'; const app = document.getElementById('app')!; -app.appendChild(createElement()!); -console.log(); +render(app, App); diff --git a/vite.config.ts b/vite.config.ts index 0855e7e..3acd7fe 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ plugins: [tsconfigPaths()], esbuild: { jsx: 'transform', - jsxInject: `import { h, FRAGMENT } from '@/lib/jsx/jsx-runtime'`, + jsxInject: `import { h, FRAGMENT } from '@/lib/jsx'`, jsxFactory: 'h', jsxFragment: 'FRAGMENT', }, From 7d4d3dc7ccb0dc66cf1683d682c07e78be7d0ff8 Mon Sep 17 00:00:00 2001 From: d5ng Date: Sat, 4 Jan 2025 10:46:32 +0900 Subject: [PATCH 13/16] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20Fragment=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 20 ++++++++++++++++++-- src/lib/jsx/jsx-runtime.ts | 19 ++++++++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 9b8a8bc..6794c13 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,31 @@ import { useState } from './lib/render'; +function Wrapper() { + return ( + <> +

HelloWorld

+ + ); +} + export default function App() { const [count, setCount] = useState(1); const handleIncrease = () => { setCount(count + 1); }; + // return ( + //
+ //

{count}

+ // + //
+ // ); + return ( -
+ <>

{count}

-
+ + ); } diff --git a/src/lib/jsx/jsx-runtime.ts b/src/lib/jsx/jsx-runtime.ts index 3a981f6..5d87d58 100644 --- a/src/lib/jsx/jsx-runtime.ts +++ b/src/lib/jsx/jsx-runtime.ts @@ -61,6 +61,8 @@ export function updateElement(parent: HTMLElement, newNode?: VNode, oldNode?: VN return; } + if (!parent.childNodes[index]) return; + if (diffTextVDOM(newNode, oldNode)) { parent.replaceChild(createElement(newNode), parent.childNodes[index]); return; @@ -68,6 +70,15 @@ export function updateElement(parent: HTMLElement, newNode?: VNode, oldNode?: VN if (!isVDOM(oldNode) || !isVDOM(newNode)) return; + const maxLength = Math.max(newNode!.children.length, oldNode!.children.length); + + if (newNode.type === FRAGMENT || oldNode.type === FRAGMENT) { + const fragmentElement = parent.childNodes[index]?.parentNode || parent; + Array.from({ length: maxLength }).forEach((_, i) => + updateElement(fragmentElement as HTMLElement, newNode.children[i], oldNode.children[i], i), + ); + } + if (newNode.type !== oldNode.type) { parent.replaceChild(createElement(newNode), parent.childNodes[index]); return; @@ -77,11 +88,9 @@ export function updateElement(parent: HTMLElement, newNode?: VNode, oldNode?: VN updateAttributes(parent.childNodes[index] as HTMLElement, newNode.props, oldNode.props); } - const maxLength = Math.max(newNode!.children.length, oldNode!.children.length); - - for (let i = 0; i < maxLength; i++) { - updateElement(parent.childNodes[index] as HTMLElement, newNode.children[i], oldNode.children[i], i); - } + Array.from({ length: maxLength }).forEach((_, i) => + updateElement(parent.childNodes[index] as HTMLElement, newNode.children[i], oldNode.children[i], i), + ); } function updateAttributes(target: HTMLElement, newProps: Props = {}, oldProps: Props = {}) { From bb01fc40efe1d0139754e3841309fa3e9edba67d Mon Sep 17 00:00:00 2001 From: d5ng Date: Mon, 6 Jan 2025 17:46:55 +0900 Subject: [PATCH 14/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20diffText?= =?UTF-8?q?DOM=20=ED=95=A8=EC=88=98=20=EC=84=A0=EC=96=B8=EB=AC=B8=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/jsx/jsx-runtime.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/jsx/jsx-runtime.ts b/src/lib/jsx/jsx-runtime.ts index 5d87d58..a0887c5 100644 --- a/src/lib/jsx/jsx-runtime.ts +++ b/src/lib/jsx/jsx-runtime.ts @@ -44,11 +44,11 @@ function elementSetAttribute(element: HTMLElement, props: Props = {}) { }); } -const diffTextVDOM = (newVDOM: VNode, currentVDOM: VNode) => { +function diffTextVDOM(newVDOM: VNode, currentVDOM: VNode) { if (JSON.stringify(newVDOM) === JSON.stringify(currentVDOM)) return false; if (typeof newVDOM === 'object' || typeof currentVDOM === 'object') return false; return true; -}; +} export function updateElement(parent: HTMLElement, newNode?: VNode, oldNode?: VNode, index: number = 0) { if (!newNode && oldNode) { From 0852f4c25d7253818a0332fdd96f069acb335d8a Mon Sep 17 00:00:00 2001 From: d5ng Date: Wed, 8 Jan 2025 11:04:43 +0900 Subject: [PATCH 15/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 +- src/lib/core/index.ts | 49 +++++++++++++++++++++++++++++++++++++++++++ src/lib/render.ts | 49 ------------------------------------------- src/main.tsx | 2 +- 4 files changed, 51 insertions(+), 51 deletions(-) create mode 100644 src/lib/core/index.ts delete mode 100644 src/lib/render.ts diff --git a/src/App.tsx b/src/App.tsx index 6794c13..5205470 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useState } from './lib/render'; +import { useState } from './lib/core'; function Wrapper() { return ( diff --git a/src/lib/core/index.ts b/src/lib/core/index.ts new file mode 100644 index 0000000..d94cdd8 --- /dev/null +++ b/src/lib/core/index.ts @@ -0,0 +1,49 @@ +import { updateElement } from '@/lib/jsx/jsx-runtime'; +import { VDOM, Component } from '../jsx/jsx-runtime.type'; + +interface Internals { + rootElement: HTMLElement | null; + rootComponent: Component | null; + currentVDOM: null | VDOM; + states: any[]; + hookIndex: number; +} + +export const { useState, render } = (function () { + const INTERNALS: Internals = { + rootElement: null, + rootComponent: null, + currentVDOM: null, + states: [], + hookIndex: 0, + }; + + const render = (rootElement: HTMLElement, component: Component) => { + INTERNALS.rootElement = rootElement; + INTERNALS.rootComponent = component; + _render(); + }; + + const _render = () => { + const newVDOM = INTERNALS.rootComponent!(); + updateElement(INTERNALS.rootElement!, newVDOM, INTERNALS.currentVDOM); + INTERNALS.hookIndex = 0; + INTERNALS.currentVDOM = newVDOM; + }; + + const useState = (initialState: T) => { + const index = INTERNALS.hookIndex; + const state = INTERNALS.states[index] ?? initialState; + + const setState = (newState: T) => { + INTERNALS.states[index] = newState; + _render(); + }; + + INTERNALS.hookIndex++; + + return [state, setState]; + }; + + return { useState, render }; +})(); diff --git a/src/lib/render.ts b/src/lib/render.ts deleted file mode 100644 index 0a351f5..0000000 --- a/src/lib/render.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { updateElement } from '@/lib/jsx/jsx-runtime'; -import { VDOM, Component } from './jsx/jsx-runtime.type'; - -interface Global { - rootElement: HTMLElement | null; - rootComponent: Component | null; - currentVDOM: null | VDOM; - states: any[]; - hookIndex: number; -} - -export const { useState, render } = (function () { - const global: Global = { - rootElement: null, - rootComponent: null, - currentVDOM: null, - states: [], - hookIndex: 0, - }; - - const render = (rootElement: HTMLElement, component: any) => { - global.rootElement = rootElement; - global.rootComponent = component; - _render(); - }; - - const _render = () => { - const newVDOM = global.rootComponent!(); - updateElement(global.rootElement!, newVDOM, global.currentVDOM); - global.hookIndex = 0; - global.currentVDOM = newVDOM; - }; - - const useState = (initialState: T) => { - const index = global.hookIndex; - const state = global.states[index] ?? initialState; - - const setState = (newState: T) => { - global.states[index] = newState; - _render(); - }; - - global.hookIndex++; - - return [state, setState]; - }; - - return { useState, render }; -})(); diff --git a/src/main.tsx b/src/main.tsx index 9f282fc..15d888d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,5 @@ import App from './App'; -import { render } from './lib/render'; +import { render } from './lib/core'; const app = document.getElementById('app')!; From 7029af64f9c68fc402ce860a37646c726e3c9761 Mon Sep 17 00:00:00 2001 From: d5ng Date: Wed, 8 Jan 2025 11:07:00 +0900 Subject: [PATCH 16/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=ED=83=80=EC=9E=85=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 +- src/lib/core/{index.ts => hooks.ts} | 11 ++--------- src/lib/core/hooks.type.ts | 9 +++++++++ src/main.tsx | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) rename src/lib/core/{index.ts => hooks.ts} (81%) create mode 100644 src/lib/core/hooks.type.ts diff --git a/src/App.tsx b/src/App.tsx index 5205470..2613a68 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useState } from './lib/core'; +import { useState } from './lib/core/hooks'; function Wrapper() { return ( diff --git a/src/lib/core/index.ts b/src/lib/core/hooks.ts similarity index 81% rename from src/lib/core/index.ts rename to src/lib/core/hooks.ts index d94cdd8..8ecf6e4 100644 --- a/src/lib/core/index.ts +++ b/src/lib/core/hooks.ts @@ -1,13 +1,6 @@ import { updateElement } from '@/lib/jsx/jsx-runtime'; -import { VDOM, Component } from '../jsx/jsx-runtime.type'; - -interface Internals { - rootElement: HTMLElement | null; - rootComponent: Component | null; - currentVDOM: null | VDOM; - states: any[]; - hookIndex: number; -} +import { Component } from '../jsx/jsx-runtime.type'; +import { Internals } from './hooks.type'; export const { useState, render } = (function () { const INTERNALS: Internals = { diff --git a/src/lib/core/hooks.type.ts b/src/lib/core/hooks.type.ts new file mode 100644 index 0000000..ce45109 --- /dev/null +++ b/src/lib/core/hooks.type.ts @@ -0,0 +1,9 @@ +import { VDOM, Component } from '../jsx/jsx-runtime.type'; + +export interface Internals { + rootElement: HTMLElement | null; + rootComponent: Component | null; + currentVDOM: null | VDOM; + states: any[]; + hookIndex: number; +} diff --git a/src/main.tsx b/src/main.tsx index 15d888d..6068b65 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,5 @@ import App from './App'; -import { render } from './lib/core'; +import { render } from './lib/core/hooks'; const app = document.getElementById('app')!;