
사진: Unsplash의James Harrison
폴더구조
src
└───api
└─── api.js ++
└───route
│ └─── route.js ++
└───storage
│ └─── storage.js ++
└───page
└───page1
│ │ SubPage.js ++
│ └─── Page1.js ++
│ App.js ++
│ index.js ++
index.html ++
style.css
index.html
<html>
<head>
<title>타이틀</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<main class="App">
</main>
<script src="./index.js" type="module"></script>
</body>
</html>
src/route/route.js
const ROUTE_CHANGE_EVENT = 'ROUTE_CHANGE'
export const init = (onRouteChange) => {
window.addEventListener(ROUTE_CHANGE_EVENT, () => {
onRouteChange()
})
}
export const routeChange = (url, params) => {
history.pushState(null, null, url)
window.dispatchEvent(new CustomEvent(ROUTE_CHANGE_EVENT, params))
}
src/api/api.js
export const API_END_POINT = 'http://localhost:3000'
const request = async (url) => {
const res = await fetch(url)
if(res.ok) {
const json = await res.json()
return json
}
throw new Error('요청 실패')
}
export const fetchPage1 = async () => {
return await request(`${API_END_POINT}/page1`)
}
src/storage/storage.js
export const storage = localStorage
export const getItem = (key, defaultValue) => {
try {
const value = storage.getItem(key)
return value ? JSON.parse(value) : defaultValue
} catch (error) {
console.log(error)
return defaultValue
}
}
export const setItem = (key, value) => {
try {
storage.setItem(key, JSON.stringify(value))
} catch (error) {
console.log(error)
}
}
export const removeItem = (key) => {
try {
storage.removeItem(key);
} catch (error) {
console.log(error)
}
}
src/index.js
import App from './App.js'
new App({$target: document.querySelector('.App')})
src/App.js
import { init } from './route/route.js'
import Page1 from './page/Page1.js'
export default function App ({$target}){
this.state = {
prop1: null,
}
this.setState = (newState) => {
this.state = {
...this.state,
...newState
}
}
this.route = () => {
const { pathname } = location
$target.innerHTML = ``
if(pathname === '/web/'){
const Page1 = new Page1({$target, initialState: {
prop1: this.state.prop1
}}).render()
}
window.addEventListener('popstate', this.route)
init(this.route)
this.route()
}
src/page/Page1.js
import { fetchPage1 } from "../api/api.js"
import { routeChange } from "../route/route.js"
import SubPage1 from './page1/SubPage1.js'
export default function Page1 ({$target, initialState}) {
let subPage1 = null;
this.$element = document.createElement('div')
this.$element.className = 'page1'
$target.appendChild(this.$element)
this.render = () => {
this.$element.innerHTML = ``
this.$element.innerHTML = `
<li data-product-id="1">
<span> text </span>
</li>
<div class="subPage1"></div>
`
subPage1 = new SubPage1({$target: this.$element.querySelector('.subPage1') , initialState: { text: this.state.text} })
}
this.state = {
text: null,
...initialState,
}
this.setState = (newState) => {
this.state = {
...this.state,
...newState
}
this.render()
}
this.$element.addEventListener('click', (e) => {
const $li = e.target.closest('li')
const { productId } = $li.dataset
if (productId) {
routeChange(`/web`)
}
this.setState({text: 'subText'})
})
this.getPage1 = async () => {
await fetchPage1()
}
this.getPage1();
this.render()
}
src/page/page1/SubPage.js
import { routeChange } from "../../route/route.js"
export default function SubPage1 ({$target, initialState}) {
this.$element = document.createElement('ul')
$target.appendChild(this.$element)
this.render = () => {
if(this.state && this.state.text){
this.$element.innerHTML = `
<li>
<span> ${this.state.text} </span>
</li>
`
}
}
this.state = {
...initialState
}
this.setState = (newState) => {
this.state = {
...this.state,
...newState
}
this.render()
}
this.render()
}