์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- TypeScript
- icecandidate
- Node.js
- js
- ์ด๋ถํ์
- ์๋ฐ์คํฌ๋ฆฝํธ
- ๋ธ๋ฃจํธํฌ์ค
- ๋ถ์คํธ์บ ํ์น๋ชจ๋ฐ์ผ
- router v6
- ์ฝํ
- ์๋ฐฉํฅ ์ฐ๊ฒฐ ๋ฆฌ์คํธ
- ๊ณผ์ ํ ์คํธ
- Redux toolkit
- ๋ถ์คํธ์ปจํผ๋ฐ์ค
- JavaScript
- React
- ์นด์นด์ค์ฑ์ฉ
- ๋ฆฌ๋์ค ํดํท
- ์นด์นด์ค
- DP
- ๋์ ๊ณํ๋ฒ
- ์ด๋ฏธ์ง ์์
- ์ฝ๋ ํฌ๋ฉง
- ํ๋ก๊ทธ๋๋จธ์ค
- ๋๋๊ทธ ์ด๋ฒคํธ
- ์๊ณ ๋ฆฌ์ฆ
- svgํ์ผ ๋ค๋ฃจ๊ธฐ
- ๋ฐฑ์ค
- custom hook
- ์ฝ๋ฉํ ์คํธ
- Today
- Total
๐ฅ dev-ruby
[VanillaJS] ํ๋ก๊ทธ๋๋จธ์ค ๊ณผ์ ํ ์คํธ๋ฅผ ์ฐ์ตํ๋ฉด์ ๋ฐฐ์ด ์น ํ์ด์ง ๊ตฌ์กฐ ๋ณธ๋ฌธ
[VanillaJS] ํ๋ก๊ทธ๋๋จธ์ค ๊ณผ์ ํ ์คํธ๋ฅผ ์ฐ์ตํ๋ฉด์ ๋ฐฐ์ด ์น ํ์ด์ง ๊ตฌ์กฐ
ruby_s 2022. 5. 11. 13:07์ด๋ฒ์ ์ฐํ
์บ 5๊ธฐ 2์ฐจ ๊ณผ์ ํ
์คํธ๋ฅผ ์ค๋นํ๊ธฐ ์ํด ํ๋ก๊ทธ๋๋จธ์ค์ ๊ณ ์์ด ์ฌ์ง์ฒฉ ์ ํ๋ฆฌ์ผ์ด์
์ด๋ผ๋ ๊ณผ์ ํ
์คํธ๋ฅผ ํ์ด๋ณด์๋ค. ์ง๋๋ฒ์ ๊ณ ์์ด ์ฌ์ง ๊ฒ์ํ๊ธฐ๋ฅผ ํ์๋๋ฐ ๊ทธ๋ ๋ฐ๋๋ผJS๊ฐ ์ฒ์์ด๋ผ ์ง์ง ์๋ฌด๊ฒ๋ ๋ชจ๋ฅด๊ณ ์ด๊ฒ์ ๊ฒ ๋ค์ง๋ฉด์ ๊ตฌํํ๋๋ ๋จธ๋ฆฌ์ ๋จ์์๋๊ฒ ์๋ค.. ๋ฐ๋๋ผ JS์์ฒด๊ฐ ๋๋ฌด ์ฌ๋ฏธ์์ด์ ๊พธ์ญ๊พธ์ญ ํด์ ๊ทธ๋ฐ๊ฒ๋ ์๋ ๊ฒ ๊ฐ๋ค ใ
๋ญ ์จ๋ ์๋ก ์ ์ด๋งํ๊ณ ,
์ด๋ฒ ํฌ์คํ ์์๋ ๊ณผ์ ํ ์คํธ๋ฅผ ๋ฑ ์ด์์ ๋ ์๋ฌด๊ฒ๋ ์๋ ์ ํ์ผ์ ์ด๋ป๊ฒ ๊ตฌ์กฐ๋ฅผ ๊ตฌ์ฑํด๋๊ฐ๋ฉด ๋ ์ง ์์๋ณผ ๊ฒ์ด๋ค.
๋จผ์ ์ฒ์์ index.html์ด ์์ ๊ฒ์ด๋ค. ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ๊ตฌํํ๋ฉด์ ํด๋น ํ์ผ์์ ์์๊ฐ ๊ณ์ ์ถ๊ฐ ๋ ๊ฒ์ด๋ค.
htmlํ์ผ์์ ํ ์ผ์ jsํ์ผ์ ์ฐ๊ฒฐํด์ฃผ๋ฉด ๋๋ค.
<html>
<head>
<title>๊ณ ์์ด ์ฌ์ง์ฒฉ!</title>
<link rel="stylesheet" href="./src/styles/style.css" />
</head>
<body>
<h1>๊ณ ์์ด ์ฌ์ง์ฒฉ</h1>
<main class="App"></main>
<script src="src/js/index.js" type="module"></script>
</body>
</html>
scriptํ๊ทธ๋ <head>์์ ๋ฃ์ด๋, <body>์์ ๋ฃ์ด๋, <html>์์ ๋ฃ์ด๋ ์๊ด ์๋ค. ๋ค๋ง, html์ bodyํ๊ทธ๋ณด๋ค jsํ์ผ์ผ ๋จผ์ ์ปดํ์ผ๋ ์ jsํ์ผ์ body์์ ํ๊ทธ๋ค์ ์ฝ์ง ๋ชปํ ๊ฒ์ด๋ค. ์ฐ๋ฆฌ๊ฐ DOM์ ์กฐ์ ํ๋ ค๋ฉด ์กฐ์ ํ ์์๋ค๋ณด๋ค ์๋ ์ชฝ์ script๋ฅผ ์ฐ๊ฒฐํด์ค์ผ ํ๋ค.
์ฌ๊ธฐ์ ๋ ์ค์ํ ๊ฒ ์๋ค. scriptํ๊ทธ์ ์์ฑ์ ๋ณด๋ฉด type="module"์ด ๋ณด์ธ๋ค. export/import๋ฐฉ์์ผ๋ก ๊ตฌํํ๊ณ ์ถ๋ค๋ฉด ์ด ์์ฑ์ ๊ผญ ์ถ๊ฐํด์ค์ผ ํ๋ค.
์ด์ jsํ์ผ์ ํ๋์ฉ ์์ฑํด๋ณด์. ๋๋ ์๋์ฒ๋ผ ํ์ผ๊ตฌ์กฐ๋ฅผ ๊ตฌ์ฑํ๋ค.
์ปดํฌ๋ํธ ๋ถ๋ฆฌ๋ ์ ํ์ด ์๋ ํ์๋ค !
1. index.js
import App from "./App.js";
new App(document.querySelector(".App"));
๊ฐ์ฅ ๋จผ์ ํด์ผ ํ ์ผ์ ๋ฉ์ธ ์๋ฐ์คํฌ๋ฆฝํธ ํ์ผ(App,js)์ DOM์์๋ฅผ ์ฐ๊ฒฐํด์ฃผ๋ ๊ฒ์ด๋ค. import๋ก App์ ๊ฐ์ ธ์จ ํ, new ์ฐ์ฐ์๋ก App๊ฐ์ฒด์ ์ ์ต์๋จ์ App DOM์์๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
2. api.js
const API_END_POINT =
"https://zl3m4qq0l9.execute-api.ap-northeast-2.amazonaws.com/dev";
export const request = async (nodeId) => {
try {
const response = await fetch(`${API_END_POINT}/${nodeId || ""}`, {
method: "GET",
});
if (response.ok) return response.json();
else throw new Error("error");
} catch (e) {
throw new Error("error");
}
};
ํ๋ฒ ๊ตฌํํด๋ณด๋๊น api๋ฅผ ์์ฒญํ๋ ๋ถ๋ถ์ ์ฒ์์ ๊ตฌํํด๋๋ ๊ฒ์ด ํธํ ๊ฒ ๊ฐ๋ค๊ณ ๋๊ผ๋ค. ๊ทธ๋์ ๋ค์์ api๋ฅผ ์์ฒญํ๋ ํ์ผ์ด๋ค.
๊ณตํต url์ ์ ์ญ๋ณ์๋ก ๋์๋ค. ๋ค์ด๋ฐ(API_END_POINT)์ ๋ค๋ฅธ ์ฌ๋๋ค ์ฝ๋๋ฅผ ์ฐธ๊ณ ํ๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก api์์ฒญ ํจ์๋ async awaitํจ์๋ก ๊ตฌํํ์. try catch๋ฌธ์ผ๋ก ์์ธ ์ฒ๋ฆฌ๋ ํด์ฃผ์.
Promise๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ ๋ฌ ๋ฐ์์ก๋ค๋ฉด response๋ฅผ json์ผ๋ก ๋ฆฌํดํด์ฃผ์.
(์๋ฌ๋ฅผ ๋ฑ์ ๋๋ throw๋ก ๋ฑ์ด์ค ๊ฒ)
3. App.js
import { request } from "../api/api.js";
import Breadcrumb from "./Breadcrumb.js";
import Nodes from "./Nodes.js";
import ImageView from "./ImageView.js";
import Loading from "./Loading.js";
const cache = {};
export default function App($app) {
this.state = {
isRoot: true,
nodes: [],
depthStack: [],
currentPath: null,
selectedFilePath: null,
isLoading: true,
};
this.setState = (nextState) => {
this.state = nextState;
};
this.init = async () => {
try {
const rootNodes = await request(null);
this.setState({
...this.state,
nodes: rootNodes,
});
} catch (e) {
throw new Error(e);
}
};
this.init();
}
์ด๊ธฐ App์ด๋ค.
this.state: `this.${๋ณ์๋ช }`์ด๋ฐ ๋ฐฉ์์ผ๋ก ์ง์ญ๋ณ์๋ฅผ ์ ์ธํด์ฃผ๋ฉด ๋๋ค. state์์ ํ์ํ ์์๋ค์ ํ๋์ฉ ์ถ๊ฐํด์ฃผ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋ค.
this.setState: ์ง์ญ๋ณ์๋ค์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ๊ฐ ์ปดํฌ๋ํธ์ ๋ณ๊ฒฝ์ฌํญ์ ๋ฐ์ํด์ฃผ๊ธฐ ์ํด ์์ฑํ ํจ์์ด๋ค.
this.init: ์ฒซ ์ด๊ธฐ ํ๋ฉด์ ๊ตฌ์ฑํ๊ธฐ ์ํด ๋ง๋ ํจ์์ด๋ค.
1. await request()๋ก api์์ฒญ์ ๋ฐ์์จ๋ค.
2. this.setState()๋ก ๋ณ๊ฒฝ์ฌํญ์ ๋ฐ์ํด์ค๋ค. -> ...this.state: ๊ธฐ์กด state๋ ๊ทธ๋๋ก ๋ฐ์ํ๊ณ nodes๋ rootNodes๋ก ๋ณ๊ฒฝํ๋๋ก.
4. Nodes.js
export default function Nodes({ $app, state, onClick, onBackClick }) {
this.state = state;
this.onClick = onClick;
this.onBackClick = onBackClick;
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
const nodes = document.createElement("div");
nodes.className = "Nodes";
this.render = () => {
const template = this.state.nodes
.map((node) => {
return `
<div class="Node" data-node-id="${node.id}">
<img src="./assets/${node.type.toLowerCase()}.png"
data-node-id="${node.id}"/>
<div>${node.name}</div>
</div>
`;
})
.join("");
const backTemplate = `
<div class="Node">
<img src="./assets/prev.png" />
</div>`;
nodes.innerHTML = this.state.isRoot
? template
: `
${backTemplate}
${template}
`;
$app.appendChild(nodes);
};
nodes.addEventListener("click", (e) => {
const { nodeId } = e.target.closest(".Node").dataset;
if (nodeId) {
const selectedNode = this.state.nodes.find((node) => node.id === nodeId);
if (selectedNode) {
this.onClick(selectedNode);
}
} else {
this.onBackClick();
}
});
this.render();
}
๊ทธ๋ฅ ์ต์ข ์ฝ๋๋ฅผ ๋ถ์ด๊ฒ ๋ค ์ด ์ปดํฌ๋ํธ๋ ๋ง์ฐฌ๊ฐ์ง๋ก ์๋จ์ this.${๋ณ์๋ช }์ผ๋ก ํ์ํ ๋ณ์๋ค์ ์ ์ํ๋ค.
์ฌ๊ธฐ์ ์ปดํฌ๋ํธ ์์กด๋๋ฅผ ์ค์ด๊ธฐ ์ํด ๋งค๊ฐ๋ณ์๋ก ๋ฐ์์จ $app, state ๊ฐ์ ๋ณ์๋ค์ this.state = state ์ฒ๋ผ ์ง์ญ๋ณ์๋ก ๋ง๋ค์ด ์ฃผ๋๋ก ํ๋ค.
์ฌ๊ธฐ๋ this.setState๊ฐ ๋ณด์ด๋๋ฐ, Nodes์ setState๋ App.js์์ ํธ์ถ๋๋ค. App.js์ setState์์์ ํธ์ถ๋๋ค. App์์ ์ํ๊ฐ ๋ณ๊ฒฝ๋๋ฉด setState๊ฐ ํธ์ถ๋๊ณ ๊ทธ ์์์ nodes.setState({๋ณ๊ฒฝ๋ ๋ณ์}) ์ด๋ ๊ฒ ํธ์ถ๋๋ค. ๊ทธ๋ผNodes์ ์ง์ญ๋ณ์์ธ this.state์ App์์ ๋ณ๊ฒฝ๋์๋ nextState๊ฐ ๋ฐ์๋๋ค.
์๋ ์ฐธ๊ณ .
this.setState = (nextState) => {
this.state = nextState;
nodes.setState({
isRoot: this.state.isRoot,
nodes: this.state.nodes,
currentPath: this.state.currentPath,
});
};
๊ทธ๋ฆฌ๊ณ ๊ผญ ์ปดํฌ๋ํธ ์์ ์ง์ญ๋ณ์๊ฐ ๋ณ๊ฒฝ๋์๋ค๋ฉด this.render()๋ฅผ ๋ค์ ํธ์ถํด์ค์ผ html์ ๋ฐ์์ด ๋๋ค. ๋ฐ๋ผ์ this.setState์์ ๋ณ๊ฒฝ์ฌํญ์ด ๋ค ๋ฐ์๋ ํ render()๋ฅผ ํธ์ถํด์ฃผ๋๋ก ํ๋ค. ์ ๊ทธ๋ฆฌ๊ณ ๋ณ๊ฒฝ๋์์ ๋๋ง ํธ์ถ ๋๋๊ฒ ์๋๋ผ ์ด๊ธฐ์๋ ํธ์ถ์ด ํ์ํ๊ธฐ ๋๋ฌธ์ ์ตํ๋จ์ this.render(); ๋ ์ถ๊ฐํด์ค์ผ ํ๋ค.
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
render์์๋ ํด๋น ์ปดํฌ๋ํธ์ ๊ด๋ จ๋ ์ถ๊ฐํด์ค์ผ ํ ์์๋ค์ ์กฐ์ํ๋ค. ์กฐ์๋ง ํ๋ฉด ์๋๋ค. ์ฐ๋ฆฌ๋ new App์์ $app์ผ๋ก ์ต์๋จ DOM์์๋ฅผ ์ ๋ฌ ๋ฐ์๊ธฐ ๋๋ฌธ์ ๊ฐ ์ปดํฌ๋ํธ๋ง๋ค $app์ ์ ๋ฌํด์ค ํ์๊ฐ ์๋ค. $app์ ์ถ๊ฐํด์ค ์์๋ค์ appendChild๋ก ์ถ๊ฐํด์ค๋ค.
$app.appendChild(nodes);
์ด๋ฒคํธ ๋ฆฌ์ค๋์ ๊ฒฝ์ฐ ์ฌ๋งํ๋ฉด renderํจ์ ์์ ์ ์ํ์ง ๋ง๊ณ ๋ฐ์ ์ ์ํ๋๋ก ํ์. -> ์ด๋ฒคํธ ์ต์ ํ
nodes.addEventListener("click", (e) => {
const { nodeId } = e.target.closest(".Node").dataset;
if (nodeId) {
const selectedNode = this.state.nodes.find((node) => node.id === nodeId);
if (selectedNode) {
this.onClick(selectedNode);
}
} else {
this.onBackClick();
}
});
์ด๋ฐ์์ผ๋ก ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ค๋ ๊ตฌ์ฑํ๋ฉด ๋๋ค ! ์๋์ ์ ์ฒด ์ฝ๋๋ฅผ ์ฒจ๋ถํ๊ฒ ๋ค.
index.js
import App from "./App.js";
new App(document.querySelector(".App"));
api.js
const API_END_POINT =
"https://zl3m4qq0l9.execute-api.ap-northeast-2.amazonaws.com/dev";
export const request = async (nodeId) => {
try {
const response = await fetch(`${API_END_POINT}/${nodeId || ""}`, {
method: "GET",
});
if (response.ok) return response.json();
else throw new Error("error");
} catch (e) {
throw new Error("error");
}
};
App.js
import { request } from "../api/api.js";
import Breadcrumb from "./Breadcrumb.js";
import Nodes from "./Nodes.js";
import ImageView from "./ImageView.js";
import Loading from "./Loading.js";
const cache = {};
export default function App($app) {
this.state = {
isRoot: true,
nodes: [],
depthStack: [],
currentPath: null,
selectedFilePath: null,
isLoading: true,
};
const loading = new Loading({ $app, initialState: this.state.isLoading });
const breadcrumb = new Breadcrumb({
$app,
state: this.state.depthStack,
onClick: (index) => {
// ๋ฃจํธ ๋
ธ๋๋ฅผ ์ ํํ ๊ฒฝ์ฐ
if (index === null) {
this.setState({
...this.state,
isRoot: true,
depthStack: [],
nodes: cache["root"],
selectedFilePath: null,
});
return;
}
// ํ์ฌ path๋ฅผ ์ ํํ ๊ฒฝ์ฐ
if (index === this.state.depthStack.length) {
return;
}
// ๊ทธ ์ธ
const depthStack = this.state.depthStack.slice(0, index + 1);
this.setState({
...this.state,
depthStack: depthStack,
nodes: cache[depthStack[depthStack.length - 1]].id,
selectedFilePath: null,
});
},
});
const nodes = new Nodes({
$app,
state: this.state,
onClick: async (node) => {
try {
this.setState({
...this.state,
isLoading: true,
});
if (node.type === "DIRECTORY") {
if (cache[node.id]) {
this.setState({
...this.state,
isRoot: false,
depthStack: [...this.state.depthStack, node],
nodes: cache[node.id],
});
} else {
const nextNodes = await request(node.id);
this.setState({
...this.state,
isRoot: false,
depthStack: [...this.state.depthStack, node],
nodes: nextNodes,
});
cache[node.id] = nextNodes;
}
} else if (node.type === "FILE") {
this.setState({
...this.state,
selectedFilePath: node.filePath,
});
}
} catch (e) {
throw new Error(e.message);
} finally {
this.setState({
...this.state,
isLoading: false,
});
}
},
onBackClick: async () => {
try {
this.setState({
...this.state,
isLoading: true,
});
this.state.depthStack.pop();
const prevId = this.state.depthStack.length
? this.state.depthStack[this.state.depthStack.length].id
: null;
if (prevId === null) {
this.setState({
isRoot: true,
nodes: cache["root"],
depthStack: this.state.depthStack,
});
} else {
this.setState({
...this.state,
isRoot: false,
nodes: cache[prevId],
selectedFilePath: null,
depthStack: this.state.depthStack,
});
}
} catch (e) {
throw new Error(e.message);
} finally {
this.setState({
...this.state,
isLoading: false,
});
}
},
});
const imageView = new ImageView({ $app, state: this.state.selectedFilePath });
this.setState = (nextState) => {
this.state = nextState;
nodes.setState({
isRoot: this.state.isRoot,
nodes: this.state.nodes,
currentPath: this.state.currentPath,
});
breadcrumb.setState(this.state.depthStack);
imageView.setState(this.state.selectedFilePath);
loading.setState(this.state.isLoading);
};
this.init = async () => {
try {
const rootNodes = await request(null);
this.setState({
...this.state,
nodes: rootNodes,
});
cache["root"] = rootNodes;
} catch (e) {
throw new Error(e);
} finally {
this.setState({
...this.state,
isLoading: false,
});
}
};
this.init();
}
Nodes.js
export default function Nodes({ $app, state, onClick, onBackClick }) {
this.state = state;
this.onClick = onClick;
this.onBackClick = onBackClick;
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
const nodes = document.createElement("div");
nodes.className = "Nodes";
this.render = () => {
const template = this.state.nodes
.map((node) => {
return `
<div class="Node" data-node-id="${node.id}">
<img src="./assets/${node.type.toLowerCase()}.png"
data-node-id="${node.id}"/>
<div>${node.name}</div>
</div>
`;
})
.join("");
const backTemplate = `
<div class="Node">
<img src="./assets/prev.png" />
</div>`;
nodes.innerHTML = this.state.isRoot
? template
: `
${backTemplate}
${template}
`;
$app.appendChild(nodes);
};
nodes.addEventListener("click", (e) => {
const { nodeId } = e.target.closest(".Node").dataset;
if (nodeId) {
const selectedNode = this.state.nodes.find((node) => node.id === nodeId);
if (selectedNode) {
this.onClick(selectedNode);
}
} else {
this.onBackClick();
}
});
this.render();
}
Breadcrumb.js
export default function Breadcrumb({ $app, state, onClick }) {
this.state = state;
this.onClick = onClick;
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
this.$target = document.createElement("nav");
this.$target.className = "Breadcrumb";
$app.appendChild(this.$target);
this.render = () => {
if (this.state.length) {
this.$target.innerHTML = `<div class="nav-item">root</div>
${this.state
.map(
(node, index) =>
`<div class="nav-item" data-index="${index + 1}">${node.name}</div>`
)
.join("")}`;
} else {
this.$target.innerHTML = `<div>root</div>`;
}
};
this.$target.addEventListener("click", (e) => {
const { index } = e.target.closest(".nav-item").dataset;
this.onClick(parseInt(index, 10) || null);
});
this.render();
}
ImageView.js
const IMAGE_PATH_PREFIX = `https://fe-dev-matching-2021-03-serverlessdeploymentbuck-t3kpj3way537.s3.ap-northeast-2.amazonaws.com/public`;
export default function ImageView({ $app, initialState }) {
this.state = initialState;
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
this.$target = document.createElement("div");
this.$target.className = "Modal ImageViewer";
this.render = () => {
this.$target.innerHTML = `
<div class="content">
${this.state ? `<img src="${IMAGE_PATH_PREFIX}${this.state}" />` : ""}
</div>
`;
this.$target.style.display = this.state ? "block" : "none";
};
this.$target.addEventListener("click", (e) => {
e.target === this.$target["Modal"]
? this.$target.classList.remove("ImageViewer")
: false;
this.$target.innerHTML = "";
this.$target.style.display = "none";
});
this.render();
$app.appendChild(this.$target);
}
Loading.js
export default function Loading({ $app, initialState }) {
this.state = initialState;
this.$target = document.createElement("div");
this.$target.className = "Loading Modal";
$app.appendChild(this.$target);
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
this.render = () => {
this.$target.innerHTML = `
<div class="content">
<img src="./assets/nyan-cat.gif"/>
</div>
`;
if (this.state) this.$target.style.display = "block";
else this.$target.style.display = "none";
};
this.render();
}